[Git][debian-gis-team/stac-pydantic][master] 8 commits: New upstream version 3.5.0

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sun Feb 8 17:27:57 GMT 2026



Antonio Valentino pushed to branch master at Debian GIS Project / stac-pydantic


Commits:
eeab05bb by Antonio Valentino at 2026-02-08T17:09:29+00:00
New upstream version 3.5.0
- - - - -
20b543b2 by Antonio Valentino at 2026-02-08T17:09:30+00:00
Update upstream source from tag 'upstream/3.5.0'

Update to upstream version '3.5.0'
with Debian dir f5c0a3a752b8352e272a7937195a1b158d0abe1b
- - - - -
762cdf41 by Antonio Valentino at 2026-02-08T17:10:00+00:00
New upstream release

- - - - -
aa4dd068 by Antonio Valentino at 2026-02-08T17:13:00+00:00
Refresh all patches

- - - - -
4b36a48f by Antonio Valentino at 2026-02-08T17:14:54+00:00
Refresh patches

- - - - -
cedb7b8e by Antonio Valentino at 2026-02-08T17:15:40+00:00
Reorder Files paragraphs in debian/copyright by directory depth.

Changes-By: lintian-brush
Fixes: lintian: globbing-patterns-out-of-order
See-also: https://lintian.debian.org/tags/globbing-patterns-out-of-order.html
Fixes: lintian: globbing-patterns-out-of-order
See-also: https://lintian.debian.org/tags/globbing-patterns-out-of-order.html

- - - - -
d29fcb8f by Antonio Valentino at 2026-02-08T17:22:45+00:00
Add build-dependency on pythom3-hatchling

- - - - -
d7fb978c by Antonio Valentino at 2026-02-08T17:25:11+00:00
Update dates in d/copyright

- - - - -


22 changed files:

- .github/workflows/cicd.yml
- .github/workflows/release.yml
- .pre-commit-config.yaml
- CHANGELOG.md
- CONTRIBUTING.md
- README.md
- debian/changelog
- debian/control
- debian/copyright
- − debian/patches/0001-Network-mark.patch
- debian/patches/0002-No-coverage.patch → debian/patches/0001-No-coverage.patch
- debian/patches/series
- pyproject.toml
- stac_pydantic/item.py
- stac_pydantic/shared.py
- stac_pydantic/version.py
- tests/api/test_item_collection.py
- tests/api/test_landing_page.py
- tests/test_cli.py
- tests/test_models.py
- − tox.ini
- + uv.lock


Changes:

=====================================
.github/workflows/cicd.yml
=====================================
@@ -9,38 +9,48 @@ on:
     - '*'
   pull_request:
 env:
-  LATEST_PY_VERSION: '3.13'
+  LATEST_PY_VERSION: '3.14'
 
 jobs:
   tests:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+        python-version:
+          - '3.8'
+          - '3.9'
+          - '3.10'
+          - '3.11'
+          - '3.12'
+          - '3.13'
+          - '3.14'
 
     steps:
-      - uses: actions/checkout at v4
+      - uses: actions/checkout at v5
 
-      - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python at v5
+      - name: Install uv
+        uses: astral-sh/setup-uv at v7
         with:
+          version: "0.9.*"
+          enable-cache: true
           python-version: ${{ matrix.python-version }}
 
       - name: Install dependencies
+        run: uv sync
+
+      - name: Run pre-commit
+        if: ${{ matrix.python-version == env.LATEST_PY_VERSION }}
         run: |
-          python -m pip install --upgrade pip
-          python -m pip install tox pre-commit
-          pre-commit install
+          uv run pre-commit run --all-files
 
-      # Run tox using the version of Python in `PATH`
-      - name: Run Tox
-        run: tox -e py
+      - name: Run tests
+        run: uv run pytest --cov stac_pydantic --cov-report term-missing --cov-report xml
 
       - name: Upload Results
         if: ${{ matrix.python-version == env.LATEST_PY_VERSION }}
-        uses: codecov/codecov-action at v4
+        uses: codecov/codecov-action at v5
         with:
-          file: ./coverage.xml
+          files: ./coverage.xml
           flags: unittests
-          name: ${{ matrix.python-version }}
           fail_ci_if_error: false
+          token: ${{ secrets.CODECOV_TOKEN }}


=====================================
.github/workflows/release.yml
=====================================
@@ -12,22 +12,35 @@ jobs:
     runs-on: ubuntu-latest
     if: ${{ github.repository }} == 'stac-utils/stac-pydantic'
     steps:
-      - uses: actions/checkout at v4
+      - uses: actions/checkout at v5
 
-      - name: Set up Python 3.x
-        uses: actions/setup-python at v5
+      - name: Install uv
+        uses: astral-sh/setup-uv at v7
         with:
-          python-version: "3.x"
+          version: "0.9.*"
+          enable-cache: true
+          python-version: '3.14'
 
-      - name: Install release dependencies
+      - name: Install dependencies
         run: |
-          python -m pip install --upgrade pip
-          python -m pip install build twine
+          uv sync --group deploy
 
-      - name: Build and publish package
+      - name: Set tag version
+        id: tag
+        run: |
+          echo "version=${GITHUB_REF#refs/*/}"
+          echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
+
+      - name: Set module version
+        id: module
+        run: |
+          echo "version=$(uv run hatch --quiet version)" >> $GITHUB_OUTPUT
+
+      - name: Build and publish
+        if: ${{ steps.tag.outputs.version }} == ${{ steps.module.outputs.version}}
         env:
-          TWINE_USERNAME: ${{ secrets.PYPI_STACUTILS_USERNAME }}
-          TWINE_PASSWORD: ${{ secrets.PYPI_STACUTILS_PASSWORD }}
+          HATCH_INDEX_USER: ${{ secrets.PYPI_STACUTILS_USERNAME }}
+          HATCH_INDEX_AUTH: ${{ secrets.PYPI_STACUTILS_PASSWORD }}
         run: |
-          python -m build
-          twine upload dist/*
+          uv run hatch build
+          uv run hatch publish


=====================================
.pre-commit-config.yaml
=====================================
@@ -1,8 +1,6 @@
-# See https://pre-commit.com for more information
-# See https://pre-commit.com/hooks.html for more hooks
 repos:
   - repo: https://github.com/abravalheri/validate-pyproject
-    rev: v0.16
+    rev: v0.24
     hooks:
       - id: validate-pyproject
 
@@ -29,11 +27,12 @@ repos:
       - id: ruff-format
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.15.0
+    rev: v1.11.2
     hooks:
       - id: mypy
         language_version: python
-        # No reason to run if only tests have changed. They intentionally break typing.
-        exclude: tests/.*
         additional_dependencies:
+        - types-attrs
         - types-requests
+        - types-PyYAML
+        - pydantic~=2.0


=====================================
CHANGELOG.md
=====================================
@@ -1,6 +1,57 @@
 
 ## Unreleased
 
+## 3.5.0 (2026-01-29)
+
+- add python 3.14 support
+- use `uv` for project managment
+- fix: make sure to return `properties.datetime: null` and `geometry: null` when serializing the Item model
+
+    ```python
+    from stac_pydantic.api import Item
+
+    stac_item = Item.model_validate(
+        {
+            "id": "12345",
+            "type": "Feature",
+            "stac_extensions": [],
+            "geometry": None,
+            "properties": {
+                "datetime": None,
+                "start_datetime": "2024-01-01T00:00:00Z",
+                "end_datetime": "2024-01-02T00:00:00Z",
+            },
+            "collection": "collection",
+            "links": [
+                {
+                    "rel": "self",
+                    "href": "http://stac.example.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json"
+                },
+                {
+                    "rel": "collection",
+                    "href": "http://stac.example.com/catalog/CS3-20160503_132130_04/catalog.json"
+                },
+                {
+                    "rel": "root",
+                    "href": "http://stac.example.com/catalog"
+                }
+            ],
+            "assets": {},
+        }
+    )
+
+    out = stac_item.model_dump(exclude_none=True)
+    # `geometry` is required
+    assert out["geometry"] is None
+    # `datetime` is a required property
+    assert out["properties"]["datetime"] is None
+
+    # force exclusion of required keys
+    out = stac_item.model_dump(exclude_none=True, exclude={"properties": {"datetime"}, "geometry": True})
+    assert "geometry" not in out
+    assert "datetime" not in out["properties"]
+    ```
+
 ## 3.4.0 (2025-07-17)
 
 - Remove 'label:assets' (extension) from Link attribute (#184, @fmigneault)


=====================================
CONTRIBUTING.md
=====================================
@@ -2,25 +2,33 @@
 
 Issues and pull requests are more than welcome.
 
-**dev install**
+We recommand using [`uv`](https://docs.astral.sh/uv) as project manager for development.
+
+See https://docs.astral.sh/uv/getting-started/installation/ for installation
 
 ```bash
 git clone https://github.com/stac-utils/stac-pydantic.git
 cd stac-pydantic
-python -m pip install -e ".[dev]"
+uv sync
 ```
 
 You can then run the tests with the following command:
 
 ```sh
-python -m pytest --cov stac_pydantic --cov-report term-missing
+uv run pytest --cov stac_pydantic --cov-report term-missing
 ```
 
+To run only tests that do not require access to the internet,
+the following command can be used:
+
+```sh
+uv run pytest -m "not network"
+```
 
 **pre-commit**
 
 This repo is set to use `pre-commit` to run *ruff*, *pydocstring* and mypy when committing new code.
 
 ```bash
-pre-commit install
+uv run pre-commit install
 ```


=====================================
README.md
=====================================
@@ -18,12 +18,6 @@ python -m pip install stac-pydantic
 python -m pip install stac-pydantic["validation"]
 ```
 
-For local development:
-
-```shell
-python -m pip install -e '.[dev,lint]'
-```
-
 | stac-pydantic | STAC Version | STAC API Version | Pydantic Version |
 |--------------|---------------|------------------|-----------------|
 | 1.2.x         | 1.0.0-beta.1 | <1* | ^1.6 |
@@ -34,38 +28,6 @@ python -m pip install -e '.[dev,lint]'
 
 \* various beta releases, specs not fully implemented
 
-## Development
-
-Install the [pre-commit](https://pre-commit.com/) hooks:
-
-```shell
-pre-commit install
-```
-
-## Testing
-
-Ensure you have all Python versions installed that the tests will be run against. If using pyenv, run:
-
-```shell
-pyenv install 3.8.18
-pyenv install 3.9.18
-pyenv install 3.10.13
-pyenv install 3.11.5
-pyenv local 3.8.18 3.9.18 3.10.13 3.11.5
-```
-
-Run the entire test suite:
-
-```shell
-tox
-```
-
-Run a single test case using the standard pytest convention:
-
-```shell
-python -m pytest -v tests/test_models.py::test_item_extensions
-```
-
 ## Usage
 
 ### Loading Models
@@ -137,31 +99,34 @@ It also implements models for defining ItemSeach queries.
 ```python
 from stac_pydantic.api import Item, ItemCollection
 
-stac_item = Item(**{
-    "id": "12345",
-    "type": "Feature",
-    "stac_extensions": [],
-    "geometry": { "type": "Point", "coordinates": [0, 0] },
-    "bbox": [0.0, 0.0, 0.0, 0.0],
-    "properties": {
-        "datetime": "2020-03-09T14:53:23.262208+00:00",
-    },
-    "collection": "CS3",
-    "links": [
-          {
-            "rel": "self",
-            "href": "http://stac.example.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json"
-          },
-          {
-            "rel": "collection",
-            "href": "http://stac.example.com/catalog/CS3-20160503_132130_04/catalog.json"
-          },
-          {
-            "rel": "root",
-            "href": "http://stac.example.com/catalog"
-          }],
-    "assets": {},
-    })
+stac_item = Item.model_validate(
+    {
+        "id": "12345",
+        "type": "Feature",
+        "stac_extensions": [],
+        "geometry": { "type": "Point", "coordinates": [0, 0] },
+        "bbox": [0.0, 0.0, 0.0, 0.0],
+        "properties": {
+            "datetime": "2020-03-09T14:53:23.262208+00:00",
+        },
+        "collection": "CS3",
+        "links": [
+            {
+                "rel": "self",
+                "href": "http://stac.example.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json"
+            },
+            {
+                "rel": "collection",
+                "href": "http://stac.example.com/catalog/CS3-20160503_132130_04/catalog.json"
+            },
+            {
+                "rel": "root",
+                "href": "http://stac.example.com/catalog"
+            }
+        ],
+        "assets": {},
+    }
+)
 
 stac_item_collection = ItemCollection(**{
     "type": "FeatureCollection",
@@ -192,6 +157,55 @@ item_dict = item.model_dump()
 assert item_dict['properties']['landsat:row'] == item.properties.row == 250
 ```
 
+#### Required keys
+
+STAC specification requires some keys to be present even if their value is `null`. When exporting a model to dict or json, stac-pydantic will make sure to keep the keys even if `exclude_none` is set to `True`. Users can overwrite this by using `exclude={'key'}`.
+
+```python
+from stac_pydantic.api import Item
+
+stac_item = Item.model_validate(
+    {
+        "id": "12345",
+        "type": "Feature",
+        "stac_extensions": [],
+        "geometry": None,
+        "properties": {
+            "datetime": None,
+            "start_datetime": "2024-01-01T00:00:00Z",
+            "end_datetime": "2024-01-02T00:00:00Z",
+        },
+        "collection": "collection",
+        "links": [
+            {
+                "rel": "self",
+                "href": "http://stac.example.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json"
+            },
+            {
+                "rel": "collection",
+                "href": "http://stac.example.com/catalog/CS3-20160503_132130_04/catalog.json"
+            },
+            {
+                "rel": "root",
+                "href": "http://stac.example.com/catalog"
+            }
+        ],
+        "assets": {},
+    }
+)
+
+out = stac_item.model_dump(exclude_none=True)
+# `geometry` is required
+assert out["geometry"] is None
+# `datetime` is a required property
+assert out["properties"]["datetime"] is None
+
+# force exclusion of required keys
+out = stac_item.model_dump(exclude_none=True, exclude={"properties": {"datetime"}, "geometry": True})
+assert "geometry" not in out
+assert "datetime" not in out["properties"]
+```
+
 ### CLI
 
 ```text
@@ -205,3 +219,7 @@ Options:
 Commands:
   validate-item  Validate STAC Item
 ```
+
+## Contribution & Development
+
+See [CONTRIBUTING.md](CONTRIBUTING.md)


=====================================
debian/changelog
=====================================
@@ -1,3 +1,15 @@
+stac-pydantic (3.5.0-1) UNRELEASED; urgency=medium
+
+  * New upstream release.
+  * debian/patches:
+    - Drop 0001-Network-mark.patch, applied upstream.
+    - Refresh and renumber remainig patches.
+  * Reorder Files paragraphs in d/copyright by directory depth.
+  * debian/control:
+    - Add build-dependency on python3-hatchling.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it>  Sun, 08 Feb 2026 17:09:32 +0000
+
 stac-pydantic (3.4.0-1) unstable; urgency=low
 
   * Initial release (Closes: #1123818).


=====================================
debian/control
=====================================
@@ -9,6 +9,7 @@ Build-Depends: debhelper-compat (= 13),
                python3-click,
                python3-dictdiffer <!nocheck>,
                python3-geojson-pydantic,
+               python3-hatchling,
                python3-jsonschema,
                python3-pydantic,
                python3-pytest <!nocheck>,


=====================================
debian/copyright
=====================================
@@ -7,6 +7,14 @@ Files: *
 Copyright: 2020, Arturo AI
 License: Expat
 
+Files: debian/*
+Copyright: 2025-2026, Antonio Valentino <antonio.valentino at tiscali.it>
+License: Expat
+
+Files: tests/example_stac/example-collection_version-extension.json
+Copyright: NONE
+License: CC0-1.0
+
 Files: tests/api/examples/v1.0.0/itemcollection-sample-full.json
        tests/example_stac/example-search.json
        tests/example_stac/itemcollection-sample-full.json
@@ -14,14 +22,6 @@ Files: tests/api/examples/v1.0.0/itemcollection-sample-full.json
 Copyright: NONE
 License: PDDL-1.0
 
-Files: tests/example_stac/example-collection_version-extension.json
-Copyright: NONE
-License: CC0-1.0
-
-Files: debian/*
-Copyright: 2025, Antonio Valentino <antonio.valentino at tiscali.it>
-License: Expat
-
 License: CC0-1.0
  To the extent possible under law, the author(s) have dedicated all copyright
  and related and neighboring rights to this software to the public domain


=====================================
debian/patches/0001-Network-mark.patch deleted
=====================================
@@ -1,100 +0,0 @@
-From: Antonio Valentino <antonio.valentino at tiscali.it>
-Date: Mon, 22 Dec 2025 10:20:04 +0000
-Subject: Network-mark
-
-Makt tests requiring access to the internet.
-
-Forwarded: https://github.com/stac-utils/stac-pydantic/pull/187
----
- pyproject.toml                    | 1 +
- tests/api/test_item_collection.py | 1 +
- tests/api/test_landing_page.py    | 1 +
- tests/test_cli.py                 | 2 ++
- tests/test_models.py              | 4 ++++
- 5 files changed, 9 insertions(+)
-
-diff --git a/pyproject.toml b/pyproject.toml
-index d01281d..a8f0cfc 100644
---- a/pyproject.toml
-+++ b/pyproject.toml
-@@ -75,6 +75,7 @@ exclude = ["tests*"]
- 
- [tool.pytest.ini_options]
- addopts = "-sv --cov stac_pydantic --cov-report xml --cov-report term-missing  --cov-fail-under 95"
-+markers = ["network"]
- 
- [tool.isort]
- profile = "black"
-diff --git a/tests/api/test_item_collection.py b/tests/api/test_item_collection.py
-index d714750..b1ea8fe 100644
---- a/tests/api/test_item_collection.py
-+++ b/tests/api/test_item_collection.py
-@@ -12,6 +12,7 @@ ITEM_COLLECTION = "itemcollection-sample-full.json"
- PATH = ["tests", "api", "examples", f"v{STAC_API_VERSION}"]
- 
- 
-+ at pytest.mark.network
- @pytest.mark.parametrize(
-     "example_url",
-     [
-diff --git a/tests/api/test_landing_page.py b/tests/api/test_landing_page.py
-index 08fb765..280ef01 100644
---- a/tests/api/test_landing_page.py
-+++ b/tests/api/test_landing_page.py
-@@ -57,6 +57,7 @@ def test_landing_page_invalid_features(example_url):
-         LandingPage(**example)
- 
- 
-+ at pytest.mark.network
- @pytest.mark.parametrize("example_url,schema_url", unique_combinations)
- def test_schema(example_url, schema_url):
-     rsp_yaml = requests.get(schema_url).text
-diff --git a/tests/test_cli.py b/tests/test_cli.py
-index 4293cec..42e2162 100644
---- a/tests/test_cli.py
-+++ b/tests/test_cli.py
-@@ -1,6 +1,8 @@
- from stac_pydantic.scripts.cli import app
-+import pytest
- 
- 
-+ at pytest.mark.network
- def test_valid_stac_item(cli_runner):
-     result = cli_runner.invoke(
-         app,
-diff --git a/tests/test_models.py b/tests/test_models.py
-index a292134..ea0cb91 100644
---- a/tests/test_models.py
-+++ b/tests/test_models.py
-@@ -92,6 +92,7 @@ def test_item_assets_extension() -> None:
-     dict_match(test_coll, valid_coll)
- 
- 
-+ at pytest.mark.network
- def test_label_extension() -> None:
-     test_item = request(LABEL_EXTENSION)
- 
-@@ -119,6 +120,7 @@ def test_explicit_extension_validation() -> None:
-     validate_extensions(test_item)
- 
- 
-+ at pytest.mark.network
- def test_extension_validation_schema_cache() -> None:
-     # Defines 3 extensions, but one is a non-existing URL
-     test_item = request(EO_EXTENSION)
-@@ -296,6 +298,7 @@ def test_excludes() -> None:
-     assert "eo:bands" not in valid_item["properties"]
- 
- 
-+ at pytest.mark.network
- def test_validate_extensions() -> None:
-     test_item = request(SAR_EXTENSION)
-     assert validate_extensions(test_item)
-@@ -310,6 +313,7 @@ def test_validate_extensions_reraise_exception() -> None:
-         validate_extensions(test_item, reraise_exception=True)
- 
- 
-+ at pytest.mark.network
- def test_validate_extensions_rfc3339_with_partial_seconds() -> None:
-     test_item = request(SAR_EXTENSION)
-     test_item["properties"]["updated"] = "2018-10-01T01:08:32.033Z"


=====================================
debian/patches/0002-No-coverage.patch → debian/patches/0001-No-coverage.patch
=====================================
@@ -10,11 +10,11 @@ Forwarded: not-needed
  1 file changed, 1 insertion(+), 1 deletion(-)
 
 diff --git a/pyproject.toml b/pyproject.toml
-index a8f0cfc..70dcdb4 100644
+index 48f0032..313e161 100644
 --- a/pyproject.toml
 +++ b/pyproject.toml
-@@ -74,7 +74,7 @@ include = ["stac_pydantic*"]
- exclude = ["tests*"]
+@@ -76,7 +76,7 @@ only-include = ["stac_pydantic/"]
+ only-include = ["stac_pydantic/"]
  
  [tool.pytest.ini_options]
 -addopts = "-sv --cov stac_pydantic --cov-report xml --cov-report term-missing  --cov-fail-under 95"


=====================================
debian/patches/series
=====================================
@@ -1,2 +1 @@
-0001-Network-mark.patch
-0002-No-coverage.patch
+0001-No-coverage.patch


=====================================
pyproject.toml
=====================================
@@ -1,10 +1,7 @@
-[build-system]
-requires = ["setuptools>=61.0"]
-build-backend = "setuptools.build_meta"
-
 [project]
 name="stac-pydantic"
 description="Pydantic data models for the STAC spec"
+readme = "README.md"
 classifiers=[
         "Intended Audience :: Developers",
         "Intended Audience :: Information Technology",
@@ -15,18 +12,25 @@ classifiers=[
         "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: 3.12",
         "Programming Language :: Python :: 3.13",
+        "Programming Language :: Python :: 3.14",
         "License :: OSI Approved :: MIT License",
 ]
 keywords=["stac", "pydantic", "validation"]
-authors=[{ name = "Arturo Engineering", email = "engineering at arturo.ai"}]
-license= { text = "MIT" }
+authors=[
+        { name = "Arturo Engineering", email = "engineering at arturo.ai"},
+]
+maintainers = [
+  { name = "Vincent Sarago", email = "vincent at developmentseed.org" },
+  { name = "Pete Gadomski", email = "pete.gadomski at gmail.com" },
+]
+license = {file = "LICENSE"}
 requires-python=">=3.8"
+dynamic = ["version"]
 dependencies = [
         "click>=8.1.7",
         "pydantic>=2.4.1",
         "geojson-pydantic>=1.0.0",
 ]
-dynamic = ["version", "readme"]
 
 [project.scripts]
 stac-pydantic = "stac_pydantic.scripts.cli:app"
@@ -36,8 +40,12 @@ homepage = "https://github.com/stac-utils/stac-pydantic"
 repository ="https://github.com/stac-utils/stac-pydantic.git"
 
 [project.optional-dependencies]
-validation = ["jsonschema>=4.19.1", "requests>=2.31.0"]
+validation = [
+        "jsonschema>=4.19.1",
+        "requests>=2.31.0",
+]
 
+[dependency-groups]
 dev = [
         "pytest>=7.4.2",
         "pytest-cov>=4.1.0",
@@ -46,35 +54,30 @@ dev = [
         "shapely>=2.0.1",
         "dictdiffer>=0.9.0",
         "jsonschema>=4.19.1",
-        "pyyaml>=6.0.1"
+        "pyyaml>=6.0.1",
+        "pre-commit",
 ]
 
-lint = [
-        "types-requests>=2.31.0.5",
-        "types-jsonschema>=4.19.0.3",
-        "types-PyYAML>=6.0.12.12",
-        "black>=23.9.1",
-        "isort>=5.12.0",
-        "flake8>=6.1.0",
-        "Flake8-pyproject>=1.2.3",
-        "mypy>=1.5.1",
-        "pre-commit>=3.4.0",
-        "tox>=4.11.3"
+deploy = [
+    "hatch",
 ]
 
-[tool.setuptools.dynamic]
-version = { attr = "stac_pydantic.version.__version__" }
-readme = {file = ["README.md"], content-type = "text/markdown"}
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.version]
+path = "stac_pydantic/version.py"
 
-[tool.setuptools.package-data]
-stac_pydantic= ["*.typed"]
+[tool.hatch.build.targets.sdist]
+only-include = ["stac_pydantic/"]
 
-[tool.setuptools.packages.find]
-include = ["stac_pydantic*"]
-exclude = ["tests*"]
+[tool.hatch.build.targets.wheel]
+only-include = ["stac_pydantic/"]
 
 [tool.pytest.ini_options]
 addopts = "-sv --cov stac_pydantic --cov-report xml --cov-report term-missing  --cov-fail-under 95"
+markers = ["network"]
 
 [tool.isort]
 profile = "black"


=====================================
stac_pydantic/item.py
=====================================
@@ -1,7 +1,15 @@
 from typing import Any, Dict, List, Optional
 
 from geojson_pydantic import Feature
-from pydantic import AnyUrl, ConfigDict, Field, model_serializer, model_validator
+from pydantic import (
+    AnyUrl,
+    ConfigDict,
+    Field,
+    SerializationInfo,
+    SerializerFunctionWrapHandler,
+    model_serializer,
+    model_validator,
+)
 
 from stac_pydantic.links import Links
 from stac_pydantic.shared import SEMVER_REGEX, Asset, StacBaseModel, StacCommonMetadata
@@ -38,10 +46,19 @@ class Item(Feature, StacBaseModel):
         return values
 
     # https://github.com/developmentseed/geojson-pydantic/issues/147
-    @model_serializer(mode="wrap")
-    def _serialize(self, handler):
-        data = handler(self)
+    @model_serializer(when_used="always", mode="wrap")
+    def _serialize(
+        self,
+        serializer: SerializerFunctionWrapHandler,
+        info: SerializationInfo,
+    ):
+        data = serializer(self)
         for field in self.__geojson_exclude_if_none__:
             if field in data and data[field] is None:
                 del data[field]
+
+        if "geometry" not in data:
+            if info.exclude_none and "geometry" not in (info.exclude or {}):
+                data["geometry"] = None
+
         return data


=====================================
stac_pydantic/shared.py
=====================================
@@ -10,7 +10,10 @@ from pydantic import (
     BaseModel,
     ConfigDict,
     Field,
+    SerializationInfo,
+    SerializerFunctionWrapHandler,
     TypeAdapter,
+    model_serializer,
     model_validator,
 )
 from typing_extensions import Annotated, Self
@@ -34,7 +37,7 @@ UtcDatetime = Annotated[
     AfterValidator(lambda d: d.astimezone(timezone.utc)),
 ]
 
-SearchDatetime = TypeAdapter(Optional[UtcDatetime])
+SearchDatetime: TypeAdapter = TypeAdapter(Optional[UtcDatetime])
 
 
 class MimeTypes(str, Enum):
@@ -112,14 +115,14 @@ class StacBaseModel(BaseModel):
             by_alias=by_alias, exclude_unset=exclude_unset, **kwargs
         )
 
-    def model_dump(
+    def model_dump(  # type: ignore[override]
         self, *, by_alias: bool = True, exclude_unset: bool = True, **kwargs: Any
     ) -> Dict[str, Any]:
         return super().model_dump(
             by_alias=by_alias, exclude_unset=exclude_unset, **kwargs
         )
 
-    def model_dump_json(
+    def model_dump_json(  # type: ignore[override]
         self, *, by_alias: bool = True, exclude_unset: bool = True, **kwargs: Any
     ) -> str:
         return super().model_dump_json(
@@ -185,6 +188,22 @@ class StacCommonMetadata(StacBaseModel):
             )
         return self
 
+    @model_serializer(when_used="always", mode="wrap")
+    def include_datetime_null(
+        self,
+        serializer: SerializerFunctionWrapHandler,
+        info: SerializationInfo,
+    ):
+        """Custom Model serializer make sure to allways keep datetime."""
+        data = serializer(self)
+        start = data.get("start_datetime")
+        end = data.get("end_datetime")
+        if not data.get("datetime") and (start and end):
+            if info.exclude_none and "datetime" not in (info.exclude or {}):
+                data["datetime"] = None
+
+        return data
+
 
 class Asset(StacBaseModel):
     """


=====================================
stac_pydantic/version.py
=====================================
@@ -1,5 +1,5 @@
 """stac-pydantic and STAC spec versions."""
 
-__version__ = "3.4.0"
+__version__ = "3.5.0"
 
 STAC_VERSION = "1.0.0"


=====================================
tests/api/test_item_collection.py
=====================================
@@ -12,6 +12,7 @@ ITEM_COLLECTION = "itemcollection-sample-full.json"
 PATH = ["tests", "api", "examples", f"v{STAC_API_VERSION}"]
 
 
+ at pytest.mark.network
 @pytest.mark.parametrize(
     "example_url",
     [


=====================================
tests/api/test_landing_page.py
=====================================
@@ -57,6 +57,7 @@ def test_landing_page_invalid_features(example_url):
         LandingPage(**example)
 
 
+ at pytest.mark.network
 @pytest.mark.parametrize("example_url,schema_url", unique_combinations)
 def test_schema(example_url, schema_url):
     rsp_yaml = requests.get(schema_url).text


=====================================
tests/test_cli.py
=====================================
@@ -1,6 +1,9 @@
+import pytest
+
 from stac_pydantic.scripts.cli import app
 
 
+ at pytest.mark.network
 def test_valid_stac_item(cli_runner):
     result = cli_runner.invoke(
         app,


=====================================
tests/test_models.py
=====================================
@@ -92,6 +92,7 @@ def test_item_assets_extension() -> None:
     dict_match(test_coll, valid_coll)
 
 
+ at pytest.mark.network
 def test_label_extension() -> None:
     test_item = request(LABEL_EXTENSION)
 
@@ -119,6 +120,7 @@ def test_explicit_extension_validation() -> None:
     validate_extensions(test_item)
 
 
+ at pytest.mark.network
 def test_extension_validation_schema_cache() -> None:
     # Defines 3 extensions, but one is a non-existing URL
     test_item = request(EO_EXTENSION)
@@ -198,13 +200,43 @@ def test_geo_interface() -> None:
     ],
 )
 def test_stac_common_dates(args) -> None:
-    StacCommonMetadata(**args)
+    metadata = StacCommonMetadata(**args)
+    assert "datetime" in metadata.model_dump(mode="json")
+    assert "datetime" in metadata.model_dump(mode="json", exclude_unset=True)
+    assert "datetime" in metadata.model_dump(exclude_none=True)
+    assert "datetime" in metadata.model_dump(mode="json", exclude_none=True)
+    assert "datetime" not in metadata.model_dump(
+        exclude_none=True, exclude={"datetime"}
+    )
+
+
+def test_item_datetime_null() -> None:
+    """Check datetime custom serialization works for sub-model."""
+    test_item = request(SAR_EXTENSION)
+    test_item["properties"]["datetime"] = None
+    itm = Item.model_validate(test_item)
+    assert not itm.properties.datetime
+
+    itm_dict = itm.model_dump(exclude_none=True)
+    assert itm_dict["properties"]["datetime"] is None
+    assert Item.model_validate(itm_dict)
+
+    itm_dict = itm.model_dump(mode="json", exclude_none=True)
+    assert itm_dict["properties"]["datetime"] is None
+    assert Item.model_validate(itm_dict)
+
+    itm_json = itm.model_dump_json(exclude_none=True)
+    assert '"datetime":null' in itm_json
+    assert Item.model_validate_json(itm_json)
+
+    itm_dict = itm.model_dump(exclude_none=True, exclude={"properties": {"datetime"}})
+    assert "datetime" not in itm_dict
 
 
 def test_stac_null_datetime_required() -> None:
     with pytest.raises(ValidationError):
-        StacCommonMetadata(
-            **{
+        StacCommonMetadata.model_validate(
+            {
                 "start_datetime": "2024-01-01T00:00:00Z",
                 "end_datetime": "2024-01-02T00:00:00Z",
             }
@@ -296,6 +328,7 @@ def test_excludes() -> None:
     assert "eo:bands" not in valid_item["properties"]
 
 
+ at pytest.mark.network
 def test_validate_extensions() -> None:
     test_item = request(SAR_EXTENSION)
     assert validate_extensions(test_item)
@@ -310,6 +343,7 @@ def test_validate_extensions_reraise_exception() -> None:
         validate_extensions(test_item, reraise_exception=True)
 
 
+ at pytest.mark.network
 def test_validate_extensions_rfc3339_with_partial_seconds() -> None:
     test_item = request(SAR_EXTENSION)
     test_item["properties"]["updated"] = "2018-10-01T01:08:32.033Z"
@@ -341,8 +375,19 @@ def test_resolve_links() -> None:
 
 def test_geometry_null_item() -> None:
     test_item = request(ITEM_GEOMETRY_NULL)
-    valid_item = Item(**test_item).model_dump()
-    dict_match(test_item, valid_item)
+    valid_item = Item.model_validate(test_item)
+    dict_match(test_item, valid_item.model_dump())
+
+    assert not valid_item.geometry
+    item_dict = valid_item.model_dump(exclude_none=True)
+    assert "geometry" in item_dict
+
+    itm_json = valid_item.model_dump_json(exclude_none=True)
+    assert '"geometry":null' in itm_json
+    assert Item.model_validate_json(itm_json)
+
+    itm_dict = valid_item.model_dump(exclude_none=True, exclude={"geometry"})
+    assert "geometry" not in itm_dict
 
 
 def test_item_bbox_validation() -> None:


=====================================
tox.ini deleted
=====================================
@@ -1,35 +0,0 @@
-[tox]
-envlist = py38,py39,py310,py311,py312
-
-[testenv]
-extras = dev
-commands = python -m pytest
-
-[testenv:lint]
-extras = lint
-description = run linters
-commands = SKIP=mypy pre-commit run --all-files
-
-[testenv:type]
-extras = lint
-description = run type checks
-commands = pre-commit run mypy --all-files
-
-[testenv:build]
-basepython = python3
-skip_install = true
-deps = build
-commands = python -m build
-
-[testenv:release]
-setenv =
-    TWINE_USERNAME = {env:TWINE_USERNAME}
-    TWINE_PASSWORD = {env:TWINE_PASSWORD}
-basepython = python3
-skip_install = true
-deps =
-    {[testenv:build]deps}
-    twine >= 1.5.0
-commands =
-    {[testenv:build]commands}
-    twine upload --skip-existing dist/*


=====================================
uv.lock
=====================================
The diff for this file was not included because it is too large.


View it on GitLab: https://salsa.debian.org/debian-gis-team/stac-pydantic/-/compare/553c3e13da9573085c65894b0062b40418230191...d7fb978cbbfb847c66fa69584b7f0090e4b6256c

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/stac-pydantic/-/compare/553c3e13da9573085c65894b0062b40418230191...d7fb978cbbfb847c66fa69584b7f0090e4b6256c
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260208/168a5500/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list