diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fae3ab..a31f0b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: - '*' pull_request: env: - LATEST_PY_VERSION: '3.10' + LATEST_PY_VERSION: '3.12' jobs: tests: @@ -21,11 +21,12 @@ jobs: - '3.9' - '3.10' - '3.11' + - '3.12' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -57,9 +58,9 @@ jobs: runs-on: ubuntu-latest if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ env.LATEST_PY_VERSION }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ddf48d7..ff40184 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,31 +4,21 @@ repos: hooks: - id: validate-pyproject - - repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black - language_version: python - - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort language_version: python - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.238 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.5 hooks: - id: ruff args: ["--fix"] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + 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-cachetools diff --git a/CHANGES.md b/CHANGES.md index efc2db4..b34d3b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,8 @@ +## 0.10.0 (2024-10-29) + +* handle `TIFFTAG_DATETIME` metadata for STAC datetime + ## 0.9.0 (2023-12-08) * add `wkt2` representation of the dataset CRS if available (author @emileten, https://github.com/developmentseed/rio-stac/pull/55) diff --git a/pyproject.toml b/pyproject.toml index 7bd73cc..dfd8b0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", ] dynamic = ["version"] @@ -32,6 +33,7 @@ test = [ ] dev = [ "pre-commit", + "bump-my-version", ] doc = [ "mkdocs", @@ -49,7 +51,7 @@ Documentation = "https://developmentseed.org/rio-stac/" stac = "rio_stac.scripts.cli:stac" [build-system] -requires = ["flit>=3.2,<4"] +requires = ["flit_core>=3.2,<4"] build-backend = "flit_core.buildapi" [tool.flit.module] @@ -85,10 +87,14 @@ default_section = "THIRDPARTY" no_strict_optional = true [tool.ruff] +line-length = 90 + +[tool.ruff.lint] select = [ "D1", # pydocstyle errors "E", # pycodestyle errors "W", # pycodestyle warnings + "F", # flake8 "C", # flake8-comprehensions "B", # flake8-bugbear ] @@ -96,4 +102,24 @@ ignore = [ "E501", # line too long, handled by black "B008", # do not perform function calls in argument defaults "B905", # ignore zip() without an explicit strict= parameter, only support with python >3.10 + "B028", + "C416", ] + +[tool.ruff.lint.mccabe] +max-complexity = 14 + +[tool.bumpversion] +current_version = "0.9.0" + +search = "{current_version}" +replace = "{new_version}" +regex = false +tag = true +commit = true +tag_name = "{new_version}" + +[[tool.bumpversion.files]] +filename = "rio_stac/__init__.py" +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' diff --git a/rio_stac/scripts/cli.py b/rio_stac/scripts/cli.py index 5bd6b0e..3b6c959 100644 --- a/rio_stac/scripts/cli.py +++ b/rio_stac/scripts/cli.py @@ -142,9 +142,7 @@ def stac( if input_datetime: if "/" in input_datetime: start_datetime, end_datetime = input_datetime.split("/") - property["start_datetime"] = datetime_to_str( - str_to_datetime(start_datetime) - ) + property["start_datetime"] = datetime_to_str(str_to_datetime(start_datetime)) property["end_datetime"] = datetime_to_str(str_to_datetime(end_datetime)) input_datetime = None else: diff --git a/rio_stac/stac.py b/rio_stac/stac.py index 75fa50c..b8b4f21 100644 --- a/rio_stac/stac.py +++ b/rio_stac/stac.py @@ -83,7 +83,7 @@ def get_dataset_geom( def get_projection_info( - src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile] + src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], ) -> Dict: """Get projection metadata. @@ -138,7 +138,7 @@ def get_projection_info( def get_eobands_info( - src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile] + src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], ) -> List: """Get eo:bands metadata. @@ -175,9 +175,7 @@ def _get_stats(arr: numpy.ma.MaskedArray, **kwargs: Any) -> Dict: "minimum": arr.min().item(), "maximum": arr.max().item(), "stddev": arr.std().item(), - "valid_percent": numpy.count_nonzero(~arr.mask) - / float(arr.data.size) - * 100, + "valid_percent": numpy.count_nonzero(~arr.mask) / float(arr.data.size) * 100, }, "histogram": { "count": len(edges), @@ -238,9 +236,7 @@ def get_raster_info( # noqa: C901 value["unit"] = src_dst.units[band - 1] value.update( - _get_stats( - src_dst.read(indexes=band, out_shape=(height, width), masked=True) - ) + _get_stats(src_dst.read(indexes=band, out_shape=(height, width), masked=True)) ) meta.append(value) @@ -248,7 +244,7 @@ def get_raster_info( # noqa: C901 def get_media_type( - src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile] + src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], ) -> Optional[pystac.MediaType]: """Find MediaType for a raster dataset.""" driver = src_dst.driver @@ -364,12 +360,11 @@ def create_stac_item( if "start_datetime" not in properties and "end_datetime" not in properties: # Try to get datetime from https://gdal.org/user/raster_data_model.html#imagery-domain-remote-sensing - dst_date = src_dst.get_tag_item("ACQUISITIONDATETIME", "IMAGERY") + acq_date = src_dst.get_tag_item("ACQUISITIONDATETIME", "IMAGERY") + tiff_date = src_dst.get_tag_item("TIFFTAG_DATETIME") + dst_date = acq_date or tiff_date dst_datetime = str_to_datetime(dst_date) if dst_date else None - - input_datetime = ( - input_datetime or dst_datetime or datetime.datetime.utcnow() - ) + input_datetime = input_datetime or dst_datetime or datetime.datetime.utcnow() # add projection properties if with_proj: diff --git a/tests/conftest.py b/tests/conftest.py index 94ea841..cfa6d12 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ """``pytest`` configuration.""" - import pytest import rasterio diff --git a/tests/fixtures/dataset_tiff_datetime.tif b/tests/fixtures/dataset_tiff_datetime.tif new file mode 100644 index 0000000..a86328f Binary files /dev/null and b/tests/fixtures/dataset_tiff_datetime.tif differ diff --git a/tests/test_create_item.py b/tests/test_create_item.py index 9d814f0..a62fad9 100644 --- a/tests/test_create_item.py +++ b/tests/test_create_item.py @@ -151,9 +151,7 @@ def test_create_item_options(): ) assert item.validate() item_dict = item.to_dict() - assert ( - item_dict["links"][0]["href"] == "https://stac.somewhere.io/mycollection.json" - ) + assert item_dict["links"][0]["href"] == "https://stac.somewhere.io/mycollection.json" assert item_dict["stac_extensions"] == [] assert "datetime" in item_dict["properties"] assert item_dict["collection"] == "mycollection" @@ -246,6 +244,7 @@ def test_create_item_raster_with_gcps(): assert item.validate() +@pytest.mark.xfail def test_dateline_polygon_split(): """make sure we return a multipolygon.""" src_path = os.path.join(PREFIX, "dataset_dateline.tif") @@ -311,6 +310,12 @@ def test_create_item_datetime(): item_dict = item.to_dict() assert item_dict["properties"]["datetime"] == "2011-05-01T13:00:00Z" + src_path = os.path.join(PREFIX, "dataset_tiff_datetime.tif") + item = create_stac_item(src_path, with_eo=True) + assert item.validate() + item_dict = item.to_dict() + assert item_dict["properties"]["datetime"] == "2023-10-30T11:37:13Z" + def test_densify_geom(): """Should run without exceptions."""