Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Felt Python Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Run tests
env:
FELT_API_TOKEN: ${{ secrets.FELT_API_TOKEN }}
run: |
python tests/tests.py
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.DS_Store
.DS_Store

.python-version
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ refreshing files and (Geo)DataFrames or updating layer styles and element proper
pip install felt-python
```

## Usage
## Notebooks

See the [notebooks](/notebooks) directory for Juypter notebooks with complete examples of using the API.

## Basic Usage

### Authentication

Expand All @@ -37,7 +41,7 @@ import os
os.environ["FELT_API_TOKEN"] = "YOUR_API_TOKEN"
```

### Creating a map
### Create a map

```python
from felt_python import create_map
Expand All @@ -51,10 +55,10 @@ response = create_map(
map_id = response["id"]
```

### Uploading a file
### Upload anything

```python
from felt_python import upload_file, list_layers
from felt_python import upload_file

upload = upload_file(
map_id=map_id,
Expand Down Expand Up @@ -103,9 +107,9 @@ refresh_file_layer(

### Styling a layer
```python
from felt_python import get_layer_details, update_layer_style
from felt_python import get_layer, update_layer_style

current_style = get_layer_details(
current_style = get_layer(
map_id=map_id,
layer_id=layer_id,
)["style"]
Expand Down
105 changes: 95 additions & 10 deletions felt_python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# For now, hoist all functions to the top level
# Hoist all functions to the top level
from .maps import (
create_map,
delete_map,
get_map_details,
get_map,
update_map,
move_map
move_map,
create_embed_token,
add_source_layer,
# Deprecated
get_map_details,
)
from .exceptions import AuthError
from .layers import (
Expand All @@ -15,19 +19,56 @@
upload_url,
refresh_file_layer,
refresh_url_layer,
get_layer_details,
get_layer,
update_layer_style,
get_export_link,
download_layer,
update_layers,
delete_layer,
publish_layer,
create_custom_export,
get_custom_export_status,
duplicate_layers,
# Deprecated
get_layer_details,
)
from .elements import (
list_elements,
list_element_groups,
list_elements_in_group,
post_elements,
upsert_elements,
delete_element,
get_element_group,
upsert_element_groups,
# Deprecated:
post_elements,
post_element_group,
list_elements_in_group,
)
from .layer_groups import (
list_layer_groups,
get_layer_group,
update_layer_groups,
delete_layer_group,
publish_layer_group,
)
from .projects import (
list_projects,
create_project,
get_project,
update_project,
delete_project,
)
from .sources import (
list_sources,
create_source,
get_source,
update_source,
delete_source,
sync_source,
)
from .library import list_library_layers
from .comments import export_comments, resolve_comment, delete_comment
from .user import get_current_user

__doc__ = """
The official Python client for the Felt API
Expand All @@ -41,26 +82,70 @@
"""

__all__ = [
# Maps
"create_map",
"delete_map",
"get_map_details",
"update_map",
"move_map",
"create_embed_token",
"add_source_layer",
# Layers
"list_layers",
"upload_file",
"upload_geodataframe",
"upload_dataframe",
"upload_url",
"refresh_file_layer",
"refresh_url_layer",
"get_layer_details",
"get_layer",
"update_layer_style",
"get_export_link",
"download_layer",
"AuthError",
"update_layers",
"delete_layer",
"publish_layer",
"create_custom_export",
"get_custom_export_status",
"duplicate_layers",
# Layer groups
"list_layer_groups",
"get_layer_group",
"update_layer_groups",
"delete_layer_group",
"publish_layer_group",
# Elements
"list_elements",
"list_element_groups",
"list_elements_in_group",
"post_elements",
"upsert_elements",
"delete_element",
"upsert_element_groups",
# Projects
"list_projects",
"create_project",
"get_project",
"update_project",
"delete_project",
# Sources
"list_sources",
"create_source",
"get_source",
"update_source",
"delete_source",
"sync_source",
# Library
"list_library_layers",
# Comments
"export_comments",
"resolve_comment",
"delete_comment",
# User
"get_current_user",
# Exceptions
"AuthError",
# Deprecated
"post_elements",
"post_element_group",
"get_map_details",
"get_layer_details",
]
14 changes: 11 additions & 3 deletions felt_python/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
import typing
import urllib.request

from importlib.metadata import version, PackageNotFoundError

try:
import certifi
Expand All @@ -17,7 +17,7 @@
from .exceptions import AuthError


BASE_URL = "https://felt.com/api/v2/"
BASE_URL = os.getenv("FELT_BASE_URL", "https://felt.com/api/v2/")


def make_request(
Expand All @@ -35,7 +35,15 @@ def make_request(
"No API token found. Pass explicitly or set the FELT_API_TOKEN environment variable"
) from exc

data, headers = None, {"Authorization": f"Bearer {api_token}"}
try:
package_version = version("felt_python")
except PackageNotFoundError:
package_version = "local"

data, headers = None, {
"Authorization": f"Bearer {api_token}",
"User-Agent": f"felt-python/{package_version}",
}
if json is not None:
data = json_.dumps(json).encode("utf8")
headers["Content-Type"] = "application/json"
Expand Down
66 changes: 66 additions & 0 deletions felt_python/comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Comments"""

import json

from urllib.parse import urljoin

from .api import make_request, BASE_URL


COMMENT = urljoin(BASE_URL, "maps/{map_id}/comments/{comment_id}")
COMMENT_RESOLVE = urljoin(BASE_URL, "maps/{map_id}/comments/{comment_id}/resolve")
COMMENT_EXPORT = urljoin(BASE_URL, "maps/{map_id}/comments/export")


def export_comments(map_id: str, format: str = "json", api_token: str | None = None):
"""Export comments from a map

Args:
map_id: The ID of the map to export comments from
format: The format to export the comments in, either 'csv' or 'json' (default)
api_token: Optional API token

Returns:
The exported comments in the specified format
"""
url = f"{COMMENT_EXPORT.format(map_id=map_id)}?format={format}"
response = make_request(
url=url,
method="GET",
api_token=api_token,
)
return json.load(response)


def resolve_comment(map_id: str, comment_id: str, api_token: str | None = None):
"""Resolve a comment

Args:
map_id: The ID of the map that contains the comment
comment_id: The ID of the comment to resolve
api_token: Optional API token

Returns:
Confirmation of the resolved comment
"""
response = make_request(
url=COMMENT_RESOLVE.format(map_id=map_id, comment_id=comment_id),
method="POST",
api_token=api_token,
)
return json.load(response)


def delete_comment(map_id: str, comment_id: str, api_token: str | None = None):
"""Delete a comment

Args:
map_id: The ID of the map that contains the comment
comment_id: The ID of the comment to delete
api_token: Optional API token
"""
make_request(
url=COMMENT.format(map_id=map_id, comment_id=comment_id),
method="DELETE",
api_token=api_token,
)
Loading