Skip to content

refactor: streamline geojson handling by centralizing file reading logic#1336

Merged
giswqs merged 13 commits into
masterfrom
geojson
Jul 4, 2026
Merged

refactor: streamline geojson handling by centralizing file reading logic#1336
giswqs merged 13 commits into
masterfrom
geojson

Conversation

@giswqs

@giswqs giswqs commented Jul 4, 2026

Copy link
Copy Markdown
Member

Updated the handling of GeoJSON inputs across multiple files to utilize the new geojson_to_gdf function. This change improves code consistency and simplifies the process of reading GeoJSON data from both local files and URLs. Adjustments made in common.py, foliumap.py, leafmap.py, and maplibregl.py to replace direct calls to gpd.read_file with the new function.

Summary by CodeRabbit

  • Bug Fixes
    • Improved GeoJSON loading from HTTP/HTTPS URLs across map layers, vector editing/data workflows, and date filter sources.
    • URL handling is more robust, including better parsing for both single-feature and multi-feature GeoJSON responses.
    • Local file and non-URL inputs continue to behave as before, including the JupyterLite path.
  • Chores
    • Updated CI workflows and test matrices to include Python 3.13.
  • Dependencies
    • Added pyproj>=3.7 for Python 3.13+.

Updated the handling of GeoJSON inputs across multiple files to utilize the new `geojson_to_gdf` function. This change improves code consistency and simplifies the process of reading GeoJSON data from both local files and URLs. Adjustments made in `common.py`, `foliumap.py`, `leafmap.py`, and `maplibregl.py` to replace direct calls to `gpd.read_file` with the new function.
Copilot AI review requested due to automatic review settings July 4, 2026 18:46
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

GeoJSON loading now routes HTTP and file inputs through a shared helper across multiple map entry points. Workflow matrices and one dependency entry also move to Python 3.13.

Changes

HTTP GeoJSON loading via geojson_to_gdf

Layer / File(s) Summary
geojson_to_gdf HTTP branch
leafmap/common.py
Adds an HTTP URL branch that tries gpd.read_file first, then fetches JSON with requests and builds a GeoDataFrame from features, handling Feature and FeatureCollection inputs.
add_geojson updates
leafmap/foliumap.py, leafmap/leafmap.py
Map.add_geojson now calls geojson_to_gdf(...) instead of gpd.read_file(...) for non-JupyterLite HTTP and local string inputs.
maplibregl loading entry points
leafmap/maplibregl.py
HTTP string sources in add_geojson, add_vector, add_symbol, add_vector_editor, create_vector_data, edit_vector_data, and DateFilterWidget now route through geojson_to_gdf, while non-HTTP paths keep using gpd.read_file.

Python 3.13 support

Layer / File(s) Summary
Workflow matrix updates
.github/workflows/docs.yml, .github/workflows/installation.yml, .github/workflows/macos.yml, .github/workflows/ubuntu.yml, .github/workflows/windows.yml
The workflow matrices switch from Python 3.12 to 3.13, and the Ubuntu matrix changes from 3.9/3.10/3.11/3.12 to 3.11/3.12/3.13.
Conditional pyproj dependency
requirements.txt
pyproj>=3.7 is added for python_version >= "3.13".

Estimated code review effort: 3 (Moderate) | ~25 minutes

Poem

A rabbit hops through GeoJSON streams,
and HTTP paths now share the same dreams.
When read_file stumbles, requests takes a peek,
and features hop home without a squeak.
CI bounces onward to 3.13 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactor to centralize GeoJSON file reading logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch geojson

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Jul 4, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors GeoJSON ingestion across the library by routing string GeoJSON inputs (local paths and URLs) through the shared common.geojson_to_gdf() helper, reducing duplicated geopandas.read_file() usage and standardizing conversion to GeoJSON interfaces.

Changes:

  • Added HTTP URL handling to common.geojson_to_gdf() and updated callers to use it.
  • Replaced direct gpd.read_file(...).__geo_interface__ calls with geojson_to_gdf(...).__geo_interface__ in MapLibre and Leafmap/Folium map entry points.
  • Extended some MapLibre vector-editing flows to load GeoJSON from HTTP URLs via the shared helper.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
leafmap/common.py Extends geojson_to_gdf to handle GeoJSON URLs via HTTP fetch and from_features.
leafmap/maplibregl.py Routes multiple GeoJSON/vector entry points through geojson_to_gdf for URL/file handling.
leafmap/leafmap.py Uses geojson_to_gdf for reading GeoJSON inputs instead of direct gpd.read_file.
leafmap/foliumap.py Uses geojson_to_gdf for reading GeoJSON inputs instead of direct gpd.read_file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread leafmap/common.py Outdated
giswqs and others added 2 commits July 4, 2026 14:50
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Updated the Python version in multiple GitHub Actions workflows to 3.13, ensuring compatibility with the latest features and improvements. Changes made in docs.yml, installation.yml, macos.yml, ubuntu.yml, and windows.yml.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
leafmap/maplibregl.py (3)

4339-4348: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same redundant HTTP/local branching as add_vector.

geojson_to_gdf already falls back to gpd.read_file internally for non-HTTP strings, so this if/else can be collapsed to a single geojson_to_gdf(source).__geo_interface__ call, consistent with the DRY suggestion on add_vector above.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/maplibregl.py` around lines 4339 - 4348, The source-loading branch in
the string-handling path is redundant and should be simplified to match the DRY
approach used in add_vector. In the logic around source_name and geojson
handling, remove the explicit HTTP vs local file if/else and call
geojson_to_gdf(source).__geo_interface__ directly, since geojson_to_gdf already
handles non-HTTP inputs internally.

9972-9980: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same simplification applies to edit_vector_data.

Same duplication as add_vector_editor: the http/else pair is redundant now that geojson_to_gdf internally falls back to gpd.read_file for non-HTTP paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/maplibregl.py` around lines 9972 - 9980, The same redundant http/else
branching appears in edit_vector_data as in add_vector_editor: the URL check is
unnecessary because geojson_to_gdf already falls back to gpd.read_file for local
paths. Update edit_vector_data to match the simplified add_vector_editor flow by
keeping the parquet handling and then calling geojson_to_gdf for the remaining
string filenames, using the edit_vector_data and geojson_to_gdf symbols to
locate the change.

7834-7842: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

HTTP branch duplicates geojson_to_gdf's internal fallback.

The elif filename.startswith("http") / else: gpd.read_file(filename) pair can be merged now that geojson_to_gdf handles both cases; only the parquet special-case needs to remain separate.

♻️ Proposed simplification
             if ext in [".parquet", ".pq", ".geoparquet"]:
                 gdf = gpd.read_parquet(filename)
-            elif filename.startswith("http"):
-                gdf = geojson_to_gdf(filename)
             else:
-                gdf = gpd.read_file(filename)
+                gdf = geojson_to_gdf(filename)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/maplibregl.py` around lines 7834 - 7842, The filename handling in
maplibregl.py duplicates the HTTP fallback logic inside geojson_to_gdf, so
simplify the branch in the file-loading block by keeping only the parquet
extension special-case and routing all non-parquet inputs through
geojson_to_gdf. Update the conditional around gpd.read_parquet and
geojson_to_gdf so the helper function becomes the single path for HTTP and local
non-parquet sources, using the existing filename check in the same load logic.
leafmap/common.py (1)

3946-3958: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Strip encoding before the from_features fallback.

If gpd.read_file() fails here, this branch still passes encoding (and any other reader-only kwargs) into GeoDataFrame.from_features, which only accepts crs and columns and will raise TypeError. Filter those kwargs before calling it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/common.py` around lines 3946 - 3958, The fallback in the GeoJSON
handling path passes reader-only kwargs like encoding into
GeoDataFrame.from_features, which only supports a limited set of arguments. In
the branch inside the GeoJSON loader function where gpd.read_file falls back to
from_features, strip out encoding and any other unsupported kwargs before
calling gpd.GeoDataFrame.from_features, while preserving only the valid options
such as crs and columns.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@leafmap/common.py`:
- Around line 3953-3954: The HTTP fetch in the in_geojson branch uses
requests.get without any timeout or response validation, which can hang or fail
silently. Update the in_geojson handling path to use the existing request flow
in this function with a timeout, check the response status before parsing, and
wrap the fetch/JSON decode in error handling so failures are surfaced clearly.
Keep the change localized to the branch that handles string URLs starting with
http and preserve the behavior for non-HTTP inputs.

In `@leafmap/maplibregl.py`:
- Around line 11113-11120: The `DateFilterWidget.__init__` source-loading branch
duplicates the same URL/file check already handled inside `geojson_to_gdf`.
Simplify the logic where `gdfs.append(gdf)` is built by removing the
`isinstance(source, str) and source.startswith("http")` conditional and calling
`geojson_to_gdf(source)` directly for non-None sources, or extract a small
shared helper used by all repeated sites in this module.
- Around line 1680-1684: The HTTP/file branching here is duplicated because
geojson_to_gdf already decides whether to fetch from a URL or read locally.
Simplify this call site in the data-loading path by replacing the manual
isinstance(data, str) and startswith("http") check with a single geojson_to_gdf
call, then use its __geo_interface__ result. Apply the same cleanup in
add_symbol and DateFilterWidget.__init__ so the logic lives only in
geojson_to_gdf.

---

Outside diff comments:
In `@leafmap/common.py`:
- Around line 3946-3958: The fallback in the GeoJSON handling path passes
reader-only kwargs like encoding into GeoDataFrame.from_features, which only
supports a limited set of arguments. In the branch inside the GeoJSON loader
function where gpd.read_file falls back to from_features, strip out encoding and
any other unsupported kwargs before calling gpd.GeoDataFrame.from_features,
while preserving only the valid options such as crs and columns.

In `@leafmap/maplibregl.py`:
- Around line 4339-4348: The source-loading branch in the string-handling path
is redundant and should be simplified to match the DRY approach used in
add_vector. In the logic around source_name and geojson handling, remove the
explicit HTTP vs local file if/else and call
geojson_to_gdf(source).__geo_interface__ directly, since geojson_to_gdf already
handles non-HTTP inputs internally.
- Around line 9972-9980: The same redundant http/else branching appears in
edit_vector_data as in add_vector_editor: the URL check is unnecessary because
geojson_to_gdf already falls back to gpd.read_file for local paths. Update
edit_vector_data to match the simplified add_vector_editor flow by keeping the
parquet handling and then calling geojson_to_gdf for the remaining string
filenames, using the edit_vector_data and geojson_to_gdf symbols to locate the
change.
- Around line 7834-7842: The filename handling in maplibregl.py duplicates the
HTTP fallback logic inside geojson_to_gdf, so simplify the branch in the
file-loading block by keeping only the parquet extension special-case and
routing all non-parquet inputs through geojson_to_gdf. Update the conditional
around gpd.read_parquet and geojson_to_gdf so the helper function becomes the
single path for HTTP and local non-parquet sources, using the existing filename
check in the same load logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2d3b48b6-c106-4c3b-b0a2-621b5d1287b8

📥 Commits

Reviewing files that changed from the base of the PR and between beeabc4 and acdd5c7.

📒 Files selected for processing (4)
  • leafmap/common.py
  • leafmap/foliumap.py
  • leafmap/leafmap.py
  • leafmap/maplibregl.py

Comment thread leafmap/common.py Outdated
Comment thread leafmap/maplibregl.py Outdated
Comment thread leafmap/maplibregl.py
- Remove redundant HTTP/file branching at three call sites in maplibregl.py
  (add_vector/add_geojson path, add_symbol, DateFilterWidget.__init__) since
  geojson_to_gdf() now internally branches on http URLs vs local files

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@leafmap/common.py`:
- Around line 3953-3962: The HTTP fallback in the GeoJSON loading branch is
forwarding all of `**kwargs` into `gpd.GeoDataFrame.from_features()`, but that
method only supports a limited subset and can raise a new `TypeError`. Update
the `in_geojson` HTTP fallback inside `common.py` to pass only supported
arguments (for example, `crs` and `columns`) to `GeoDataFrame.from_features()`,
and keep the original `gpd.read_file()` call unchanged.
- Line 3956: The fallback handling around the initial gpd.read_file attempt is
swallowing the original fetch/parse failure with a blind except Exception.
Update this block to capture the exception as a variable and preserve it when
entering the fallback path so the original error is not lost if the fallback
also fails; keep the change localized to the read_file/fallback logic in
common.py and ensure the eventual failure message includes the first error
details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7ce27036-0872-426e-8e70-b01a059df4ae

📥 Commits

Reviewing files that changed from the base of the PR and between acdd5c7 and dfaf34f.

📒 Files selected for processing (6)
  • .github/workflows/docs.yml
  • .github/workflows/installation.yml
  • .github/workflows/macos.yml
  • .github/workflows/ubuntu.yml
  • .github/workflows/windows.yml
  • leafmap/common.py

Comment thread leafmap/common.py
Comment on lines +3953 to +3962
elif isinstance(in_geojson, str) and in_geojson.startswith("http"):
try:
return gpd.read_file(in_geojson, encoding=encoding, **kwargs)
except Exception:
response = requests.get(in_geojson, timeout=30)
response.raise_for_status()
data = response.json()
if isinstance(data, dict) and data.get("type") == "Feature":
return gpd.GeoDataFrame.from_features([data], **kwargs)
return gpd.GeoDataFrame.from_features(data, **kwargs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does geopandas GeoDataFrame.from_features accept arbitrary keyword arguments beyond crs and columns?

💡 Result:

No, geopandas.GeoDataFrame.from_features does not accept arbitrary keyword arguments [1][2]. The method signature is explicitly defined as classmethod GeoDataFrame.from_features(features, crs=None, columns=None), and it only supports the parameters 'features', 'crs', and 'columns' [1][2][3]. Passing additional keyword arguments will result in a TypeError.

Citations:


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
path = Path("leafmap/common.py")
lines = path.read_text().splitlines()
start, end = 3935, 3970
for i in range(start, min(end, len(lines)) + 1):
    print(f"{i:5d}: {lines[i-1]}")
PY

Repository: opengeos/leafmap

Length of output: 1778


Drop unsupported kwargs in the HTTP fallback. gpd.GeoDataFrame.from_features() only accepts crs and columns, so forwarding the full **kwargs from gpd.read_file() here can turn the fallback into a new TypeError and mask the original error. Pass only the supported kwargs in this branch.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 3956-3956: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.get(in_geojson, timeout=30)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

🪛 Ruff (0.15.20)

[warning] 3956-3956: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/common.py` around lines 3953 - 3962, The HTTP fallback in the GeoJSON
loading branch is forwarding all of `**kwargs` into
`gpd.GeoDataFrame.from_features()`, but that method only supports a limited
subset and can raise a new `TypeError`. Update the `in_geojson` HTTP fallback
inside `common.py` to pass only supported arguments (for example, `crs` and
`columns`) to `GeoDataFrame.from_features()`, and keep the original
`gpd.read_file()` call unchanged.

Comment thread leafmap/common.py
elif isinstance(in_geojson, str) and in_geojson.startswith("http"):
try:
return gpd.read_file(in_geojson, encoding=encoding, **kwargs)
except Exception:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Blind except Exception swallows the original fetch/parse error.

except Exception: (flagged by ruff BLE001) silently discards the failure reason from the initial gpd.read_file attempt, making it hard to diagnose why the fallback path was triggered when the fallback itself later fails.

♻️ Suggested improvement
-        except Exception:
+        except Exception as e:
+            logger.debug(f"gpd.read_file failed for {in_geojson}, falling back to requests: {e}")
             response = requests.get(in_geojson, timeout=30)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception:
except Exception as e:
logger.debug(f"gpd.read_file failed for {in_geojson}, falling back to requests: {e}")
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 3956-3956: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.get(in_geojson, timeout=30)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

🪛 Ruff (0.15.20)

[warning] 3956-3956: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/common.py` at line 3956, The fallback handling around the initial
gpd.read_file attempt is swallowing the original fetch/parse failure with a
blind except Exception. Update this block to capture the exception as a variable
and preserve it when entering the fallback path so the original error is not
lost if the fallback also fails; keep the change localized to the
read_file/fallback logic in common.py and ensure the eventual failure message
includes the first error details.

Source: Linters/SAST tools

giswqs added 9 commits July 4, 2026 15:00
- uv was resolving the transitive pyproj (via geopandas) to 3.6.1, which
  has no cp313 wheel and fails to build from source (PROJ not found),
  breaking the 3.13 installation workflows on Linux/macOS/Windows
- pyproj 3.7+ ships prebuilt cp313 wheels
- Unconditional pyproj>=3.7 conflicts with requires-python>=3.9 since
  pyproj 3.7+ requires Python>=3.10, making resolution unsatisfiable for
  3.9-3.12
- Add an environment marker so the pin only applies on Python 3.13, where
  the missing cp313 wheel for pyproj 3.6.1 is the actual problem
…v run

The pyproj>=3.7 pin (reverted here) could not work: leafmap declares
requires-python>=3.9, and `uv run` resolves the whole version range at once
(universal resolution), so pyproj is forced to 3.6.1 (the only version that
supports 3.9) — which has no cp313 wheel and fails to build from source.

Instead, `uv pip install .` already resolves per-interpreter and correctly
installs pyproj 3.7.x (with a cp313 wheel) on Python 3.13. The failure was
only the subsequent `uv run` step re-resolving universally. Run the installed
.venv's python/pytest directly so no re-resolution happens.

- Remove pyproj pin from requirements.txt
- installation/macos/windows/ubuntu workflows: replace `uv run` in the
  Test import and pytest steps with the .venv interpreter
Reverting ubuntu.yml to `uv run`: switching to the per-interpreter .venv
pulls in numpy>=2.1, which breaks map JSON serialization in the test suite
(29 tests fail with "ndarray is not JSON serializable"). The install-only
workflows (installation/macos/windows) keep the .venv fix since they only
do an import check and are unaffected.
…o CI

Root-cause fixes for the CI failures and Python 3.14 support:

- sanitize_geojson (common.py): recursively convert NumPy arrays/scalars in
  GeoJSON to native Python types. Newer geopandas/numpy read list-valued
  feature properties (e.g. a "coordinates" property) back as ndarrays, which
  are not JSON serializable and broke ipyleaflet/folium widget serialization
  ("Object of type ndarray is not JSON serializable") across ~30 tests.
  Applied in Map.add_geojson for both leafmap.py and foliumap.py.

- fit_bounds (leafmap.py): override ipyleaflet's fit_bounds to ensure an
  asyncio event loop exists first. Python 3.14 no longer provides an implicit
  current loop outside a running loop, so ipyleaflet's
  asyncio.ensure_future(self._fit_bounds(...)) raised RuntimeError.

- Make fiona optional (leafmap.py, foliumap.py, common.py): fiona has no
  Python 3.14 wheel. It was only used to register the KML driver; the default
  pyogrio engine reads KML natively, so import it lazily and fall back.

- requirements_dev.txt: fiona only on Python < 3.14 (no cp314 wheel yet).

- CI: add Python 3.14 to the ubuntu/macOS/windows/installation matrices.
  ubuntu.yml now runs the installed .venv interpreter for the import check and
  pytest instead of `uv run`, which re-resolves universally under
  requires-python>=3.9 and pins pyproj to 3.6.1 (no cp313/cp314 wheel).

Verified locally: full test suite (80 tests) passes on Python 3.14.
The pinned uv 0.4.16 predates Python 3.14 and has no download for it
("No download found for request: cpython-3.14-..."). Bump the setup-uv
version to "latest" in the workflows that test 3.14.
docs.yml already uses 3.13; align docs-build.yml (was 3.12).
Bumping docs-build to 3.13 exposed the same universal-resolution trap: `uv run`
re-resolves under requires-python>=3.9 and pins pyproj to 3.6.1, which has no
cp313 wheel and fails to build (proj not found). Use the installed .venv
interpreter for the import, pytest, and mkdocs steps in both docs.yml and
docs-build.yml.
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

@github-actions github-actions Bot temporarily deployed to pull request July 4, 2026 20:09 Inactive
@giswqs giswqs merged commit c25cb04 into master Jul 4, 2026
21 of 22 checks passed
@giswqs giswqs deleted the geojson branch July 4, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants