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
6 changes: 5 additions & 1 deletion docs/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@ m.on_layer_change(lambda e: print("layers", e["layerIds"]))
| `add_colormap(colormap, vmin=, vmax=, label=, **kwargs)` | Add a colorbar from a named colormap (leafmap-style alias of `add_colorbar`). |
| `set_center(lng, lat, zoom=None)` | Center (and optionally zoom) the map. |
| `set_center_zoom(lng, lat, zoom=None)` | Alias of `set_center` (leafmap compatibility). |
| `remove_layer(layer_id)` / `clear_layers()` | Remove layers. |
| `set_zoom(zoom)` / `set_bearing(bearing)` / `set_pitch(pitch)` / `fit_project_bounds(bounds)` | Persist camera changes without requiring the widget to be displayed. |
| `center` / `zoom` / `bearing` / `pitch` / `basemap` / `name` | Read persisted project and camera state; `name` is writable. |
| `rename_layer(layer, name)` / `move_layer(layer, index)` / `duplicate_layer(layer, name=)` / `show_layer(layer)` / `hide_layer(layer)` | Manage layers by id, name, or `Layer` handle. |
| `layer_properties(layer)` / `column_values(layer, column)` / `describe()` | Inspect inlined data and summarize a project without a browser round trip. |
| `remove_layer(layer_id)` / `clear_layers()` | Remove one layer by id, name, or handle, or remove all layers. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `to_project(keep_credentials=False)` | Return the current project as a dict, credentials redacted unless `keep_credentials=True`. |
| `load_project(src)` | Replace the project from a dict, JSON string, or `.geolibre.json` path. |
| `save_project(path, keep_credentials=False)` | Write the current project to a `.geolibre.json` file, credentials redacted unless `keep_credentials=True`. |
Expand Down
8 changes: 7 additions & 1 deletion python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ m.to_project()["mapView"]["center"]
| `layer_names` / `find_layer(name)` / `set_layer_visibility` / `set_layer_opacity` | Inspect and update layers conveniently. |
| `rename_layer` / `move_layer` / `duplicate_layer` / `show_layer` / `hide_layer` | Manage layers by id, name, or `Layer` handle. |
| `layer_properties(layer)` / `column_values(layer, column)` / `describe()` | Inspect inlined data and summarize a project without a browser round trip. |
| `remove_layer(layer)` / `clear_layers()` | Remove one layer by id, name, or handle, or remove all layers. |
| `remove_layer(layer_id)` / `clear_layers()` | Remove one layer by id, name, or handle, or remove all layers. |
| `center` / `zoom` / `bearing` / `pitch` / `basemap` / `name` | Read persisted project and camera state; `name` is writable. |
| `set_zoom` / `set_bearing` / `set_pitch` / `fit_project_bounds` | Persist camera changes without requiring the widget to be displayed. |
| `to_project()` / `load_project(src)` / `save_project(path)` | Project I/O. |
Expand Down Expand Up @@ -126,6 +126,12 @@ print(describe_project(project))
save_project("copy.geolibre.json", project)
```

These are the lossless file primitives: unlike `Map.save_project`, the top-level
`save_project` writes the project **verbatim**, credentials included, so that
editing a project in place cannot strip your own API keys out of it. Pass a
project through `geolibre.project.redact_credentials` first if the file is going
anywhere untrusted, or use `Map.save_project`, which redacts by default.

## Notes

- The bundled app is served from a localhost HTTP server, so the interactive
Expand Down
12 changes: 11 additions & 1 deletion python/src/geolibre/authoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,19 @@ def save_project(path: str | Path, project: dict[str, Any]) -> Path:
can approach ``MAX_PROJECT_BYTES``, and the MCP server rewrites the whole
file on every edit, so a truncating write is a real way to lose work.

Note:
This writes *verbatim*, credentials included. It is the lossless
primitive the MCP server round-trips a user's own project file through,
where stripping an API key on every small edit would quietly destroy the
file's usefulness. :meth:`geolibre.Map.save_project` is the counterpart
for producing a file to share: it redacts unless
``keep_credentials=True``. Run a project through
:func:`geolibre.project.redact_credentials` before calling this if the
result is going anywhere untrusted.

Args:
path: Destination path.
project: The project dict to serialize.
project: The project dict to serialize, written as given.

Returns:
The resolved path written to.
Expand Down
4 changes: 3 additions & 1 deletion python/src/geolibre/geolibre.py
Original file line number Diff line number Diff line change
Expand Up @@ -2525,7 +2525,9 @@ def source(self) -> Any:
into an output that often gets committed or shared. Read
:attr:`Map.project` for the record exactly as stored.
"""
return _project.redact_layer(self._layer()).get("source")
# Sweep the one field rather than the whole layer: `redact_layer` would
# copy an inlined geojson blob first, only to discard it here.
return _project.redact_layer_field(self._layer().get("source"))

@property
def data(self) -> dict[str, Any]:
Expand Down
9 changes: 9 additions & 0 deletions python/src/geolibre/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ def _sweep_layer_credentials(layer: dict[str, Any]) -> None:
layer[field] = _redact_config(layer[field])


def redact_layer_field(value: Any) -> Any:
"""Return one of a layer's config fields, detached and swept.

The single-field counterpart to :func:`redact_layer`, for a read that wants
only ``source`` and should not pay to copy an inlined GeoJSON blob first.
"""
return _redact_config(value)


def redact_layer(layer: dict[str, Any]) -> dict[str, Any]:
"""Return a detached copy of one layer, safe to display or hand to others.

Expand Down
Loading