Skip to content

Commit 108ce43

Browse files
authored
Add duplicate_map, update_layer_group (#28)
* Add duplicate_map, update_layer_group * Update visibility_interaction enum * Caption isn't deprecated
1 parent 4bf81dd commit 108ce43

File tree

7 files changed

+209
-15
lines changed

7 files changed

+209
-15
lines changed

felt_python/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
move_map,
88
create_embed_token,
99
add_source_layer,
10+
duplicate_map,
1011
# Deprecated
1112
get_map_details,
1213
)
@@ -47,6 +48,7 @@
4748
from .layer_groups import (
4849
list_layer_groups,
4950
get_layer_group,
51+
update_layer_group,
5052
update_layer_groups,
5153
delete_layer_group,
5254
publish_layer_group,
@@ -89,6 +91,7 @@
8991
"move_map",
9092
"create_embed_token",
9193
"add_source_layer",
94+
"duplicate_map",
9295
# Layers
9396
"list_layers",
9497
"upload_file",
@@ -110,6 +113,7 @@
110113
# Layer groups
111114
"list_layer_groups",
112115
"get_layer_group",
116+
"update_layer_group",
113117
"update_layer_groups",
114118
"delete_layer_group",
115119
"publish_layer_group",

felt_python/layer_groups.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,50 @@ def delete_layer_group(
100100
)
101101

102102

103+
def update_layer_group(
104+
map_id: str,
105+
layer_group_id: str,
106+
name: str = None,
107+
caption: str = None,
108+
ordering_key: int = None,
109+
visibility_interaction: str = None,
110+
api_token: str | None = None,
111+
):
112+
"""Update a single layer group
113+
114+
Args:
115+
map_id: The ID of the map containing the layer group
116+
layer_group_id: The ID of the layer group to update
117+
name: Optional new name for the layer group
118+
caption: Optional new caption for the layer group
119+
ordering_key: Optional new ordering key for positioning
120+
visibility_interaction: Optional visibility interaction setting
121+
("default", "slider")
122+
api_token: Optional API token
123+
124+
Returns:
125+
The updated layer group
126+
"""
127+
json_payload = {}
128+
129+
if name is not None:
130+
json_payload["name"] = name
131+
if caption is not None:
132+
json_payload["caption"] = caption
133+
if ordering_key is not None:
134+
json_payload["ordering_key"] = ordering_key
135+
if visibility_interaction is not None:
136+
json_payload["visibility_interaction"] = visibility_interaction
137+
138+
response = make_request(
139+
url=GROUP.format(map_id=map_id, layer_group_id=layer_group_id),
140+
method="POST",
141+
json=json_payload,
142+
api_token=api_token,
143+
)
144+
return json.load(response)
145+
146+
103147
def publish_layer_group(
104148
map_id: str,
105149
layer_group_id: str,

felt_python/maps.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
MAP_MOVE = urljoin(BASE_URL, "maps/{map_id}/move")
1515
MAP_EMBED_TOKEN = urljoin(BASE_URL, "maps/{map_id}/embed_token")
1616
MAP_ADD_SOURCE_LAYER = urljoin(BASE_URL, "maps/{map_id}/add_source_layer")
17+
MAP_DUPLICATE = urljoin(BASE_URL, "maps/{map_id}/duplicate")
1718

1819

1920
def create_map(
@@ -230,3 +231,45 @@ def add_source_layer(
230231
api_token=api_token,
231232
)
232233
return json.load(response)
234+
235+
236+
def duplicate_map(
237+
map_id: str,
238+
title: str = None,
239+
project_id: str = None,
240+
folder_id: str = None,
241+
api_token: str = None,
242+
):
243+
"""Duplicate a map
244+
245+
Args:
246+
map_id: The ID of the map to duplicate
247+
title: Optional title for the duplicated map
248+
project_id: The ID of the project to place the duplicated map in
249+
(mutually exclusive with folder_id)
250+
folder_id: The ID of the folder to place the duplicated map in
251+
(mutually exclusive with project_id)
252+
api_token: Optional API token
253+
254+
Returns:
255+
The duplicated map
256+
"""
257+
if project_id is not None and folder_id is not None:
258+
raise ValueError("Cannot specify both project_id and folder_id")
259+
260+
json_args = {}
261+
if title is not None:
262+
json_args["title"] = title
263+
264+
if project_id is not None:
265+
json_args["destination"] = {"project_id": project_id}
266+
elif folder_id is not None:
267+
json_args["destination"] = {"folder_id": folder_id}
268+
269+
response = make_request(
270+
url=MAP_DUPLICATE.format(map_id=map_id),
271+
method="POST",
272+
json=json_args,
273+
api_token=api_token,
274+
)
275+
return json.load(response)

notebooks/layer_groups.ipynb

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
" delete_map,\n",
2222
" list_layer_groups,\n",
2323
" get_layer_group,\n",
24+
" update_layer_group,\n",
2425
" update_layer_groups,\n",
2526
" delete_layer_group,\n",
2627
" publish_layer_group,\n",
@@ -204,14 +205,41 @@
204205
"\n",
205206
"result = update_layer_groups(map_id, updated_groups)\n",
206207
"result"
207-
]
208-
},
209-
{
210-
"cell_type": "markdown",
211-
"metadata": {},
212-
"source": [
213-
"# Update layers to assign them to groups\n"
214-
]
208+
]
209+
},
210+
{
211+
"cell_type": "markdown",
212+
"metadata": {},
213+
"source": [
214+
"# Update a single layer group\n",
215+
"\n",
216+
"You can also update individual layer groups instead of doing bulk updates"
217+
]
218+
},
219+
{
220+
"cell_type": "code",
221+
"execution_count": null,
222+
"metadata": {},
223+
"outputs": [],
224+
"source": [
225+
"# Update just the first group individually\n",
226+
"individual_update_result = update_layer_group(\n",
227+
" map_id=map_id,\n",
228+
" layer_group_id=group1_id,\n",
229+
" name=\"Vector Data (Individual Update)\",\n",
230+
" caption=\"Updated via individual update function\",\n",
231+
" ordering_key=10,\n",
232+
" visibility_interaction=\"slider\"\n",
233+
")\n",
234+
"individual_update_result"
235+
]
236+
},
237+
{
238+
"cell_type": "markdown",
239+
"metadata": {},
240+
"source": [
241+
"# Update layers to assign them to groups\n"
242+
]
215243
},
216244
{
217245
"cell_type": "code",
@@ -222,11 +250,11 @@
222250
"# Prepare updates for both layers\n",
223251
"layer_updates = [\n",
224252
" {\n",
225-
" \"id\": layer1_id\n",
253+
" \"id\": layer1_id,\n",
226254
" \"layer_group_id\": group1_id,\n",
227255
" },\n",
228256
" {\n",
229-
" \"id\": layer2_id\n",
257+
" \"id\": layer2_id,\n",
230258
" \"layer_group_id\": group2_id,\n",
231259
" }\n",
232260
"]\n",
@@ -316,4 +344,4 @@
316344
},
317345
"nbformat": 4,
318346
"nbformat_minor": 4
319-
}
347+
}

notebooks/maps.ipynb

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
" resolve_comment,\n",
2626
" delete_comment,\n",
2727
" create_embed_token,\n",
28-
" add_source_layer\n",
28+
" add_source_layer,\n",
29+
" duplicate_map\n",
2930
")\n",
3031
"\n",
3132
"os.environ[\"FELT_API_TOKEN\"] = \"<YOUR_API_TOKEN>\""
@@ -183,6 +184,33 @@
183184
"print(f\"Expires at: {embed_token['expires_at']}\")"
184185
]
185186
},
187+
{
188+
"cell_type": "markdown",
189+
"metadata": {},
190+
"source": [
191+
"# Duplicating a map\n",
192+
"\n",
193+
"You can duplicate an existing map to create a copy with optional title and destination."
194+
]
195+
},
196+
{
197+
"cell_type": "code",
198+
"execution_count": null,
199+
"metadata": {},
200+
"outputs": [],
201+
"source": [
202+
"duplicated_map = duplicate_map(\n",
203+
" map_id=map_id,\n",
204+
" title=\"Duplicated felt-python map\"\n",
205+
" # project_id=\"project_id_here\" # Optional: specify destination project\n",
206+
" # folder_id=\"folder_id_here\" # Optional: specify destination folder\n",
207+
")\n",
208+
"\n",
209+
"duplicated_map_id = duplicated_map[\"id\"]\n",
210+
"print(f\"Duplicated map created with ID: {duplicated_map_id}\")\n",
211+
"print(f\"Duplicated map URL: {duplicated_map['url']}\")"
212+
]
213+
},
186214
{
187215
"cell_type": "markdown",
188216
"metadata": {},
@@ -196,7 +224,10 @@
196224
"metadata": {},
197225
"outputs": [],
198226
"source": [
199-
"delete_map(map_id)"
227+
"delete_map(map_id)\n",
228+
"\n",
229+
"# Also delete the duplicated map\n",
230+
"delete_map(duplicated_map_id)"
200231
]
201232
}
202233
],

tests/layer_groups_test.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
create_map,
1616
list_layer_groups,
1717
get_layer_group,
18+
update_layer_group,
1819
update_layer_groups,
1920
publish_layer_group,
2021
update_layers,
@@ -191,7 +192,27 @@ def test_layer_groups_workflow(self):
191192

192193
print("Layer groups updated successfully")
193194

194-
# Step 7: Update layers to assign them to groups
195+
# Step 7: Test individual layer group update
196+
print("Testing individual layer group update...")
197+
198+
individual_update_result = update_layer_group(
199+
map_id=map_id,
200+
layer_group_id=group1_id,
201+
name="Vector Data (Individual Update)",
202+
caption="Updated via individual update function",
203+
ordering_key=10,
204+
visibility_interaction="slider"
205+
)
206+
207+
self.assertIsNotNone(individual_update_result)
208+
self.assertEqual(individual_update_result["name"], "Vector Data (Individual Update)")
209+
self.assertEqual(individual_update_result["caption"], "Updated via individual update function")
210+
self.assertEqual(individual_update_result["ordering_key"], 10)
211+
self.assertEqual(individual_update_result["visibility_interaction"], "slider")
212+
213+
print("Individual layer group update completed successfully")
214+
215+
# Step 8: Update layers to assign them to groups
195216
print("Updating layers to assign them to groups...")
196217
layer_updates = [
197218
{
@@ -221,7 +242,7 @@ def test_layer_groups_workflow(self):
221242
any(layer["id"] == layer2_id for layer in group2_details["layers"])
222243
)
223244

224-
# Step 8: Publish a layer group to the library
245+
# Step 9: Publish a layer group to the library
225246
print(f"Publishing layer group: {group1_id} to the library...")
226247
try:
227248
published_group = publish_layer_group(

tests/maps_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
resolve_comment,
2121
delete_comment,
2222
create_embed_token,
23+
duplicate_map,
2324
)
2425

2526

@@ -134,6 +135,28 @@ def test_map_workflow(self):
134135
self.assertIn("token", token_data)
135136
self.assertIn("expires_at", token_data)
136137
print(f"Created embed token that expires at {token_data['expires_at']}")
138+
139+
# Step 8: Duplicate the map
140+
print("Duplicating map...")
141+
142+
duplicated_map_name = f"Duplicated Map ({self.timestamp})"
143+
duplicated_map = duplicate_map(
144+
map_id=map_id,
145+
title=duplicated_map_name
146+
)
147+
148+
self.assertIsNotNone(duplicated_map)
149+
self.assertIn("id", duplicated_map)
150+
self.assertEqual(duplicated_map["title"], duplicated_map_name)
151+
self.assertNotEqual(duplicated_map["id"], map_id) # Should be different ID
152+
153+
duplicated_map_id = duplicated_map["id"]
154+
print(f"Duplicated map created with ID: {duplicated_map_id}")
155+
156+
# Clean up duplicated map
157+
print("Cleaning up duplicated map...")
158+
delete_map(duplicated_map_id)
159+
137160
print("\nTest completed successfully!")
138161

139162

0 commit comments

Comments
 (0)