Repository: stac-utils/stac-geoparquet

Issue:
stac-geoparquet 0.6 deltalake dependency requires python<=3.12
I think this will work itself out soon, but I was very confused about why I had an older version of stac-geoparquet in a new environment.  Specifically, a new python 3.13 environment will install version 0.4.1 instead of 0.6 `mamba create -n test python=3.13 stac-geoparquet --dry-run` .

Looks like this is due to the introduction of the `deltalake` dependency:
```
Dependencies:
 - pyarrow >=16
 - pyarrow-hotfix
 - python >=3.12,<3.13.0a0
 - python >=3.12,<3.13.0a0 *_cpython
 - python_abi 3.12.* *_cp312
```

Discussion:
@TomAugspurger: We could make `deltalake` an optional dependency and have `stac_geoparquet.arrow._delta_lake` guard against that ImportError. 

Solution patch:
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 5ff6c81..8085e41 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -17,6 +17,7 @@ jobs:
           - "3.10"
           - "3.11"
           - "3.12"
+          - "3.13"
     steps:
       - uses: actions/checkout@v2
       - uses: astral-sh/setup-uv@v5
diff --git a/docs/whatsnew.md b/docs/whatsnew.md
index 6ea797b..7bbc704 100644
--- a/docs/whatsnew.md
+++ b/docs/whatsnew.md
@@ -2,6 +2,10 @@
 
 This is a list of changes to `stac-geoparquet`.
 
+## 0.8.0
+
+- Make `deltalake` an optional dependency (https://github.com/stac-utils/stac-geoparquet/pull/106)
+
 ## 0.7.0
 
 - Updated stac-geoparquet Parquet File metadata fields (https://github.com/stac-utils/stac-geoparquet/pull/98)
diff --git a/pyproject.toml b/pyproject.toml
index 1c70f75..7e2dc79 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,7 +12,6 @@ dynamic = ["version", "description"]
 requires-python = ">=3.9"
 dependencies = [
     "ciso8601",
-    "deltalake",
     "geopandas",
     "packaging",
     "pandas",
@@ -33,6 +32,10 @@ source = "vcs"
 version-file = "stac_geoparquet/_version.py"
 
 [project.optional-dependencies]
+deltalake = [
+    "deltalake",
+]
+
 pgstac = [
     "fsspec",
     "psycopg[binary,pool]",
@@ -40,7 +43,14 @@ pgstac = [
     "python-dateutil",
     "tqdm",
 ]
-pc = ["adlfs", "azure-data-tables", "psycopg[binary,pool]", "pypgstac", "tqdm"]
+
+pc = [
+    "adlfs",
+    "azure-data-tables",
+    "psycopg[binary,pool]",
+    "pypgstac",
+    "tqdm"
+]
 
 [dependency-groups]
 dev = [
@@ -53,6 +63,7 @@ dev = [
     "pytest",
     "requests",
     "ruff",
+    "stac-geoparquet[deltalake]",
     "stac-geoparquet[pc]",
     "stac-geoparquet[pgstac]",
     "types-python-dateutil",
diff --git a/stac_geoparquet/arrow/_delta_lake.py b/stac_geoparquet/arrow/_delta_lake.py
index 91caad2..446ab0b 100644
--- a/stac_geoparquet/arrow/_delta_lake.py
+++ b/stac_geoparquet/arrow/_delta_lake.py
@@ -5,7 +5,13 @@ from pathlib import Path
 from typing import TYPE_CHECKING, Any
 
 import pyarrow as pa
-from deltalake import write_deltalake
+
+try:
+    from deltalake import write_deltalake
+
+    _deltalake_installed = True
+except ImportError:
+    _deltalake_installed = False
 
 from stac_geoparquet.arrow._api import parse_stac_ndjson_to_arrow
 from stac_geoparquet.arrow._constants import DEFAULT_JSON_CHUNK_SIZE
@@ -47,6 +53,11 @@ def parse_stac_ndjson_to_delta_lake(
         schema_version: GeoParquet specification version; if not provided will default
             to latest supported version.
     """
+    if not _deltalake_installed:
+        raise ImportError(
+            "The `deltalake` package is required to use parse_stac_ndjson_to_delta_lake"
+            "Enable with `pip install stac_geoparquet[deltalake]`."
+        )
     record_batch_reader = parse_stac_ndjson_to_arrow(
         input_path, chunk_size=chunk_size, schema=schema, limit=limit
     )


Test patch:
diff --git a/tests/test_delta_lake.py b/tests/test_delta_lake.py
index 4bf0124..843e28f 100644
--- a/tests/test_delta_lake.py
+++ b/tests/test_delta_lake.py
@@ -4,8 +4,7 @@ from pathlib import Path
 import pytest
 from deltalake import DeltaTable
 
-from stac_geoparquet.arrow import stac_table_to_items
-from stac_geoparquet.arrow._delta_lake import parse_stac_ndjson_to_delta_lake
+import stac_geoparquet.arrow
 
 from .json_equals import assert_json_value_equal
 
@@ -27,16 +26,25 @@ TEST_COLLECTIONS = [
 ]
 
 
+def test_no_deltalake_raises():
+    # Dev environment has deltalake, so pretend we don't have it
+    stac_geoparquet.arrow._delta_lake._deltalake_installed = False
+    with pytest.raises(ImportError, match="The `deltalake` package is required"):
+        stac_geoparquet.arrow.parse_stac_ndjson_to_delta_lake("foo", "bar")
+    # Reset for subsequent tests
+    stac_geoparquet.arrow._delta_lake._deltalake_installed = True
+
+
 @pytest.mark.parametrize("collection_id", TEST_COLLECTIONS)
 def test_round_trip_via_delta_lake(collection_id: str, tmp_path: Path):
     path = HERE / "data" / f"{collection_id}-pc.json"
     out_path = tmp_path / collection_id
-    parse_stac_ndjson_to_delta_lake(path, out_path)
+    stac_geoparquet.arrow.parse_stac_ndjson_to_delta_lake(path, out_path)
 
     # Read back into table and convert to json
     dt = DeltaTable(out_path)
     table = dt.to_pyarrow_table()
-    items_result = list(stac_table_to_items(table))
+    items_result = list(stac_geoparquet.arrow.stac_table_to_items(table))
 
     # Compare with original json
     with open(HERE / "data" / f"{collection_id}-pc.json") as f: