From d72793810065f359ff4359e7c7daaf22ed842c37 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 7 Jan 2026 16:44:27 -0700 Subject: [PATCH 01/60] refactor!: initial v2 refactor --- .github/workflows/continuous-integration.yml | 60 +- .github/workflows/pr.yml | 4 +- .github/workflows/release-please.yml | 10 +- .github/workflows/release.yml | 10 +- .python-version | 2 +- docs/Makefile | 22 - docs/_static/STAC-03.png | Bin 118918 -> 0 bytes docs/api.rst | 230 - docs/api/asset.rst | 12 - docs/api/cache.rst | 6 - docs/api/catalog.rst | 12 - docs/api/collection.rst | 12 - docs/api/common_metadata.rst | 7 - docs/api/errors.rst | 7 - docs/api/extensions.rst | 38 - docs/api/extensions/base.rst | 6 - docs/api/extensions/classification.rst | 6 - docs/api/extensions/datacube.rst | 6 - docs/api/extensions/eo.rst | 6 - docs/api/extensions/ext.rst | 7 - docs/api/extensions/file.rst | 6 - docs/api/extensions/grid.rst | 6 - docs/api/extensions/hooks.rst | 6 - docs/api/extensions/item_assets.rst | 6 - docs/api/extensions/mgrs.rst | 6 - docs/api/extensions/mlm.rst | 8 - docs/api/extensions/pointcloud.rst | 6 - docs/api/extensions/projection.rst | 6 - docs/api/extensions/raster.rst | 6 - docs/api/extensions/render.rst | 6 - docs/api/extensions/sar.rst | 6 - docs/api/extensions/sat.rst | 6 - docs/api/extensions/scientific.rst | 6 - docs/api/extensions/storage.rst | 6 - docs/api/extensions/table.rst | 6 - docs/api/extensions/timestamps.rst | 6 - docs/api/extensions/version.rst | 6 - docs/api/extensions/view.rst | 6 - docs/api/extensions/xarray_assets.rst | 6 - docs/api/item.rst | 12 - docs/api/item_assets.rst | 7 - docs/api/item_collection.rst | 12 - docs/api/layout.rst | 6 - docs/api/link.rst | 12 - docs/api/media_type.rst | 7 - docs/api/provider.rst | 7 - docs/api/pystac.rst | 227 - docs/api/rel_type.rst | 7 - docs/api/serialization.rst | 12 - docs/api/serialization/common_properties.rst | 6 - docs/api/serialization/identify.rst | 20 - docs/api/serialization/migrate.rst | 6 - docs/api/stac_io.rst | 6 - docs/api/stac_object.rst | 19 - docs/api/summaries.rst | 12 - docs/api/utils.rst | 10 - docs/api/validation.rst | 13 - docs/api/validation/local_validator.rst | 6 - docs/api/validation/schema_uri_map.rst | 6 - docs/api/validation/stac_validator.rst | 12 - docs/api/version.rst | 7 - docs/concepts.rst | 1004 --- docs/conf.py | 258 - docs/contributing.rst | 154 - docs/example-catalog/catalog.json | 22 - .../2018-05/LC80150322018141LGN00.json | 276 - .../2018-06/LC80140332018166LGN00.json | 276 - .../2018-06/LC80300332018166LGN00.json | 276 - .../2018-07/LC80150332018189LGN00.json | 275 - .../landsat-8-l1/collection.json | 156 - docs/index.rst | 57 - docs/installation.rst | 121 - docs/make.bat | 35 - docs/quickstart.ipynb | 2401 ------ docs/tutorials.rst | 71 - .../adding-new-and-custom-extensions.ipynb | 444 - docs/tutorials/creating-a-landsat-stac.ipynb | 2542 ------ .../how-to-create-stac-catalogs.ipynb | 4341 ---------- .../how-to-read-data-from-stac.ipynb | 7466 ----------------- docs/tutorials/pystac-introduction.ipynb | 6660 --------------- docs/tutorials/pystac-spacenet-tutorial.ipynb | 377 - pyproject.toml | 124 +- pystac/asset.py | 395 - pystac/catalog.py | 1307 --- pystac/collection.py | 899 -- pystac/item.py | 534 -- pystac/item_collection.py | 257 - pystac/link.py | 528 -- pystac/stac_object.py | 701 -- src/pystac/__init__.py | 85 + src/pystac/asset.py | 148 + {pystac => src/pystac}/cache.py | 7 + src/pystac/catalog.py | 114 + src/pystac/collection.py | 264 + {pystac => src/pystac}/common_metadata.py | 4 +- src/pystac/constants.py | 5 + src/pystac/deserialize.py | 21 + {pystac => src/pystac}/errors.py | 0 {pystac => src/pystac}/extensions/__init__.py | 0 {pystac => src/pystac}/extensions/base.py | 0 .../pystac}/extensions/classification.py | 0 {pystac => src/pystac}/extensions/datacube.py | 0 {pystac => src/pystac}/extensions/eo.py | 0 {pystac => src/pystac}/extensions/ext.py | 0 {pystac => src/pystac}/extensions/file.py | 0 {pystac => src/pystac}/extensions/grid.py | 0 {pystac => src/pystac}/extensions/hooks.py | 0 .../pystac}/extensions/item_assets.py | 0 {pystac => src/pystac}/extensions/label.py | 0 {pystac => src/pystac}/extensions/mgrs.py | 0 {pystac => src/pystac}/extensions/mlm.py | 0 .../pystac}/extensions/pointcloud.py | 0 .../pystac}/extensions/projection.py | 0 {pystac => src/pystac}/extensions/raster.py | 4 +- {pystac => src/pystac}/extensions/render.py | 0 {pystac => src/pystac}/extensions/sar.py | 0 {pystac => src/pystac}/extensions/sat.py | 0 .../pystac}/extensions/scientific.py | 4 +- {pystac => src/pystac}/extensions/storage.py | 0 {pystac => src/pystac}/extensions/table.py | 0 .../pystac}/extensions/timestamps.py | 0 {pystac => src/pystac}/extensions/version.py | 0 {pystac => src/pystac}/extensions/view.py | 0 .../pystac}/extensions/xarray_assets.py | 0 src/pystac/geo_interface.py | 7 + {pystac => src/pystac}/html/JSON.jinja2 | 0 {pystac => src/pystac}/html/Macros.jinja2 | 0 {pystac => src/pystac}/html/__init__.py | 0 {pystac => src/pystac}/html/jinja_env.py | 0 src/pystac/io.py | 10 + src/pystac/item.py | 251 + {pystac => src/pystac}/item_assets.py | 0 src/pystac/item_collection.py | 22 + src/pystac/jsonschema.py | 110 + {pystac => src/pystac}/layout.py | 21 +- src/pystac/link.py | 143 + {pystac => src/pystac}/media_type.py | 0 {pystac => src/pystac}/py.typed | 0 src/pystac/reader.py | 37 + {pystac => src/pystac}/rel_type.py | 0 src/pystac/schemas/geojson/Feature.json | 505 ++ src/pystac/schemas/geojson/Geometry.json | 218 + src/pystac/schemas/stac/v1.0.0/basics.json | 18 + src/pystac/schemas/stac/v1.0.0/catalog.json | 94 + .../schemas/stac/v1.0.0/collection.json | 270 + src/pystac/schemas/stac/v1.0.0/datetime.json | 53 + .../schemas/stac/v1.0.0/instrument.json | 32 + src/pystac/schemas/stac/v1.0.0/item.json | 272 + src/pystac/schemas/stac/v1.0.0/licensing.json | 12 + src/pystac/schemas/stac/v1.0.0/provider.json | 47 + .../pystac/schemas/stac}/v1.1.0/bands.json | 0 .../pystac/schemas/stac}/v1.1.0/basics.json | 0 .../pystac/schemas/stac}/v1.1.0/catalog.json | 0 .../schemas/stac}/v1.1.0/collection.json | 0 .../pystac/schemas/stac}/v1.1.0/common.json | 0 .../schemas/stac}/v1.1.0/data-values.json | 0 .../pystac/schemas/stac}/v1.1.0/datetime.json | 0 .../schemas/stac}/v1.1.0/instrument.json | 0 .../pystac/schemas/stac}/v1.1.0/item.json | 0 .../schemas/stac}/v1.1.0/licensing.json | 0 .../pystac/schemas/stac}/v1.1.0/provider.json | 0 .../pystac}/serialization/__init__.py | 7 + .../serialization/common_properties.py | 4 +- .../pystac}/serialization/identify.py | 0 .../pystac}/serialization/migrate.py | 0 {pystac => src/pystac}/stac_io.py | 6 + src/pystac/stac_object.py | 447 + {pystac => src/pystac}/summaries.py | 4 +- {pystac => src/pystac}/utils.py | 21 +- {pystac => src/pystac/v1}/__init__.py | 0 {pystac => src/pystac/v1}/client.py | 0 {pystac => src/pystac/v1}/provider.py | 0 {pystac => src/pystac/v1}/static/__init__.py | 0 .../pystac/v1}/static/fields-normalized.json | 0 {pystac => src/pystac}/validation/__init__.py | 7 + .../validation/jsonschemas/__init__.py | 0 .../jsonschemas/geojson/Feature.json | 0 .../jsonschemas/geojson/Geometry.json | 0 .../jsonschemas/stac-spec/v1.1.0/bands.json | 24 + .../jsonschemas/stac-spec/v1.1.0/basics.json | 34 + .../jsonschemas/stac-spec/v1.1.0/catalog.json | 57 + .../stac-spec/v1.1.0/collection.json | 230 + .../jsonschemas/stac-spec/v1.1.0/common.json | 30 + .../stac-spec/v1.1.0/data-values.json | 84 + .../stac-spec/v1.1.0/datetime.json | 53 + .../stac-spec/v1.1.0/instrument.json | 32 + .../jsonschemas/stac-spec/v1.1.0/item.json | 347 + .../stac-spec/v1.1.0/licensing.json | 12 + .../stac-spec/v1.1.0/provider.json | 47 + .../pystac}/validation/local_validator.py | 0 .../pystac}/validation/schema_uri_map.py | 0 .../pystac}/validation/stac_validator.py | 0 src/pystac/validator.py | 11 + {pystac => src/pystac}/version.py | 0 src/pystac/writer.py | 35 + .../test_collection_from_dict[v1.0.0].yaml | 222 + .../test_collection_from_dict[v1.1.0].yaml | 205 + .../test_example_from_dict[path12].yaml | 68 + .../test_example_from_dict[path15].yaml | 478 ++ .../test_example_from_dict[path19].yaml | 559 ++ .../test_example_from_dict[path3].yaml | 165 + tests/conftest.py | 119 +- tests/data-files/examples/v1.0.0/catalog.json | 43 + .../collection-with-schemas.json | 277 + .../v1.0.0/collection-only/collection.json | 233 + .../examples/v1.0.0/collection.json | 112 + .../examples/v1.0.0/collectionless-item.json | 144 + .../data-files/examples/v1.0.0/core-item.json | 125 + .../examples/v1.0.0/extended-item.json | 199 + .../extensions-collection/collection.json | 68 + .../proj-example/proj-example.json | 285 + .../examples/v1.0.0/simple-item.json | 81 + .../examples/{1.1.0 => v1.1.0}/README.md | 0 .../examples/{1.1.0 => v1.1.0}/catalog.json | 0 .../collection-with-schemas.json | 311 + .../v1.1.0/collection-only/collection.json | 233 + .../{1.1.0 => v1.1.0}/collection.json | 0 .../collectionless-item.json | 0 .../examples/{1.1.0 => v1.1.0}/core-item.json | 0 .../examples/v1.1.0/extended-item.json | 210 + .../extensions-collection/collection.json | 0 .../proj-example/proj-example.json | 0 .../{1.1.0 => v1.1.0}/simple-item.json | 0 tests/test_catalog.py | 2042 +---- tests/test_collection.py | 844 +- tests/test_examples.py | 27 + tests/test_item.py | 693 +- tests/test_obstore.py | 20 + tests/{ => v1}/__init__.py | 0 .../TestCatalog.test_read_remote.yaml | 0 .../TestCatalog.test_validate_all[cat0].yaml | 0 .../TestCatalog.test_validate_all[cat1].yaml | 0 .../TestCatalog.test_validate_all[cat3].yaml | 0 .../TestCatalog.test_validate_all[cat4].yaml | 0 .../TestCatalog.test_validate_all[cat5].yaml | 0 .../TestCatalog.test_validate_all[cat6].yaml | 0 .../test_validate_all_with_max_n.yaml | 0 .../test_validate_all_with_recusive_off.yaml | 0 .../test_non_hierarchical_relative_link.yaml | 0 .../test_item/test_null_geometry.yaml | 0 .../test_proj_json_schema_is_readable.yaml | 0 .../test_stac_io/test_retry_stac_io.yaml | 0 .../test_stac_io/test_retry_stac_io_404.yaml | 0 .../test_urls_with_non_ascii_characters.yaml | 0 ...estcases[ABSOLUTE_PUBLISHED-catalog0].yaml | 0 ...estcases[ABSOLUTE_PUBLISHED-catalog1].yaml | 0 ...estcases[ABSOLUTE_PUBLISHED-catalog3].yaml | 0 ...estcases[ABSOLUTE_PUBLISHED-catalog4].yaml | 0 ...estcases[ABSOLUTE_PUBLISHED-catalog5].yaml | 0 ...estcases[ABSOLUTE_PUBLISHED-catalog6].yaml | 0 ...estcases[RELATIVE_PUBLISHED-catalog0].yaml | 0 ...estcases[RELATIVE_PUBLISHED-catalog1].yaml | 0 ...estcases[RELATIVE_PUBLISHED-catalog3].yaml | 0 ...estcases[RELATIVE_PUBLISHED-catalog4].yaml | 0 ...estcases[RELATIVE_PUBLISHED-catalog5].yaml | 0 ...estcases[RELATIVE_PUBLISHED-catalog6].yaml | 0 ...st_testcases[SELF_CONTAINED-catalog0].yaml | 0 ...st_testcases[SELF_CONTAINED-catalog1].yaml | 0 ...st_testcases[SELF_CONTAINED-catalog3].yaml | 0 ...st_testcases[SELF_CONTAINED-catalog4].yaml | 0 ...st_testcases[SELF_CONTAINED-catalog5].yaml | 0 ...st_testcases[SELF_CONTAINED-catalog6].yaml | 0 tests/v1/conftest.py | 115 + .../cbers-partial/CBERS4AWFI/collection.json | 0 .../cbers-partial/CBERS4MUX/collection.json | 0 .../CBERS4PAN10M/collection.json | 0 .../cbers-partial/CBERS4PAN5M/collection.json | 0 .../catalogs/cbers-partial/catalog.json | 0 .../catalogs/invalid-catalog/catalog.json | 0 .../acc/665946-labels/665946-labels.json | 0 .../acc/665946/665946.json | 0 .../acc/a42435-labels/a42435-labels.json | 0 .../acc/a42435/a42435.json | 0 .../acc/ca041a-labels/ca041a-labels.json | 0 .../acc/ca041a/ca041a.json | 0 .../label_catalog-v0.8.1/acc/collection.json | 0 .../acc/d41d81-labels/d41d81-labels.json | 0 .../acc/d41d81/d41d81.json | 0 .../label_catalog-v0.8.1/catalog.json | 0 .../dar/0a4c40-labels/0a4c40-labels.json | 0 .../dar/0a4c40/0a4c40.json | 0 .../dar/353093-labels/353093-labels.json | 0 .../dar/353093/353093.json | 0 .../dar/42f235-labels/42f235-labels.json | 0 .../dar/42f235/42f235.json | 0 .../dar/a017f9-labels/a017f9-labels.json | 0 .../dar/a017f9/a017f9.json | 0 .../dar/b15fce-labels/b15fce-labels.json | 0 .../dar/b15fce/b15fce.json | 0 .../label_catalog-v0.8.1/dar/collection.json | 0 .../dar/f883a0-labels/f883a0-labels.json | 0 .../dar/f883a0/f883a0.json | 0 .../kam/4e7c7f-labels/4e7c7f-labels.json | 0 .../kam/4e7c7f/4e7c7f.json | 0 .../label_catalog-v0.8.1/kam/collection.json | 0 .../mon/207cc7-labels/207cc7-labels.json | 0 .../mon/207cc7/207cc7.json | 0 .../mon/401175-labels/401175-labels.json | 0 .../mon/401175/401175.json | 0 .../mon/493701-labels/493701-labels.json | 0 .../mon/493701/493701.json | 0 .../label_catalog-v0.8.1/mon/collection.json | 0 .../mon/f15272-labels/f15272-labels.json | 0 .../mon/f15272/f15272.json | 0 .../nia/825a50-labels/825a50-labels.json | 0 .../nia/825a50/825a50.json | 0 .../label_catalog-v0.8.1/nia/collection.json | 0 .../ptn/abe1a3-labels/abe1a3-labels.json | 0 .../ptn/abe1a3/abe1a3.json | 0 .../label_catalog-v0.8.1/ptn/collection.json | 0 .../ptn/f49f31-labels/f49f31-labels.json | 0 .../ptn/f49f31/f49f31.json | 0 .../znz/06f252-labels/06f252-labels.json | 0 .../znz/06f252/06f252.json | 0 .../znz/076995-labels/076995-labels.json | 0 .../znz/076995/076995.json | 0 .../znz/33cae6-labels/33cae6-labels.json | 0 .../znz/33cae6/33cae6.json | 0 .../znz/3b20d4-labels/3b20d4-labels.json | 0 .../znz/3b20d4/3b20d4.json | 0 .../znz/3f8360-labels/3f8360-labels.json | 0 .../znz/3f8360/3f8360.json | 0 .../znz/425403-labels/425403-labels.json | 0 .../znz/425403/425403.json | 0 .../znz/75cdfa-labels/75cdfa-labels.json | 0 .../znz/75cdfa/75cdfa.json | 0 .../znz/9b8638-labels/9b8638-labels.json | 0 .../znz/9b8638/9b8638.json | 0 .../znz/aee7fd-labels/aee7fd-labels.json | 0 .../znz/aee7fd/aee7fd.json | 0 .../znz/bc32f1-labels/bc32f1-labels.json | 0 .../znz/bc32f1/bc32f1.json | 0 .../znz/bd5c14-labels/bd5c14-labels.json | 0 .../znz/bd5c14/bd5c14.json | 0 .../znz/c7415c-labels/c7415c-labels.json | 0 .../znz/c7415c/c7415c.json | 0 .../label_catalog-v0.8.1/znz/collection.json | 0 .../znz/e52478-labels/e52478-labels.json | 0 .../znz/e52478/e52478.json | 0 .../collection.json | 0 .../hurricane-harvey/catalog.json | 0 .../20170831_162740_ssc1d1.json | 0 .../20170831_172754_101c.json | 0 .../20170831_195425_SS02.json | 0 .../2017831_195552_SS02.json | 0 ...ston-East-20170831-103f-100d-0f4f-RGB.json | 0 .../hurricane-harvey-0831/catalog.json | 0 .../catalogs/test-case-1/catalog.json | 0 .../area-1-1-imagery/area-1-1-imagery.json | 0 .../area-1-1-labels/area-1-1-labels.json | 0 .../country-1/area-1-1/collection.json | 0 .../area-1-2-imagery/area-1-2-imagery.json | 0 .../area-1-2-labels/area-1-2-labels.json | 0 .../country-1/area-1-2/collection.json | 0 .../test-case-1/country-1/catalog.json | 0 .../area-2-1-imagery/area-2-1-imagery.json | 0 .../area-2-1-labels/area-2-1-labels.json | 0 .../country-2/area-2-1/collection.json | 0 .../area-2-2-imagery/area-2-2-imagery.json | 0 .../area-2-2-labels/area-2-2-labels.json | 0 .../country-2/area-2-2/collection.json | 0 .../test-case-1/country-2/catalog.json | 0 .../cf73ec1a-d790-4b59-b077-e101738571ed.json | 0 .../data.geojson | 0 .../collection.json | 0 .../collection.json | 0 .../collection.json | 0 .../d43bead8-e3f8-4c51-95d6-e24e750a402b.json | 0 .../catalogs/test-case-2/catalog.json | 0 .../acc/665946-labels/665946-labels.json | 0 .../test-case-4/acc/665946/665946.json | 0 .../acc/a42435-labels/a42435-labels.json | 0 .../test-case-4/acc/a42435/a42435.json | 0 .../acc/ca041a-labels/ca041a-labels.json | 0 .../test-case-4/acc/ca041a/ca041a.json | 0 .../catalogs/test-case-4/acc/collection.json | 0 .../acc/d41d81-labels/d41d81-labels.json | 0 .../test-case-4/acc/d41d81/d41d81.json | 0 .../catalogs/test-case-4/catalog.json | 0 .../dar/0a4c40-labels/0a4c40-labels.json | 0 .../test-case-4/dar/0a4c40/0a4c40.json | 0 .../dar/353093-labels/353093-labels.json | 0 .../test-case-4/dar/353093/353093.json | 0 .../dar/42f235-labels/42f235-labels.json | 0 .../test-case-4/dar/42f235/42f235.json | 0 .../dar/a017f9-labels/a017f9-labels.json | 0 .../test-case-4/dar/a017f9/a017f9.json | 0 .../dar/b15fce-labels/b15fce-labels.json | 0 .../test-case-4/dar/b15fce/b15fce.json | 0 .../catalogs/test-case-4/dar/collection.json | 0 .../dar/f883a0-labels/f883a0-labels.json | 0 .../test-case-4/dar/f883a0/f883a0.json | 0 .../kam/4e7c7f-labels/4e7c7f-labels.json | 0 .../test-case-4/kam/4e7c7f/4e7c7f.json | 0 .../catalogs/test-case-4/kam/collection.json | 0 .../mon/207cc7-labels/207cc7-labels.json | 0 .../test-case-4/mon/207cc7/207cc7.json | 0 .../mon/401175-labels/401175-labels.json | 0 .../test-case-4/mon/401175/401175.json | 0 .../mon/493701-labels/493701-labels.json | 0 .../test-case-4/mon/493701/493701.json | 0 .../catalogs/test-case-4/mon/collection.json | 0 .../mon/f15272-labels/f15272-labels.json | 0 .../test-case-4/mon/f15272/f15272.json | 0 .../nia/825a50-labels/825a50-labels.json | 0 .../test-case-4/nia/825a50/825a50.json | 0 .../catalogs/test-case-4/nia/collection.json | 0 .../ptn/abe1a3-labels/abe1a3-labels.json | 0 .../test-case-4/ptn/abe1a3/abe1a3.json | 0 .../catalogs/test-case-4/ptn/collection.json | 0 .../ptn/f49f31-labels/f49f31-labels.json | 0 .../test-case-4/ptn/f49f31/f49f31.json | 0 .../znz/06f252-labels/06f252-labels.json | 0 .../test-case-4/znz/06f252/06f252.json | 0 .../znz/076995-labels/076995-labels.json | 0 .../test-case-4/znz/076995/076995.json | 0 .../znz/33cae6-labels/33cae6-labels.json | 0 .../test-case-4/znz/33cae6/33cae6.json | 0 .../znz/3b20d4-labels/3b20d4-labels.json | 0 .../test-case-4/znz/3b20d4/3b20d4.json | 0 .../znz/3f8360-labels/3f8360-labels.json | 0 .../test-case-4/znz/3f8360/3f8360.json | 0 .../znz/425403-labels/425403-labels.json | 0 .../test-case-4/znz/425403/425403.json | 0 .../znz/75cdfa-labels/75cdfa-labels.json | 0 .../test-case-4/znz/75cdfa/75cdfa.json | 0 .../znz/9b8638-labels/9b8638-labels.json | 0 .../test-case-4/znz/9b8638/9b8638.json | 0 .../znz/aee7fd-labels/aee7fd-labels.json | 0 .../test-case-4/znz/aee7fd/aee7fd.json | 0 .../znz/bc32f1-labels/bc32f1-labels.json | 0 .../test-case-4/znz/bc32f1/bc32f1.json | 0 .../znz/bd5c14-labels/bd5c14-labels.json | 0 .../test-case-4/znz/bd5c14/bd5c14.json | 0 .../znz/c7415c-labels/c7415c-labels.json | 0 .../test-case-4/znz/c7415c/c7415c.json | 0 .../catalogs/test-case-4/znz/collection.json | 0 .../znz/e52478-labels/e52478-labels.json | 0 .../test-case-4/znz/e52478/e52478.json | 0 .../CBERS_4_MUX_20190510_027_069_L2.json | 0 .../CBERS4-MUX-027/069/catalog.json | 0 .../CBERS4-MUX-027/collection.json | 0 .../CBERS4MUX/CBERS4-MUX-027/catalog.json | 0 .../CBERS4/CBERS4MUX/collection.json | 0 .../catalogs/test-case-5/CBERS4/catalog.json | 0 .../catalogs/test-case-5/catalog.json | 0 .../3c67b59c-2e6f-47fb-ba3c-0dd106941096.json | 0 .../collection.json | 0 .../data.geojson | 0 .../3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json | 0 .../41c57cea-50ba-495c-9ee7-17ddba837380.json | 0 .../4514bbc6-cd17-4c14-816d-1709dfc61079.json | 0 .../52072b95-0275-4255-be5e-c567006f80cb.json | 0 .../84fe42c0-9d23-404a-bd0f-1406112cca8c.json | 0 .../884a939d-1d73-4351-9601-f9ee89a980b0.json | 0 .../a6bd2ff9-3805-40ab-96fa-b7f112b18973.json | 0 .../collection.json | 0 .../e40bb30d-0295-42ed-ba04-6cf563b057f9.json | 0 .../eb1124cc-3b45-4487-bc2c-4855ac877ad9.json | 0 .../collection.json | 0 .../catalogs/test-case-6/catalog.json | 0 .../data-files/change_stac_version.py | 6 +- .../classification_landsat_example.json | 0 .../collection-item-assets-raster-bands.json | 0 .../data-files/collections/multi-extent.json | 0 .../data-files/collections/with-assets.json | 0 tests/{ => v1}/data-files/datacube/item.json | 0 .../{ => v1}/data-files/eo/eo-collection.json | 0 .../data-files/eo/eo-landsat-example.json | 0 .../data-files/eo/eo-sentinel2-item.json | 0 .../eo/sample-bands-in-item-properties.json | 0 .../0.8.1/catalog-spec/examples/catalog.json | 0 .../catalog-spec/examples/summaries-s2.json | 0 .../examples/landsat-collection.json | 0 .../examples/landsat-item.json | 0 .../collection-spec/examples/sentinel2.json | 0 .../asset/examples/example-landsat8.json | 0 .../checksum/examples/example-sentinel1.json | 0 .../extensions/datacube/examples/example.json | 0 .../examples/example-video.json | 0 .../eo/examples/example-landsat8.json | 0 .../label/examples/multidataset/catalog.json | 0 .../AOI_2_Vegas_img2636.json | 0 .../AOI_3_Paris_img1648.json | 0 .../AOI_4_Shanghai_img3344.json | 0 .../spacenet-buildings/collection.json | 0 .../multidataset/zanzibar/collection.json | 0 .../multidataset/zanzibar/znz001.json | 0 .../multidataset/zanzibar/znz029.json | 0 .../spacenet-roads/roads_collection.json | 0 .../examples/spacenet-roads/roads_item.json | 0 .../examples/spacenet-roads/roads_source.json | 0 .../pointcloud/examples/example-autzen.json | 0 .../extensions/sar/examples/envisat.json | 0 .../extensions/sar/examples/sentinel1.json | 0 .../scientific/examples/collection.json | 0 .../extensions/scientific/examples/item.json | 0 .../examples/example-search.json | 0 .../examples/digitalglobe-sample.json | 0 .../examples/itemcollection-sample-full.json | 0 .../itemcollection-sample-minimal.json | 0 .../item-spec/examples/landsat8-sample.json | 0 .../item-spec/examples/planet-sample.json | 0 .../0.8.1/item-spec/examples/sample-full.json | 0 .../0.8.1/item-spec/examples/sample.json | 0 .../examples/sentinel-s2-l1c-collection.json | 0 .../item-spec/examples/sentinel2-sample.json | 0 .../0.9.0/catalog-spec/examples/catalog.json | 0 .../examples/landsat-collection.json | 0 .../examples/landsat-item.json | 0 .../collection-spec/examples/sentinel2.json | 0 .../asset/examples/example-landsat8.json | 0 .../checksum/examples/sentinel1.json | 0 .../commons/examples/landsat-collection.json | 0 .../commons/examples/landsat-item.json | 0 .../datacube/examples/example-collection.json | 0 .../datacube/examples/example-item.json | 0 .../eo/examples/example-landsat8.json | 0 .../label/examples/multidataset/catalog.json | 0 .../AOI_2_Vegas_img2636.json | 0 .../AOI_3_Paris_img1648.json | 0 .../AOI_4_Shanghai_img3344.json | 0 .../spacenet-buildings/collection.json | 0 .../multidataset/zanzibar/collection.json | 0 .../multidataset/zanzibar/znz001.json | 0 .../spacenet-roads/roads_collection.json | 0 .../examples/spacenet-roads/roads_item.json | 0 .../examples/spacenet-roads/roads_source.json | 0 .../pointcloud/examples/example-autzen.json | 0 .../projection/examples/example-landsat8.json | 0 .../extensions/sar/examples/envisat.json | 0 .../extensions/sar/examples/sentinel1.json | 0 .../sat/examples/example-landsat8.json | 0 .../scientific/examples/collection.json | 0 .../extensions/scientific/examples/item.json | 0 .../version/examples/collection.json | 0 .../extensions/version/examples/item.json | 0 .../view/examples/example-landsat8.json | 0 .../item-spec/examples/datetimerange.json | 0 .../examples/digitalglobe-sample.json | 0 .../examples/itemcollection-sample-full.json | 0 .../itemcollection-sample-minimal.json | 0 .../item-spec/examples/landsat8-sample.json | 0 .../item-spec/examples/planet-sample.json | 0 .../0.9.0/item-spec/examples/sample-full.json | 0 .../0.9.0/item-spec/examples/sample.json | 0 .../examples/sentinel-s2-l2a-collection.json | 0 .../item-spec/examples/sentinel2-sample.json | 0 .../examples/1.0.0-RC1/catalog.json | 0 .../1.0.0-RC1/collection-only/collection.json | 0 .../examples/1.0.0-RC1/collection.json | 0 .../1.0.0-RC1/collectionless-item.json | 0 .../examples/1.0.0-RC1/core-item.json | 0 .../examples/1.0.0-RC1/extended-item.json | 0 .../extensions-collection/collection.json | 0 .../proj-example/proj-example.json | 0 .../examples/1.0.0-RC1/simple-item.json | 0 .../examples/1.0.0-RC2/extended-item.json | 0 .../catalog-spec/examples/catalog-items.json | 0 .../catalog-spec/examples/catalog.json | 0 .../examples/landsat-collection.json | 0 .../collection-spec/examples/sentinel2.json | 0 .../checksum/examples/sentinel1.json | 0 .../examples/example-esm.json | 0 .../datacube/examples/example-collection.json | 0 .../datacube/examples/example-item.json | 0 .../eo/examples/example-landsat8.json | 0 .../examples/example-landsat8.json | 0 .../label/examples/multidataset/catalog.json | 0 .../AOI_2_Vegas_img2636.json | 0 .../AOI_3_Paris_img1648.json | 0 .../AOI_4_Shanghai_img3344.json | 0 .../spacenet-buildings/collection.json | 0 .../multidataset/zanzibar/collection.json | 0 .../multidataset/zanzibar/znz001.json | 0 .../multidataset/zanzibar/znz029.json | 0 .../spacenet-roads/roads_collection.json | 0 .../examples/spacenet-roads/roads_item.json | 0 .../examples/spacenet-roads/roads_source.json | 0 .../pointcloud/examples/example-autzen.json | 0 .../projection/examples/example-landsat8.json | 0 .../extensions/sar/examples/envisat.json | 0 .../extensions/sar/examples/sentinel1.json | 0 .../sat/examples/example-landsat8.json | 0 .../scientific/examples/collection.json | 0 .../extensions/scientific/examples/item.json | 0 .../examples/example-search.json | 0 .../examples/example-dimension.json | 0 .../tiled-assets/examples/example-tiled.json | 0 .../timestamps/examples/example-landsat8.json | 0 .../version/examples/collection.json | 0 .../extensions/version/examples/item.json | 0 .../view/examples/example-landsat8.json | 0 .../CBERS_4_MUX_20181029_177_106_L4.json | 0 .../item-spec/examples/datetimerange.json | 0 .../examples/digitalglobe-sample.json | 0 .../item-spec/examples/landsat8-sample.json | 0 .../item-spec/examples/null-geom-item.json | 0 .../item-spec/examples/planet-sample.json | 0 .../item-spec/examples/sample-full.json | 0 .../item-spec/examples/sample.json | 0 .../item-spec/examples/sentinel2-sample.json | 0 .../data-files/examples/1.0.0/README.md | 0 .../data-files/examples/1.0.0/catalog.json | 0 .../collection-with-schemas.json | 0 .../1.0.0/collection-only/collection.json | 0 .../data-files/examples/1.0.0/collection.json | 0 .../examples/1.0.0/collectionless-item.json | 0 .../data-files/examples/1.0.0/core-item.json | 0 .../examples/1.0.0/example-sentinel2.json | 0 .../examples/1.0.0/extended-item.json | 0 .../extensions-collection/collection.json | 0 .../proj-example/proj-example.json | 0 .../examples/1.0.0/simple-item.json | 0 tests/v1/data-files/examples/1.1.0/README.md | 91 + .../v1/data-files/examples/1.1.0/catalog.json | 43 + .../collection-with-schemas.json | 0 .../1.1.0/collection-only/collection.json | 0 .../data-files/examples/1.1.0/collection.json | 136 + .../examples/1.1.0/collectionless-item.json | 143 + .../data-files/examples/1.1.0/core-item.json | 125 + .../examples/1.1.0/extended-item.json | 0 .../extensions-collection/collection.json | 68 + .../proj-example/proj-example.json | 285 + .../examples/1.1.0/simple-item.json | 81 + .../data-files/examples/example-info.csv | 0 .../examples/hand-0.8.1/010100/010100.json | 0 .../examples/hand-0.8.1/collection.json | 0 .../examples/hand-0.9.0/010100/010100.json | 0 .../examples/hand-0.9.0/collection.json | 0 tests/{ => v1}/data-files/file/catalog.json | 0 .../{ => v1}/data-files/file/collection.json | 0 tests/{ => v1}/data-files/file/item.json | 0 .../data-files/geojson/sample.geojson | 0 tests/{ => v1}/data-files/get_examples.py | 0 .../data-files/grid/example-landsat.json | 0 .../data-files/grid/example-sentinel2.json | 0 .../data-files/invalid/shared-id/catalog.json | 0 .../invalid/shared-id/test/collection.json | 0 .../test/test-item-1/test-item-1.json | 0 .../item-assets/example-landsat8.json | 0 .../sample-item-collection.json | 0 .../item/sample-item-asset-properties.json | 0 ...ple-item-with-relative-extension-path.json | 0 .../{ => v1}/data-files/item/sample-item.json | 0 tests/{ => v1}/data-files/mgrs/item.json | 0 tests/{ => v1}/data-files/mlm/collection.json | 0 tests/{ => v1}/data-files/mlm/item_basic.json | 0 .../pointcloud/example-laz-no-statistics.json | 0 .../data-files/pointcloud/example-laz.json | 0 .../data-files/projection/another-1.1.json | 0 .../projection/collection-with-summaries.json | 0 .../projection/example-landsat8.json | 0 .../projection/example-with-version-1.1.json | 0 .../projection/example-with-version-1.2.json | 0 .../data-files/projection/optional-epsg.json | 0 .../{ => v1}/data-files/raster/gdalinfo.json | 0 .../raster/landsat-collection-example.json | 0 .../raster/raster-planet-example.json | 0 .../raster/raster-sentinel2-example.json | 0 .../data-files/render/collection.json | 0 tests/{ => v1}/data-files/render/item.json | 0 tests/{ => v1}/data-files/sar/sentinel-1.json | 0 tests/{ => v1}/data-files/sat/sentinel-1.json | 0 .../data-files/schemas/v1.0.0-projection.json | 0 .../data-files/scientific/collection.json | 0 .../{ => v1}/data-files/scientific/item.json | 0 .../data-files/storage/collection-naip.json | 0 .../data-files/storage/item-naip.json | 0 .../data-files/summaries/fields_no_bands.json | 0 tests/{ => v1}/data-files/table/README.md | 0 .../data-files/table/collection-2.json | 0 .../{ => v1}/data-files/table/collection.json | 0 tests/{ => v1}/data-files/table/item.json | 0 .../data-files/table/table-collection.json | 0 .../timestamps/example-landsat8.json | 0 .../data-files/version/collection.json | 0 tests/{ => v1}/data-files/version/item.json | 0 .../view/collection-with-summaries.json | 0 .../data-files/view/example-landsat8.json | 0 .../data-files/windows_hrefs/catalog.json | 0 .../test-collection/collection.json | 0 .../test-collection/test-item/test-asset.txt | 0 .../test-collection/test-item/test-item.json | 0 .../data-files/xarray-assets/collection.json | 0 .../data-files/xarray-assets/item.json | 0 tests/{ => v1}/extensions/__init__.py | 0 .../test_apply_bitfields.yaml | 0 .../test_apply_classes.yaml | 0 .../test_validate_classification.yaml | 0 .../test_datacube/test_set_dimensions.yaml | 0 .../test_datacube/test_set_variables.yaml | 0 .../test_datacube/test_validate.yaml | 0 .../cassettes/test_eo/test_asset_bands.yaml | 0 .../cassettes/test_eo/test_bands.yaml | 0 .../cassettes/test_eo/test_cloud_cover.yaml | 0 .../test_set_field[cloud_cover-7.8].yaml | 0 .../test_set_field[snow_cover-99].yaml | 0 .../cassettes/test_eo/test_validate_eo.yaml | 0 .../test_file/test_migrate_from_v1_0_0.yaml | 0 .../test_file/test_migrate_from_v2_0_0.yaml | 0 ...ations-local_path-different-file.xml].yaml | 0 ...n_asset[measurement-header_size-8192].yaml | 0 ...t[thumbnail-byte_order-little-endian].yaml | 0 ...0210163700a8a6501eccd00b6d3b44ddaed0].yaml | 0 ..._set_field_on_asset[thumbnail-size-1].yaml | 0 ..._on_link[about-byte_order-big-endian].yaml | 0 ...0210163700a8a6501eccd00b6d3b44ddaedb].yaml | 0 ...field_on_link[about-header_size-4092].yaml | 0 ...ield_on_link[about-local_path-a-path].yaml | 0 ..._set_field_on_link[about-size-129302].yaml | 0 .../test_file/test_validate_catalog.yaml | 0 .../test_file/test_validate_collection.yaml | 0 .../test_file/test_validate_item.yaml | 0 .../cassettes/test_grid/test_attributes.yaml | 0 .../cassettes/test_grid/test_modify.yaml | 0 .../test_set_field[grid_square-ZA].yaml | 0 .../test_set_field[latitude_band-C].yaml | 0 .../test_set_field[utm_zone-59].yaml | 0 .../cassettes/test_mgrs/test_validate.yaml | 0 .../cassettes/test_mlm/test_apply.yaml | 0 .../cassettes/test_mlm/test_validate_mlm.yaml | 0 .../cassettes/test_pointcloud/test_count.yaml | 0 .../test_pointcloud/test_density.yaml | 0 .../test_pointcloud/test_encoding.yaml | 0 .../test_pointcloud/test_schemas.yaml | 0 .../test_pointcloud/test_statistics.yaml | 0 .../cassettes/test_pointcloud/test_type.yaml | 0 .../test_validate_pointcloud.yaml | 0 .../cassettes/test_projection/test_bbox.yaml | 0 .../test_projection/test_centroid.yaml | 0 .../cassettes/test_projection/test_epsg.yaml | 0 .../test_projection/test_geometry.yaml | 0 .../test_projection/test_partial_apply.yaml | 0 .../test_projection/test_projjson.yaml | 0 .../cassettes/test_projection/test_shape.yaml | 0 .../test_projection/test_transform.yaml | 0 .../test_projection/test_validate_proj.yaml | 0 .../cassettes/test_projection/test_wkt2.yaml | 0 .../test_raster/test_asset_bands.yaml | 0 .../test_raster/test_validate_raster.yaml | 0 .../test_render/test_collection_validate.yaml | 0 .../test_render/test_item_validate.yaml | 0 .../cassettes/test_sar/test_all.yaml | 0 .../cassettes/test_sar/test_required.yaml | 0 .../test_sat/test_absolute_orbit.yaml | 0 .../cassettes/test_sat/test_anx_datetime.yaml | 0 .../cassettes/test_sat/test_both.yaml | 0 .../test_sat/test_clear_orbit_state.yaml | 0 .../test_sat/test_clear_relative_orbit.yaml | 0 .../cassettes/test_sat/test_modify.yaml | 0 .../test_sat/test_no_args_fails.yaml | 0 .../cassettes/test_sat/test_orbit_state.yaml | 0 ...est_platform_international_designator.yaml | 0 .../test_sat/test_relative_orbit.yaml | 0 .../test_relative_orbit_no_negative.yaml | 0 .../test_scientific/test_citation.yaml | 0 .../test_collection_citation.yaml | 0 .../test_scientific/test_collection_doi.yaml | 0 .../test_collection_publications.yaml | 0 .../test_collection_publications_one.yaml | 0 ...ollection_remove_all_publications_one.yaml | 0 ...ion_remove_all_publications_with_none.yaml | 0 ...ion_remove_all_publications_with_some.yaml | 0 ...collection_remove_publication_forward.yaml | 0 ...est_collection_remove_publication_one.yaml | 0 ...collection_remove_publication_reverse.yaml | 0 .../cassettes/test_scientific/test_doi.yaml | 0 .../test_scientific/test_publications.yaml | 0 .../test_publications_one.yaml | 0 .../test_remove_all_publications_one.yaml | 0 ...est_remove_all_publications_with_none.yaml | 0 ...est_remove_all_publications_with_some.yaml | 0 .../test_remove_publication_forward.yaml | 0 .../test_remove_publication_one.yaml | 0 .../test_remove_publication_reverse.yaml | 0 .../test_storage/test_asset_platform.yaml | 0 .../test_storage/test_asset_region.yaml | 0 .../test_asset_requester_pays.yaml | 0 .../test_storage/test_asset_tier.yaml | 0 .../test_storage/test_validate_storage.yaml | 0 .../cassettes/test_table/test_validate.yaml | 0 .../test_timestamps/test_expires.yaml | 0 .../test_timestamps/test_published.yaml | 0 .../test_timestamps/test_unpublished.yaml | 0 .../test_validate_timestamps.yaml | 0 .../test_add_deprecated_version.yaml | 0 .../test_add_not_deprecated_version.yaml | 0 .../test_version/test_add_version.yaml | 0 .../test_version/test_all_links.yaml | 0 .../cassettes/test_version/test_assets.yaml | 0 .../test_catalog_add_version.yaml | 0 .../test_catalog_validate_all.yaml | 0 .../test_collection_add_version.yaml | 0 .../test_collection_validate_all.yaml | 0 .../cassettes/test_version/test_latest.yaml | 0 .../test_version/test_optional_version.yaml | 0 .../test_version/test_predecessor.yaml | 0 .../test_version/test_successor.yaml | 0 .../test_version_in_properties.yaml | 0 .../cassettes/test_view/test_azimuth.yaml | 0 .../test_view/test_incidence_angle.yaml | 0 .../cassettes/test_view/test_off_nadir.yaml | 0 .../cassettes/test_view/test_sun_azimuth.yaml | 0 .../test_view/test_sun_elevation.yaml | 0 .../test_view/test_validate_view.yaml | 0 .../test_collection_validate.yaml | 0 .../test_item_validate.yaml | 0 .../test_set_field[open_kwargs-value1].yaml | 0 ...est_set_field[storage_options-value0].yaml | 0 .../extensions/test_classification.py | 3 +- tests/{ => v1}/extensions/test_custom.py | 0 tests/{ => v1}/extensions/test_datacube.py | 3 +- tests/{ => v1}/extensions/test_eo.py | 5 +- tests/{ => v1}/extensions/test_ext.py | 3 +- tests/{ => v1}/extensions/test_file.py | 3 +- tests/{ => v1}/extensions/test_grid.py | 5 +- tests/{ => v1}/extensions/test_mgrs.py | 3 +- tests/{ => v1}/extensions/test_mlm.py | 3 +- tests/{ => v1}/extensions/test_pointcloud.py | 3 +- tests/{ => v1}/extensions/test_projection.py | 3 +- tests/{ => v1}/extensions/test_raster.py | 5 +- tests/{ => v1}/extensions/test_render.py | 3 +- tests/{ => v1}/extensions/test_sar.py | 3 +- tests/{ => v1}/extensions/test_sat.py | 3 +- tests/{ => v1}/extensions/test_scientific.py | 3 +- tests/{ => v1}/extensions/test_storage.py | 3 +- tests/{ => v1}/extensions/test_table.py | 3 +- tests/{ => v1}/extensions/test_timestamps.py | 3 +- tests/{ => v1}/extensions/test_version.py | 3 +- tests/{ => v1}/extensions/test_view.py | 3 +- .../{ => v1}/extensions/test_xarray_assets.py | 3 +- tests/{ => v1}/html/__init__.py | 0 tests/{ => v1}/html/test_html.py | 3 +- tests/{ => v1}/posix_paths/__init__.py | 0 .../{ => v1}/posix_paths/test_posix_paths.py | 5 +- tests/{ => v1}/serialization/__init__.py | 0 tests/{ => v1}/serialization/test_identify.py | 5 +- tests/{ => v1}/serialization/test_migrate.py | 5 +- tests/{ => v1}/test_asset.py | 0 tests/{ => v1}/test_cache.py | 3 +- tests/v1/test_catalog.py | 2036 +++++ tests/v1/test_collection.py | 845 ++ tests/{ => v1}/test_common_metadata.py | 3 +- tests/v1/test_item.py | 695 ++ tests/{ => v1}/test_item_assets.py | 3 +- tests/{ => v1}/test_item_collection.py | 5 +- tests/{ => v1}/test_layout.py | 13 +- tests/{ => v1}/test_link.py | 3 +- tests/{ => v1}/test_pystac_client.py | 0 tests/{ => v1}/test_stac_io.py | 3 +- tests/{ => v1}/test_summaries.py | 3 +- tests/{ => v1}/test_utils.py | 11 +- tests/{ => v1}/test_version.py | 3 +- tests/{ => v1}/test_writing.py | 3 +- tests/{ => v1}/utils/__init__.py | 7 +- tests/{ => v1}/utils/os_utils.py | 0 tests/{ => v1}/utils/stac_io_mock.py | 0 tests/{ => v1}/utils/test_cases.py | 6 +- tests/{ => v1}/validation/__init__.py | 0 .../TestValidate.test_validate_all.yaml | 0 ...test_validate_all_deprecated_dict_arg.yaml | 0 ...te.test_validate_all_dict[test_case0].yaml | 0 ...te.test_validate_all_dict[test_case1].yaml | 0 ...te.test_validate_all_dict[test_case2].yaml | 0 ...te.test_validate_all_dict[test_case3].yaml | 0 ...te.test_validate_all_dict[test_case4].yaml | 0 ...te.test_validate_all_dict[test_case5].yaml | 0 ...te.test_validate_all_dict[test_case6].yaml | 0 ...lidate.test_validate_custom_validator.yaml | 0 ...date.test_validate_examples[example0].yaml | 0 ...te.test_validate_examples[example100].yaml | 0 ...te.test_validate_examples[example101].yaml | 0 ...te.test_validate_examples[example102].yaml | 0 ...te.test_validate_examples[example103].yaml | 0 ...te.test_validate_examples[example104].yaml | 0 ...te.test_validate_examples[example105].yaml | 0 ...te.test_validate_examples[example106].yaml | 0 ...te.test_validate_examples[example107].yaml | 0 ...te.test_validate_examples[example108].yaml | 0 ...te.test_validate_examples[example109].yaml | 0 ...ate.test_validate_examples[example10].yaml | 0 ...te.test_validate_examples[example110].yaml | 0 ...te.test_validate_examples[example111].yaml | 0 ...te.test_validate_examples[example112].yaml | 0 ...te.test_validate_examples[example113].yaml | 0 ...te.test_validate_examples[example114].yaml | 0 ...te.test_validate_examples[example115].yaml | 0 ...te.test_validate_examples[example116].yaml | 0 ...te.test_validate_examples[example117].yaml | 0 ...te.test_validate_examples[example118].yaml | 0 ...te.test_validate_examples[example119].yaml | 0 ...ate.test_validate_examples[example11].yaml | 0 ...te.test_validate_examples[example120].yaml | 0 ...te.test_validate_examples[example121].yaml | 0 ...te.test_validate_examples[example122].yaml | 0 ...te.test_validate_examples[example123].yaml | 0 ...te.test_validate_examples[example124].yaml | 0 ...ate.test_validate_examples[example12].yaml | 0 ...ate.test_validate_examples[example13].yaml | 0 ...ate.test_validate_examples[example14].yaml | 0 ...ate.test_validate_examples[example15].yaml | 0 ...ate.test_validate_examples[example16].yaml | 0 ...ate.test_validate_examples[example17].yaml | 0 ...ate.test_validate_examples[example18].yaml | 0 ...ate.test_validate_examples[example19].yaml | 0 ...date.test_validate_examples[example1].yaml | 0 ...ate.test_validate_examples[example20].yaml | 0 ...ate.test_validate_examples[example21].yaml | 0 ...ate.test_validate_examples[example22].yaml | 0 ...ate.test_validate_examples[example23].yaml | 0 ...ate.test_validate_examples[example24].yaml | 0 ...ate.test_validate_examples[example25].yaml | 0 ...ate.test_validate_examples[example26].yaml | 0 ...ate.test_validate_examples[example27].yaml | 0 ...ate.test_validate_examples[example28].yaml | 0 ...ate.test_validate_examples[example29].yaml | 0 ...date.test_validate_examples[example2].yaml | 0 ...ate.test_validate_examples[example30].yaml | 0 ...ate.test_validate_examples[example31].yaml | 0 ...ate.test_validate_examples[example32].yaml | 0 ...ate.test_validate_examples[example33].yaml | 0 ...ate.test_validate_examples[example34].yaml | 0 ...ate.test_validate_examples[example35].yaml | 0 ...ate.test_validate_examples[example36].yaml | 0 ...ate.test_validate_examples[example37].yaml | 0 ...ate.test_validate_examples[example38].yaml | 0 ...ate.test_validate_examples[example39].yaml | 0 ...date.test_validate_examples[example3].yaml | 0 ...ate.test_validate_examples[example40].yaml | 0 ...ate.test_validate_examples[example41].yaml | 0 ...ate.test_validate_examples[example42].yaml | 0 ...ate.test_validate_examples[example43].yaml | 0 ...ate.test_validate_examples[example44].yaml | 0 ...ate.test_validate_examples[example45].yaml | 0 ...ate.test_validate_examples[example46].yaml | 0 ...ate.test_validate_examples[example47].yaml | 0 ...ate.test_validate_examples[example48].yaml | 0 ...ate.test_validate_examples[example49].yaml | 0 ...date.test_validate_examples[example4].yaml | 0 ...ate.test_validate_examples[example50].yaml | 0 ...ate.test_validate_examples[example51].yaml | 0 ...ate.test_validate_examples[example52].yaml | 0 ...ate.test_validate_examples[example53].yaml | 0 ...ate.test_validate_examples[example54].yaml | 0 ...ate.test_validate_examples[example55].yaml | 0 ...ate.test_validate_examples[example56].yaml | 0 ...ate.test_validate_examples[example57].yaml | 0 ...ate.test_validate_examples[example58].yaml | 0 ...ate.test_validate_examples[example59].yaml | 0 ...date.test_validate_examples[example5].yaml | 0 ...ate.test_validate_examples[example60].yaml | 0 ...ate.test_validate_examples[example61].yaml | 0 ...ate.test_validate_examples[example62].yaml | 0 ...ate.test_validate_examples[example63].yaml | 0 ...ate.test_validate_examples[example64].yaml | 0 ...ate.test_validate_examples[example65].yaml | 0 ...ate.test_validate_examples[example66].yaml | 0 ...ate.test_validate_examples[example67].yaml | 0 ...ate.test_validate_examples[example68].yaml | 0 ...ate.test_validate_examples[example69].yaml | 0 ...date.test_validate_examples[example6].yaml | 0 ...ate.test_validate_examples[example70].yaml | 0 ...ate.test_validate_examples[example71].yaml | 0 ...ate.test_validate_examples[example72].yaml | 0 ...ate.test_validate_examples[example73].yaml | 0 ...ate.test_validate_examples[example74].yaml | 0 ...ate.test_validate_examples[example75].yaml | 0 ...ate.test_validate_examples[example76].yaml | 0 ...ate.test_validate_examples[example77].yaml | 0 ...ate.test_validate_examples[example78].yaml | 0 ...ate.test_validate_examples[example79].yaml | 0 ...date.test_validate_examples[example7].yaml | 0 ...ate.test_validate_examples[example80].yaml | 0 ...ate.test_validate_examples[example81].yaml | 0 ...ate.test_validate_examples[example82].yaml | 0 ...ate.test_validate_examples[example83].yaml | 0 ...ate.test_validate_examples[example84].yaml | 0 ...ate.test_validate_examples[example85].yaml | 0 ...ate.test_validate_examples[example86].yaml | 0 ...ate.test_validate_examples[example87].yaml | 0 ...ate.test_validate_examples[example88].yaml | 0 ...ate.test_validate_examples[example89].yaml | 0 ...date.test_validate_examples[example8].yaml | 0 ...ate.test_validate_examples[example90].yaml | 0 ...ate.test_validate_examples[example91].yaml | 0 ...ate.test_validate_examples[example92].yaml | 0 ...ate.test_validate_examples[example93].yaml | 0 ...ate.test_validate_examples[example94].yaml | 0 ...ate.test_validate_examples[example95].yaml | 0 ...ate.test_validate_examples[example96].yaml | 0 ...ate.test_validate_examples[example97].yaml | 0 ...ate.test_validate_examples[example98].yaml | 0 ...ate.test_validate_examples[example99].yaml | 0 ...date.test_validate_examples[example9].yaml | 0 .../validation/test_schema_uri_map.py | 0 tests/{ => v1}/validation/test_validate.py | 9 +- uv.lock | 5046 ++--------- 998 files changed, 13977 insertions(+), 40901 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/_static/STAC-03.png delete mode 100644 docs/api.rst delete mode 100644 docs/api/asset.rst delete mode 100644 docs/api/cache.rst delete mode 100644 docs/api/catalog.rst delete mode 100644 docs/api/collection.rst delete mode 100644 docs/api/common_metadata.rst delete mode 100644 docs/api/errors.rst delete mode 100644 docs/api/extensions.rst delete mode 100644 docs/api/extensions/base.rst delete mode 100644 docs/api/extensions/classification.rst delete mode 100644 docs/api/extensions/datacube.rst delete mode 100644 docs/api/extensions/eo.rst delete mode 100644 docs/api/extensions/ext.rst delete mode 100644 docs/api/extensions/file.rst delete mode 100644 docs/api/extensions/grid.rst delete mode 100644 docs/api/extensions/hooks.rst delete mode 100644 docs/api/extensions/item_assets.rst delete mode 100644 docs/api/extensions/mgrs.rst delete mode 100644 docs/api/extensions/mlm.rst delete mode 100644 docs/api/extensions/pointcloud.rst delete mode 100644 docs/api/extensions/projection.rst delete mode 100644 docs/api/extensions/raster.rst delete mode 100644 docs/api/extensions/render.rst delete mode 100644 docs/api/extensions/sar.rst delete mode 100644 docs/api/extensions/sat.rst delete mode 100644 docs/api/extensions/scientific.rst delete mode 100644 docs/api/extensions/storage.rst delete mode 100644 docs/api/extensions/table.rst delete mode 100644 docs/api/extensions/timestamps.rst delete mode 100644 docs/api/extensions/version.rst delete mode 100644 docs/api/extensions/view.rst delete mode 100644 docs/api/extensions/xarray_assets.rst delete mode 100644 docs/api/item.rst delete mode 100644 docs/api/item_assets.rst delete mode 100644 docs/api/item_collection.rst delete mode 100644 docs/api/layout.rst delete mode 100644 docs/api/link.rst delete mode 100644 docs/api/media_type.rst delete mode 100644 docs/api/provider.rst delete mode 100644 docs/api/pystac.rst delete mode 100644 docs/api/rel_type.rst delete mode 100644 docs/api/serialization.rst delete mode 100644 docs/api/serialization/common_properties.rst delete mode 100644 docs/api/serialization/identify.rst delete mode 100644 docs/api/serialization/migrate.rst delete mode 100644 docs/api/stac_io.rst delete mode 100644 docs/api/stac_object.rst delete mode 100644 docs/api/summaries.rst delete mode 100644 docs/api/utils.rst delete mode 100644 docs/api/validation.rst delete mode 100644 docs/api/validation/local_validator.rst delete mode 100644 docs/api/validation/schema_uri_map.rst delete mode 100644 docs/api/validation/stac_validator.rst delete mode 100644 docs/api/version.rst delete mode 100644 docs/concepts.rst delete mode 100644 docs/conf.py delete mode 100644 docs/contributing.rst delete mode 100644 docs/example-catalog/catalog.json delete mode 100644 docs/example-catalog/landsat-8-l1/2018-05/LC80150322018141LGN00.json delete mode 100644 docs/example-catalog/landsat-8-l1/2018-06/LC80140332018166LGN00.json delete mode 100644 docs/example-catalog/landsat-8-l1/2018-06/LC80300332018166LGN00.json delete mode 100644 docs/example-catalog/landsat-8-l1/2018-07/LC80150332018189LGN00.json delete mode 100644 docs/example-catalog/landsat-8-l1/collection.json delete mode 100644 docs/index.rst delete mode 100644 docs/installation.rst delete mode 100644 docs/make.bat delete mode 100644 docs/quickstart.ipynb delete mode 100644 docs/tutorials.rst delete mode 100644 docs/tutorials/adding-new-and-custom-extensions.ipynb delete mode 100644 docs/tutorials/creating-a-landsat-stac.ipynb delete mode 100644 docs/tutorials/how-to-create-stac-catalogs.ipynb delete mode 100644 docs/tutorials/how-to-read-data-from-stac.ipynb delete mode 100644 docs/tutorials/pystac-introduction.ipynb delete mode 100644 docs/tutorials/pystac-spacenet-tutorial.ipynb delete mode 100644 pystac/asset.py delete mode 100644 pystac/catalog.py delete mode 100644 pystac/collection.py delete mode 100644 pystac/item.py delete mode 100644 pystac/item_collection.py delete mode 100644 pystac/link.py delete mode 100644 pystac/stac_object.py create mode 100644 src/pystac/__init__.py create mode 100644 src/pystac/asset.py rename {pystac => src/pystac}/cache.py (99%) create mode 100644 src/pystac/catalog.py create mode 100644 src/pystac/collection.py rename {pystac => src/pystac}/common_metadata.py (98%) create mode 100644 src/pystac/constants.py create mode 100644 src/pystac/deserialize.py rename {pystac => src/pystac}/errors.py (100%) rename {pystac => src/pystac}/extensions/__init__.py (100%) rename {pystac => src/pystac}/extensions/base.py (100%) rename {pystac => src/pystac}/extensions/classification.py (100%) rename {pystac => src/pystac}/extensions/datacube.py (100%) rename {pystac => src/pystac}/extensions/eo.py (100%) rename {pystac => src/pystac}/extensions/ext.py (100%) rename {pystac => src/pystac}/extensions/file.py (100%) rename {pystac => src/pystac}/extensions/grid.py (100%) rename {pystac => src/pystac}/extensions/hooks.py (100%) rename {pystac => src/pystac}/extensions/item_assets.py (100%) rename {pystac => src/pystac}/extensions/label.py (100%) rename {pystac => src/pystac}/extensions/mgrs.py (100%) rename {pystac => src/pystac}/extensions/mlm.py (100%) rename {pystac => src/pystac}/extensions/pointcloud.py (100%) rename {pystac => src/pystac}/extensions/projection.py (100%) rename {pystac => src/pystac}/extensions/raster.py (99%) rename {pystac => src/pystac}/extensions/render.py (100%) rename {pystac => src/pystac}/extensions/sar.py (100%) rename {pystac => src/pystac}/extensions/sat.py (100%) rename {pystac => src/pystac}/extensions/scientific.py (99%) rename {pystac => src/pystac}/extensions/storage.py (100%) rename {pystac => src/pystac}/extensions/table.py (100%) rename {pystac => src/pystac}/extensions/timestamps.py (100%) rename {pystac => src/pystac}/extensions/version.py (100%) rename {pystac => src/pystac}/extensions/view.py (100%) rename {pystac => src/pystac}/extensions/xarray_assets.py (100%) create mode 100644 src/pystac/geo_interface.py rename {pystac => src/pystac}/html/JSON.jinja2 (100%) rename {pystac => src/pystac}/html/Macros.jinja2 (100%) rename {pystac => src/pystac}/html/__init__.py (100%) rename {pystac => src/pystac}/html/jinja_env.py (100%) create mode 100644 src/pystac/io.py create mode 100644 src/pystac/item.py rename {pystac => src/pystac}/item_assets.py (100%) create mode 100644 src/pystac/item_collection.py create mode 100644 src/pystac/jsonschema.py rename {pystac => src/pystac}/layout.py (97%) create mode 100644 src/pystac/link.py rename {pystac => src/pystac}/media_type.py (100%) rename {pystac => src/pystac}/py.typed (100%) create mode 100644 src/pystac/reader.py rename {pystac => src/pystac}/rel_type.py (100%) create mode 100644 src/pystac/schemas/geojson/Feature.json create mode 100644 src/pystac/schemas/geojson/Geometry.json create mode 100644 src/pystac/schemas/stac/v1.0.0/basics.json create mode 100644 src/pystac/schemas/stac/v1.0.0/catalog.json create mode 100644 src/pystac/schemas/stac/v1.0.0/collection.json create mode 100644 src/pystac/schemas/stac/v1.0.0/datetime.json create mode 100644 src/pystac/schemas/stac/v1.0.0/instrument.json create mode 100644 src/pystac/schemas/stac/v1.0.0/item.json create mode 100644 src/pystac/schemas/stac/v1.0.0/licensing.json create mode 100644 src/pystac/schemas/stac/v1.0.0/provider.json rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/bands.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/basics.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/catalog.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/collection.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/common.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/data-values.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/datetime.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/instrument.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/item.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/licensing.json (100%) rename {pystac/validation/jsonschemas/stac-spec => src/pystac/schemas/stac}/v1.1.0/provider.json (100%) rename {pystac => src/pystac}/serialization/__init__.py (80%) rename {pystac => src/pystac}/serialization/common_properties.py (96%) rename {pystac => src/pystac}/serialization/identify.py (100%) rename {pystac => src/pystac}/serialization/migrate.py (100%) rename {pystac => src/pystac}/stac_io.py (99%) create mode 100644 src/pystac/stac_object.py rename {pystac => src/pystac}/summaries.py (99%) rename {pystac => src/pystac}/utils.py (97%) rename {pystac => src/pystac/v1}/__init__.py (100%) rename {pystac => src/pystac/v1}/client.py (100%) rename {pystac => src/pystac/v1}/provider.py (100%) rename {pystac => src/pystac/v1}/static/__init__.py (100%) rename {pystac => src/pystac/v1}/static/fields-normalized.json (100%) rename {pystac => src/pystac}/validation/__init__.py (98%) rename {pystac => src/pystac}/validation/jsonschemas/__init__.py (100%) rename {pystac => src/pystac}/validation/jsonschemas/geojson/Feature.json (100%) rename {pystac => src/pystac}/validation/jsonschemas/geojson/Geometry.json (100%) create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json create mode 100644 src/pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json rename {pystac => src/pystac}/validation/local_validator.py (100%) rename {pystac => src/pystac}/validation/schema_uri_map.py (100%) rename {pystac => src/pystac}/validation/stac_validator.py (100%) create mode 100644 src/pystac/validator.py rename {pystac => src/pystac}/version.py (100%) create mode 100644 src/pystac/writer.py create mode 100644 tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml create mode 100644 tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path12].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path15].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path19].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path3].yaml create mode 100644 tests/data-files/examples/v1.0.0/catalog.json create mode 100644 tests/data-files/examples/v1.0.0/collection-only/collection-with-schemas.json create mode 100644 tests/data-files/examples/v1.0.0/collection-only/collection.json create mode 100644 tests/data-files/examples/v1.0.0/collection.json create mode 100644 tests/data-files/examples/v1.0.0/collectionless-item.json create mode 100644 tests/data-files/examples/v1.0.0/core-item.json create mode 100644 tests/data-files/examples/v1.0.0/extended-item.json create mode 100644 tests/data-files/examples/v1.0.0/extensions-collection/collection.json create mode 100644 tests/data-files/examples/v1.0.0/extensions-collection/proj-example/proj-example.json create mode 100644 tests/data-files/examples/v1.0.0/simple-item.json rename tests/data-files/examples/{1.1.0 => v1.1.0}/README.md (100%) rename tests/data-files/examples/{1.1.0 => v1.1.0}/catalog.json (100%) create mode 100644 tests/data-files/examples/v1.1.0/collection-only/collection-with-schemas.json create mode 100644 tests/data-files/examples/v1.1.0/collection-only/collection.json rename tests/data-files/examples/{1.1.0 => v1.1.0}/collection.json (100%) rename tests/data-files/examples/{1.1.0 => v1.1.0}/collectionless-item.json (100%) rename tests/data-files/examples/{1.1.0 => v1.1.0}/core-item.json (100%) create mode 100644 tests/data-files/examples/v1.1.0/extended-item.json rename tests/data-files/examples/{1.1.0 => v1.1.0}/extensions-collection/collection.json (100%) rename tests/data-files/examples/{1.1.0 => v1.1.0}/extensions-collection/proj-example/proj-example.json (100%) rename tests/data-files/examples/{1.1.0 => v1.1.0}/simple-item.json (100%) create mode 100644 tests/test_examples.py create mode 100644 tests/test_obstore.py rename tests/{ => v1}/__init__.py (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_read_remote.yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_validate_all[cat0].yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_validate_all[cat1].yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_validate_all[cat3].yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_validate_all[cat4].yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_validate_all[cat5].yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/TestCatalog.test_validate_all[cat6].yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/test_validate_all_with_max_n.yaml (100%) rename tests/{ => v1}/cassettes/test_catalog/test_validate_all_with_recusive_off.yaml (100%) rename tests/{ => v1}/cassettes/test_item/test_non_hierarchical_relative_link.yaml (100%) rename tests/{ => v1}/cassettes/test_item/test_null_geometry.yaml (100%) rename tests/{ => v1}/cassettes/test_stac_io/test_proj_json_schema_is_readable.yaml (100%) rename tests/{ => v1}/cassettes/test_stac_io/test_retry_stac_io.yaml (100%) rename tests/{ => v1}/cassettes/test_stac_io/test_retry_stac_io_404.yaml (100%) rename tests/{ => v1}/cassettes/test_stac_io/test_urls_with_non_ascii_characters.yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog0].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog1].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog3].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog4].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog5].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog6].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog0].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog1].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog3].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog4].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog5].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog6].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog0].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog1].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog3].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog4].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog5].yaml (100%) rename tests/{ => v1}/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog6].yaml (100%) create mode 100644 tests/v1/conftest.py rename tests/{ => v1}/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/cbers-partial/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/invalid-catalog/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json (100%) rename tests/{ => v1}/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-1/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-1/country-2/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/data.geojson (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-2/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/665946/665946.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/a42435/a42435.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/353093/353093.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/42f235/42f235.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/kam/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/401175/401175.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/493701/493701.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/mon/f15272/f15272.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/nia/825a50/825a50.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/nia/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/ptn/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/06f252/06f252.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/076995/076995.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/425403/425403.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-4/znz/e52478/e52478.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/CBERS4/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-5/catalog.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/data.geojson (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json (100%) rename tests/{ => v1}/data-files/catalogs/test-case-6/catalog.json (100%) rename tests/{ => v1}/data-files/change_stac_version.py (91%) rename tests/{ => v1}/data-files/classification/classification_landsat_example.json (100%) rename tests/{ => v1}/data-files/classification/collection-item-assets-raster-bands.json (100%) rename tests/{ => v1}/data-files/collections/multi-extent.json (100%) rename tests/{ => v1}/data-files/collections/with-assets.json (100%) rename tests/{ => v1}/data-files/datacube/item.json (100%) rename tests/{ => v1}/data-files/eo/eo-collection.json (100%) rename tests/{ => v1}/data-files/eo/eo-landsat-example.json (100%) rename tests/{ => v1}/data-files/eo/eo-sentinel2-item.json (100%) rename tests/{ => v1}/data-files/eo/sample-bands-in-item-properties.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/catalog-spec/examples/catalog.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/catalog-spec/examples/summaries-s2.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/collection-spec/examples/landsat-collection.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/collection-spec/examples/landsat-item.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/collection-spec/examples/sentinel2.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/asset/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/checksum/examples/example-sentinel1.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/datacube/examples/example.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/datetime-range/examples/example-video.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/eo/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/catalog.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_item.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_source.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/pointcloud/examples/example-autzen.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/sar/examples/envisat.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/sar/examples/sentinel1.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/scientific/examples/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/scientific/examples/item.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/extensions/single-file-stac/examples/example-search.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/digitalglobe-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-full.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-minimal.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/landsat8-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/planet-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/sample-full.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/sample.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/sentinel-s2-l1c-collection.json (100%) rename tests/{ => v1}/data-files/examples/0.8.1/item-spec/examples/sentinel2-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/catalog-spec/examples/catalog.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/collection-spec/examples/landsat-item.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/collection-spec/examples/sentinel2.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/asset/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/checksum/examples/sentinel1.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/commons/examples/landsat-collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/commons/examples/landsat-item.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/datacube/examples/example-collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/datacube/examples/example-item.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/eo/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/catalog.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_item.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_source.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/pointcloud/examples/example-autzen.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/projection/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/sar/examples/envisat.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/sar/examples/sentinel1.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/sat/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/scientific/examples/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/scientific/examples/item.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/version/examples/collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/version/examples/item.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/extensions/view/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/datetimerange.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/digitalglobe-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-full.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-minimal.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/landsat8-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/planet-sample.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/sample-full.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/sample.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/sentinel-s2-l2a-collection.json (100%) rename tests/{ => v1}/data-files/examples/0.9.0/item-spec/examples/sentinel2-sample.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/catalog.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/collection-only/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/collectionless-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/core-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/extended-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/extensions-collection/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC1/simple-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-RC2/extended-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog-items.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/collection-spec/examples/landsat-collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/collection-spec/examples/sentinel2.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/eo/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/projection/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/sar/examples/envisat.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/sar/examples/sentinel1.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/sat/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/single-file-stac/examples/example-search.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/version/examples/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/version/examples/item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/extensions/view/examples/example-landsat8.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/planet-sample.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/sample.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0-beta.2/item-spec/examples/sentinel2-sample.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/README.md (100%) rename tests/{ => v1}/data-files/examples/1.0.0/catalog.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/collection-only/collection-with-schemas.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/collection-only/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/collectionless-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/core-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/example-sentinel2.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/extended-item.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/extensions-collection/collection.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/extensions-collection/proj-example/proj-example.json (100%) rename tests/{ => v1}/data-files/examples/1.0.0/simple-item.json (100%) create mode 100644 tests/v1/data-files/examples/1.1.0/README.md create mode 100644 tests/v1/data-files/examples/1.1.0/catalog.json rename tests/{ => v1}/data-files/examples/1.1.0/collection-only/collection-with-schemas.json (100%) rename tests/{ => v1}/data-files/examples/1.1.0/collection-only/collection.json (100%) create mode 100644 tests/v1/data-files/examples/1.1.0/collection.json create mode 100644 tests/v1/data-files/examples/1.1.0/collectionless-item.json create mode 100644 tests/v1/data-files/examples/1.1.0/core-item.json rename tests/{ => v1}/data-files/examples/1.1.0/extended-item.json (100%) create mode 100644 tests/v1/data-files/examples/1.1.0/extensions-collection/collection.json create mode 100644 tests/v1/data-files/examples/1.1.0/extensions-collection/proj-example/proj-example.json create mode 100644 tests/v1/data-files/examples/1.1.0/simple-item.json rename tests/{ => v1}/data-files/examples/example-info.csv (100%) rename tests/{ => v1}/data-files/examples/hand-0.8.1/010100/010100.json (100%) rename tests/{ => v1}/data-files/examples/hand-0.8.1/collection.json (100%) rename tests/{ => v1}/data-files/examples/hand-0.9.0/010100/010100.json (100%) rename tests/{ => v1}/data-files/examples/hand-0.9.0/collection.json (100%) rename tests/{ => v1}/data-files/file/catalog.json (100%) rename tests/{ => v1}/data-files/file/collection.json (100%) rename tests/{ => v1}/data-files/file/item.json (100%) rename tests/{ => v1}/data-files/geojson/sample.geojson (100%) rename tests/{ => v1}/data-files/get_examples.py (100%) rename tests/{ => v1}/data-files/grid/example-landsat.json (100%) rename tests/{ => v1}/data-files/grid/example-sentinel2.json (100%) rename tests/{ => v1}/data-files/invalid/shared-id/catalog.json (100%) rename tests/{ => v1}/data-files/invalid/shared-id/test/collection.json (100%) rename tests/{ => v1}/data-files/invalid/shared-id/test/test-item-1/test-item-1.json (100%) rename tests/{ => v1}/data-files/item-assets/example-landsat8.json (100%) rename tests/{ => v1}/data-files/item-collection/sample-item-collection.json (100%) rename tests/{ => v1}/data-files/item/sample-item-asset-properties.json (100%) rename tests/{ => v1}/data-files/item/sample-item-with-relative-extension-path.json (100%) rename tests/{ => v1}/data-files/item/sample-item.json (100%) rename tests/{ => v1}/data-files/mgrs/item.json (100%) rename tests/{ => v1}/data-files/mlm/collection.json (100%) rename tests/{ => v1}/data-files/mlm/item_basic.json (100%) rename tests/{ => v1}/data-files/pointcloud/example-laz-no-statistics.json (100%) rename tests/{ => v1}/data-files/pointcloud/example-laz.json (100%) rename tests/{ => v1}/data-files/projection/another-1.1.json (100%) rename tests/{ => v1}/data-files/projection/collection-with-summaries.json (100%) rename tests/{ => v1}/data-files/projection/example-landsat8.json (100%) rename tests/{ => v1}/data-files/projection/example-with-version-1.1.json (100%) rename tests/{ => v1}/data-files/projection/example-with-version-1.2.json (100%) rename tests/{ => v1}/data-files/projection/optional-epsg.json (100%) rename tests/{ => v1}/data-files/raster/gdalinfo.json (100%) rename tests/{ => v1}/data-files/raster/landsat-collection-example.json (100%) rename tests/{ => v1}/data-files/raster/raster-planet-example.json (100%) rename tests/{ => v1}/data-files/raster/raster-sentinel2-example.json (100%) rename tests/{ => v1}/data-files/render/collection.json (100%) rename tests/{ => v1}/data-files/render/item.json (100%) rename tests/{ => v1}/data-files/sar/sentinel-1.json (100%) rename tests/{ => v1}/data-files/sat/sentinel-1.json (100%) rename tests/{ => v1}/data-files/schemas/v1.0.0-projection.json (100%) rename tests/{ => v1}/data-files/scientific/collection.json (100%) rename tests/{ => v1}/data-files/scientific/item.json (100%) rename tests/{ => v1}/data-files/storage/collection-naip.json (100%) rename tests/{ => v1}/data-files/storage/item-naip.json (100%) rename tests/{ => v1}/data-files/summaries/fields_no_bands.json (100%) rename tests/{ => v1}/data-files/table/README.md (100%) rename tests/{ => v1}/data-files/table/collection-2.json (100%) rename tests/{ => v1}/data-files/table/collection.json (100%) rename tests/{ => v1}/data-files/table/item.json (100%) rename tests/{ => v1}/data-files/table/table-collection.json (100%) rename tests/{ => v1}/data-files/timestamps/example-landsat8.json (100%) rename tests/{ => v1}/data-files/version/collection.json (100%) rename tests/{ => v1}/data-files/version/item.json (100%) rename tests/{ => v1}/data-files/view/collection-with-summaries.json (100%) rename tests/{ => v1}/data-files/view/example-landsat8.json (100%) rename tests/{ => v1}/data-files/windows_hrefs/catalog.json (100%) rename tests/{ => v1}/data-files/windows_hrefs/test-collection/collection.json (100%) rename tests/{ => v1}/data-files/windows_hrefs/test-collection/test-item/test-asset.txt (100%) rename tests/{ => v1}/data-files/windows_hrefs/test-collection/test-item/test-item.json (100%) rename tests/{ => v1}/data-files/xarray-assets/collection.json (100%) rename tests/{ => v1}/data-files/xarray-assets/item.json (100%) rename tests/{ => v1}/extensions/__init__.py (100%) rename tests/{ => v1}/extensions/cassettes/test_classification/test_apply_bitfields.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_classification/test_apply_classes.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_classification/test_validate_classification.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_datacube/test_set_dimensions.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_datacube/test_set_variables.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_datacube/test_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_eo/test_asset_bands.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_eo/test_bands.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_eo/test_cloud_cover.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_eo/test_set_field[cloud_cover-7.8].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_eo/test_set_field[snow_cover-99].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_eo/test_validate_eo.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_migrate_from_v1_0_0.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_migrate_from_v2_0_0.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_asset[calibrations-local_path-different-file.xml].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_asset[measurement-header_size-8192].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-byte_order-little-endian].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-checksum-90e40210163700a8a6501eccd00b6d3b44ddaed0].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-size-1].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_link[about-byte_order-big-endian].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_link[about-checksum-90e40210163700a8a6501eccd00b6d3b44ddaedb].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_link[about-header_size-4092].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_link[about-local_path-a-path].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_set_field_on_link[about-size-129302].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_validate_catalog.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_validate_collection.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_file/test_validate_item.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_grid/test_attributes.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_grid/test_modify.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_mgrs/test_set_field[grid_square-ZA].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_mgrs/test_set_field[latitude_band-C].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_mgrs/test_set_field[utm_zone-59].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_mgrs/test_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_mlm/test_apply.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_mlm/test_validate_mlm.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_count.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_density.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_encoding.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_schemas.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_statistics.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_type.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_pointcloud/test_validate_pointcloud.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_bbox.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_centroid.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_epsg.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_geometry.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_partial_apply.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_projjson.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_shape.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_transform.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_validate_proj.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_projection/test_wkt2.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_raster/test_asset_bands.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_raster/test_validate_raster.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_render/test_collection_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_render/test_item_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sar/test_all.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sar/test_required.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_absolute_orbit.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_anx_datetime.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_both.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_clear_orbit_state.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_clear_relative_orbit.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_modify.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_no_args_fails.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_orbit_state.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_platform_international_designator.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_relative_orbit.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_sat/test_relative_orbit_no_negative.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_citation.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_citation.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_doi.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_publications.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_publications_one.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_remove_all_publications_one.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_none.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_some.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_remove_publication_forward.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_remove_publication_one.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_collection_remove_publication_reverse.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_doi.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_publications.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_publications_one.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_remove_all_publications_one.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_remove_all_publications_with_none.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_remove_all_publications_with_some.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_remove_publication_forward.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_remove_publication_one.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_scientific/test_remove_publication_reverse.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_storage/test_asset_platform.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_storage/test_asset_region.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_storage/test_asset_requester_pays.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_storage/test_asset_tier.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_storage/test_validate_storage.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_table/test_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_timestamps/test_expires.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_timestamps/test_published.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_timestamps/test_unpublished.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_timestamps/test_validate_timestamps.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_add_deprecated_version.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_add_not_deprecated_version.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_add_version.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_all_links.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_assets.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_catalog_add_version.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_catalog_validate_all.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_collection_add_version.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_collection_validate_all.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_latest.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_optional_version.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_predecessor.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_successor.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_version/test_version_in_properties.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_view/test_azimuth.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_view/test_incidence_angle.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_view/test_off_nadir.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_view/test_sun_azimuth.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_view/test_sun_elevation.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_view/test_validate_view.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_xarray_assets/test_collection_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_xarray_assets/test_item_validate.yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_xarray_assets/test_set_field[open_kwargs-value1].yaml (100%) rename tests/{ => v1}/extensions/cassettes/test_xarray_assets/test_set_field[storage_options-value0].yaml (100%) rename tests/{ => v1}/extensions/test_classification.py (99%) rename tests/{ => v1}/extensions/test_custom.py (100%) rename tests/{ => v1}/extensions/test_datacube.py (99%) rename tests/{ => v1}/extensions/test_eo.py (99%) rename tests/{ => v1}/extensions/test_ext.py (99%) rename tests/{ => v1}/extensions/test_file.py (99%) rename tests/{ => v1}/extensions/test_grid.py (98%) rename tests/{ => v1}/extensions/test_mgrs.py (99%) rename tests/{ => v1}/extensions/test_mlm.py (99%) rename tests/{ => v1}/extensions/test_pointcloud.py (99%) rename tests/{ => v1}/extensions/test_projection.py (99%) rename tests/{ => v1}/extensions/test_raster.py (98%) rename tests/{ => v1}/extensions/test_render.py (99%) rename tests/{ => v1}/extensions/test_sar.py (99%) rename tests/{ => v1}/extensions/test_sat.py (99%) rename tests/{ => v1}/extensions/test_scientific.py (99%) rename tests/{ => v1}/extensions/test_storage.py (99%) rename tests/{ => v1}/extensions/test_table.py (98%) rename tests/{ => v1}/extensions/test_timestamps.py (99%) rename tests/{ => v1}/extensions/test_version.py (99%) rename tests/{ => v1}/extensions/test_view.py (99%) rename tests/{ => v1}/extensions/test_xarray_assets.py (99%) rename tests/{ => v1}/html/__init__.py (100%) rename tests/{ => v1}/html/test_html.py (98%) rename tests/{ => v1}/posix_paths/__init__.py (100%) rename tests/{ => v1}/posix_paths/test_posix_paths.py (98%) rename tests/{ => v1}/serialization/__init__.py (100%) rename tests/{ => v1}/serialization/test_identify.py (98%) rename tests/{ => v1}/serialization/test_migrate.py (98%) rename tests/{ => v1}/test_asset.py (100%) rename tests/{ => v1}/test_cache.py (98%) create mode 100644 tests/v1/test_catalog.py create mode 100644 tests/v1/test_collection.py rename tests/{ => v1}/test_common_metadata.py (99%) create mode 100644 tests/v1/test_item.py rename tests/{ => v1}/test_item_assets.py (99%) rename tests/{ => v1}/test_item_collection.py (98%) rename tests/{ => v1}/test_layout.py (98%) rename tests/{ => v1}/test_link.py (99%) rename tests/{ => v1}/test_pystac_client.py (100%) rename tests/{ => v1}/test_stac_io.py (99%) rename tests/{ => v1}/test_summaries.py (98%) rename tests/{ => v1}/test_utils.py (98%) rename tests/{ => v1}/test_version.py (96%) rename tests/{ => v1}/test_writing.py (99%) rename tests/{ => v1}/utils/__init__.py (88%) rename tests/{ => v1}/utils/os_utils.py (100%) rename tests/{ => v1}/utils/stac_io_mock.py (100%) rename tests/{ => v1}/utils/test_cases.py (97%) rename tests/{ => v1}/validation/__init__.py (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all.yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_deprecated_dict_arg.yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case0].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case1].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case2].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case3].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case4].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case5].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case6].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_custom_validator.yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example0].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example100].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example101].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example102].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example103].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example104].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example105].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example106].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example107].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example108].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example109].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example10].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example110].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example111].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example112].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example113].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example114].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example115].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example116].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example117].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example118].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example119].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example11].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example120].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example121].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example122].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example123].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example124].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example12].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example13].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example14].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example15].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example16].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example17].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example18].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example19].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example1].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example20].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example21].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example22].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example23].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example24].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example25].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example26].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example27].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example28].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example29].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example2].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example30].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example31].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example32].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example33].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example34].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example35].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example36].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example37].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example38].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example39].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example3].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example40].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example41].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example42].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example43].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example44].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example45].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example46].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example47].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example48].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example49].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example4].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example50].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example51].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example52].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example53].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example54].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example55].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example56].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example57].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example58].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example59].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example5].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example60].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example61].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example62].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example63].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example64].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example65].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example66].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example67].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example68].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example69].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example6].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example70].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example71].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example72].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example73].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example74].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example75].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example76].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example77].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example78].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example79].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example7].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example80].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example81].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example82].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example83].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example84].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example85].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example86].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example87].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example88].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example89].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example8].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example90].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example91].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example92].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example93].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example94].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example95].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example96].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example97].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example98].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example99].yaml (100%) rename tests/{ => v1}/validation/cassettes/test_validate/TestValidate.test_validate_examples[example9].yaml (100%) rename tests/{ => v1}/validation/test_schema_uri_map.py (100%) rename tests/{ => v1}/validation/test_validate.py (98%) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 43c8262de..d4ec11f4b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -4,11 +4,6 @@ on: push: branches: - main - - "0.3" - - "0.4" - - "0.5" - - "1.0" - - "2.0" pull_request: merge_group: @@ -16,8 +11,6 @@ concurrency: group: ${{ github.ref }} cancel-in-progress: true -permissions: {} - jobs: test: name: test @@ -25,21 +18,18 @@ jobs: strategy: matrix: python-version: - - "3.10" - - "3.11" - "3.12" - "3.13" + - "3.14" os: - ubuntu-latest - windows-latest - macos-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 with: python-version: ${{ matrix.python-version }} - - name: Sync - run: uv sync --all-extras - name: Lint if: runner.os != 'Windows' run: uv run pre-commit run --all-files @@ -59,22 +49,16 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - - name: Install with dependencies - run: uv sync --all-extras + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 - name: Run coverage with orjson - run: uv run pytest tests --cov - - name: Uninstall orjson - run: uv pip uninstall orjson - - name: Run coverage without orjson, appending results - run: uv run pytest tests --cov --cov-append + run: uv run --all-extras pytest tests --cov - name: Prepare ./coverage.xml # Ignore the configured fail-under to ensure we upload the coverage report. We # will trigger a failure for coverage drops in a later job run: uv run coverage xml --fail-under 0 - name: Upload All coverage to Codecov - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de if: ${{ env.GITHUB_REPOSITORY }} == 'stac-utils/pystac' with: files: ./coverage.xml @@ -85,29 +69,17 @@ jobs: # coverage drops. run: uv run coverage report - without-orjson: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - - name: Sync - run: uv sync - - name: Uninstall orjson - run: uv pip uninstall orjson - - name: Run tests - run: uv run pytest tests - check-benchmarks: # This checks to make sure any API changes haven't broken any of the # benchmarks. It doesn't do any actual benchmarking, since (IMO) that's not # appropriate for CI on GitHub actions. runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 with: python-version: "3.10" - - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 with: enable-cache: true - name: Sync @@ -116,15 +88,3 @@ jobs: run: uv run asv machine --yes - name: Check benchmarks run: uv run asv run -a repeat=1 -a rounds=1 HEAD - - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - - name: Install pandoc - run: sudo apt-get install pandoc - - name: Sync - run: uv sync --group docs - - name: Check docs - run: uv run make -C docs html SPHINXOPTS="-W --keep-going -n" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5e3d28398..78b535365 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,8 +7,6 @@ on: - edited - reopened -permissions: {} - jobs: lint: name: Lint @@ -16,6 +14,6 @@ jobs: permissions: pull-requests: read steps: - - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 07f49e3d0..37d04b289 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -16,12 +16,12 @@ jobs: outputs: prs: ${{ steps.release-please.outputs.prs }} steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf id: generate-token with: app-id: ${{ vars.RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} - - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 + - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 id: release-please with: token: ${{ steps.generate-token.outputs.token }} @@ -32,17 +32,17 @@ jobs: needs: release-please if: ${{ needs.release-please.outputs.prs }} steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf id: generate-token with: app-id: ${{ vars.RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} - name: Checkout PR branch - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 with: token: ${{ steps.generate-token.outputs.token }} ref: ${{ fromJSON(needs.release-please.outputs.prs)[0].headBranchName }} - - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 - name: Run pull-static script run: uv run scripts/pull-static - name: Run pytest with rewrite mode diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 288403c54..60cffeedc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,8 +5,6 @@ on: types: - published -permissions: {} - jobs: release: name: release @@ -18,16 +16,16 @@ jobs: id-token: write if: ${{ github.repository }} == 'stac-utils/pystac' steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Set up Python 3.x - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 with: python-version: "3.x" - name: Install build run: | python -m pip install --upgrade pip pip install build - - name: Build + - name: Build run: python -m build - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e diff --git a/.python-version b/.python-version index 7c7a975f4..e4fba2183 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10 \ No newline at end of file +3.12 diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index f9846e160..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -livehtml: - sphinx-autobuild --watch ../pystac --host 0.0.0.0 ${SOURCEDIR} $(BUILDDIR)/html -d _build/doctrees - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/STAC-03.png b/docs/_static/STAC-03.png deleted file mode 100644 index 48783f210bb422c8735d0a6bb89cdf51f5785e3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118918 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 L_Fb{Cp7;L&QFkl{ diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 66d55e50a..000000000 --- a/docs/api.rst +++ /dev/null @@ -1,230 +0,0 @@ -API Reference -============= - -.. toctree:: - :hidden: - :maxdepth: 2 - :glob: - - api/pystac - api/* - -This API reference is auto-generated from the Python docstrings. The table of contents -on the left is organized by module. The sections below are organized based on concepts -and sections within the :stac-spec:`STAC Spec <>` and PySTAC itself. - -Base Structures & Classes -------------------------- - -These are the core Python classes representing entities within the STAC Spec. These -classes provide convenient methods for serializing and deserializing from JSON, -extracting properties, and creating relationships between entities. - -* :class:`pystac.Link`: Represents a :stac-spec:`Link Object - `. -* :class:`pystac.MediaType`: Provides common values used in the Link and Asset - ``"type"`` fields. -* :class:`pystac.RelType`: Provides common values used in the Link ``"rel"`` field. -* :class:`pystac.STACObject`: Base class implementing functionality common to - :class:`Catalog `, :class:`Collection ` and - :class:`Item `. - -Items ------ - -Representations of :stac-spec:`Items ` and related structures -like :stac-spec:`Asset Objects`. - -* :class:`pystac.Asset`: Represents an :stac-spec:`Asset Object - ` -* :class:`pystac.Item`: Represents an :stac-spec:`Item ` -* :class:`pystac.CommonMetadata`: A container for fields defined in the - :stac-spec:`Common Metadata ` section of the spec. - These fields are commonly found in STAC Item properties, but may be found elsewhere. - -Collections ------------ - -These are representations of :stac-spec:`Collections -` and related structures. - -* :class:`pystac.Collection`: Represents a :stac-spec:`Collection - `. -* :class:`pystac.Extent`: Represents an - :stac-spec:`Extent Object `, which - is composed of :class:`pystac.SpatialExtent` and :class:`pystac.TemporalExtent` - instances. -* :class:`pystac.Provider`: Represents a :stac-spec:`Provider Object - `. The - :class:`pystac.ProviderRole` enum provides common values used in the ``"roles"`` - field. -* :class:`pystac.Summaries`: Class for working with various types of - :stac-spec:`CollectionSummaries ` -* :class:`pystac.ItemCollection`: Represents a GeoJSON FeatureCollection in which all - Features are STAC Items. - -Catalogs --------- - -Representations of :stac-spec:`Catalogs ` and related -structures. - -* :class:`pystac.Catalog`: Represents a :stac-spec:`Catalog - `. -* :class:`pystac.CatalogType`: Enum representing the common types of Catalogs described - in the :stac-spec:`STAC Best Practices - ` - - -I/O ---- - -These classes are used to read and write files from disk or over the network, as well -as to serialize and deserialize STAC object to and from JSON. - -* :class:`pystac.StacIO`: Base class that can be inherited to provide custom I/O -* :class:`pystac.stac_io.DefaultStacIO`: The default :class:`pystac.StacIO` - implementation used throughout the library. - -Client ------- - -A convenience method for accessing `pystac-client `__ - -**Example:** - -.. code-block:: python - - from pystac.client import Client - - -Extensions ----------- - -PySTAC provides support for the following STAC Extensions: - -* :mod:`Datacube ` -* :mod:`Electro-Optical ` -* :mod:`File Info ` -* :mod:`Item Assets ` -* :mod:`MGRS ` -* :mod:`Point Cloud ` -* :mod:`Projection ` -* :mod:`Raster ` -* :mod:`SAR ` -* :mod:`Satellite ` -* :mod:`Scientific Citation ` -* :mod:`Table ` -* :mod:`Timestamps ` -* :mod:`Versioning Indicators ` -* :mod:`View Geometry ` -* :mod:`Xarray Assets ` - -The following classes are used internally to implement these extensions and may be used -to create custom implementations of STAC Extensions not supported by the library (see -:tutorial:`Adding New and Custom Extensions ` -for details): - -* :class:`pystac.extensions.base.SummariesExtension`: Base class for extending the - properties in :attr:`pystac.Collection.summaries` to include properties defined by a - STAC Extension. -* :class:`pystac.extensions.base.PropertiesExtension`: Abstract base class for - extending the properties of an :class:`~pystac.Item` to include properties defined - by a STAC Extension. -* :class:`pystac.extensions.base.ExtensionManagementMixin`: Abstract base class with - methods for adding and removing extensions from STAC Objects. -* :class:`pystac.extensions.hooks.ExtensionHooks`: Used to implement hooks when - extending a STAC Object. Primarily used to implement migrations from one extension - version to another. -* :class:`pystac.extensions.hooks.RegisteredExtensionHooks`: Used to register hooks - defined in :class:`~pystac.extensions.hooks.ExtensionHooks` instances to ensure they - are used in object deserialization. - - -Catalog Layout --------------- - -These classes are used to set the HREFs of a STAC according to some layout. -The templating functionality is also used when generating subcatalogs based on -a template. - -* :class:`pystac.layout.LayoutTemplate`: Represents a template that can be used for - deriving paths or other information based on properties of STAC objects supplied as a - template string. -* :class:`pystac.layout.BestPracticesLayoutStrategy`: Layout strategy that represents - the catalog layout described in the :stac-spec:`STAC Best Practices documentation - `. -* :class:`pystac.layout.APILayoutStrategy`: Layout strategy that represents - the catalog layout described in - the :stac-api-spec:`STAC API documentation `. -* :class:`pystac.layout.TemplateLayoutStrategy`: Layout strategy that can take strings - to be supplied to a :class:`~pystac.layout.LayoutTemplate` to derive paths. -* :class:`pystac.layout.CustomLayoutStrategy`: Layout strategy that allows users to - supply functions to dictate stac object paths. - -Errors ------- - -The following exceptions may be raised internally by the library. - -* :class:`pystac.STACError`: Generic STAC-related error -* :class:`pystac.STACTypeError`: Raised when a representation of a STAC entity is - encountered that is not correct for the context -* :class:`pystac.DuplicateObjectKeyError`: Raised when deserializing a JSON object - containing a duplicate key. -* :class:`pystac.ExtensionAlreadyExistsError`: Raised when deserializing a JSON object - containing a duplicate key. -* :class:`pystac.ExtensionTypeError`: Raised when an extension is used against an - object to which that the extension does not apply to. -* :class:`pystac.ExtensionNotImplemented`: Raised on an attempt to extend a STAC object - that does not implement the given extension. -* :class:`pystac.RequiredPropertyMissing`: Raised when a required value is expected to - be present but is missing or ``None``. -* :class:`pystac.STACValidationError`: Raised by validation calls if the STAC JSON is - invalid. -* :class:`pystac.TemplateError`: Raised when an error occurs while converting a - template string into data for :class:`~pystac.layout.LayoutTemplate`. - -Serialization -------------- - -The ``pystac.serialization`` sub-package contains tools used internally by PySTAC to -identify, serialize, and migrate STAC objects: - -* :mod:`pystac.serialization`: Tools for identifying and migrating STAC objects - - -Validation ----------- - -.. note:: - - The tools described here require that you install PySTAC with the ``validation`` - extra (see the documentation on :ref:`installing dependencies - ` for details). - -PySTAC includes a ``pystac.validation`` package for validating STAC objects, including -from PySTAC objects and directly from JSON. - -* :class:`pystac.validation.stac_validator.STACValidator`: Abstract base class defining - methods for validating STAC JSON. Implementations define methods for validating core - objects and extension. -* :class:`pystac.validation.stac_validator.JsonSchemaSTACValidator`: The default - :class:`~pystac.validation.stac_validator.STACValidator` implementation used by - PySTAC. Uses JSON schemas read from URIs provided by a - :class:`~pystac.validation.schema_uri_map.SchemaUriMap`, to validate STAC objects. -* :class:`pystac.validation.schema_uri_map.SchemaUriMap`: Defines methods for mapping - STAC versions, object types and extension ids to schema URIs. A default - implementation is included that uses known locations; however users can provide their - own schema URI maps in a - :class:`~pystac.validation.stac_validator.JsonSchemaSTACValidator` to modify the URIs - used. -* :class:`pystac.validation.schema_uri_map.DefaultSchemaUriMap`: The default - :class:`~pystac.validation.schema_uri_map.SchemaUriMap` used by PySTAC. - -Internal Classes ------------------------ - -These classes are used internally by PySTAC for caching. - -* :class:`pystac.cache.ResolvedObjectCache` diff --git a/docs/api/asset.rst b/docs/api/asset.rst deleted file mode 100644 index 062108ead..000000000 --- a/docs/api/asset.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.asset -============ - -.. autoclass:: pystac.asset.Asset - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.asset - :members: - :undoc-members: - :exclude-members: Asset diff --git a/docs/api/cache.rst b/docs/api/cache.rst deleted file mode 100644 index cc0d2f2ab..000000000 --- a/docs/api/cache.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.cache -============ - -.. automodule:: pystac.cache - :members: - :undoc-members: diff --git a/docs/api/catalog.rst b/docs/api/catalog.rst deleted file mode 100644 index 99bfd1290..000000000 --- a/docs/api/catalog.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.catalog -============== - -.. autoclass:: pystac.catalog.Catalog - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.catalog - :members: - :undoc-members: - :exclude-members: Catalog diff --git a/docs/api/collection.rst b/docs/api/collection.rst deleted file mode 100644 index de435c23a..000000000 --- a/docs/api/collection.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.collection -================= - -.. autoclass:: pystac.collection.Collection - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.collection - :members: - :undoc-members: - :exclude-members: Collection diff --git a/docs/api/common_metadata.rst b/docs/api/common_metadata.rst deleted file mode 100644 index f3eb46a57..000000000 --- a/docs/api/common_metadata.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.common_metadata -====================== - -.. automodule:: pystac.common_metadata - :members: - :undoc-members: - :noindex: diff --git a/docs/api/errors.rst b/docs/api/errors.rst deleted file mode 100644 index 62f1ea18e..000000000 --- a/docs/api/errors.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.errors -============= - -.. automodule:: pystac.errors - :members: - :undoc-members: - :noindex: diff --git a/docs/api/extensions.rst b/docs/api/extensions.rst deleted file mode 100644 index ce37d3215..000000000 --- a/docs/api/extensions.rst +++ /dev/null @@ -1,38 +0,0 @@ -pystac.extensions -================= - -.. toctree:: - :hidden: - :maxdepth: 2 - :glob: - - extensions/* - - -.. currentmodule:: pystac.extensions - -.. autosummary:: - - classification.ClassificationExtension - datacube.DatacubeExtension - eo.EOExtension - file.FileExtension - grid.GridExtension - item_assets.ItemAssetsExtension - mgrs.MgrsExtension - mlm.MLMExtension - mlm.AssetGeneralMLMExtension - mlm.AssetDetailedMLMExtension - pointcloud.PointcloudExtension - projection.ProjectionExtension - raster.RasterExtension - render.RenderExtension - sar.SarExtension - sat.SatExtension - scientific.ScientificExtension - storage.StorageExtension - table.TableExtension - timestamps.TimestampsExtension - version.VersionExtension - view.ViewExtension - xarray_assets.XarrayAssetsExtension diff --git a/docs/api/extensions/base.rst b/docs/api/extensions/base.rst deleted file mode 100644 index 2085f3f94..000000000 --- a/docs/api/extensions/base.rst +++ /dev/null @@ -1,6 +0,0 @@ -pytac.extensions.base -===================== - -.. automodule:: pystac.extensions.base - :members: - :undoc-members: diff --git a/docs/api/extensions/classification.rst b/docs/api/extensions/classification.rst deleted file mode 100644 index 5b6e79aec..000000000 --- a/docs/api/extensions/classification.rst +++ /dev/null @@ -1,6 +0,0 @@ -pytac.extensions.classification -=============================== - -.. automodule:: pystac.extensions.classification - :members: - :undoc-members: diff --git a/docs/api/extensions/datacube.rst b/docs/api/extensions/datacube.rst deleted file mode 100644 index 24204dd26..000000000 --- a/docs/api/extensions/datacube.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.datacube -========================== - -.. automodule:: pystac.extensions.datacube - :members: - :undoc-members: diff --git a/docs/api/extensions/eo.rst b/docs/api/extensions/eo.rst deleted file mode 100644 index c46515d09..000000000 --- a/docs/api/extensions/eo.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.eo -==================== - -.. automodule:: pystac.extensions.eo - :members: - :undoc-members: diff --git a/docs/api/extensions/ext.rst b/docs/api/extensions/ext.rst deleted file mode 100644 index 23708b444..000000000 --- a/docs/api/extensions/ext.rst +++ /dev/null @@ -1,7 +0,0 @@ -pytac.extensions.ext -==================== - -.. automodule:: pystac.extensions.ext - :members: - :inherited-members: - :undoc-members: diff --git a/docs/api/extensions/file.rst b/docs/api/extensions/file.rst deleted file mode 100644 index 2e33794e1..000000000 --- a/docs/api/extensions/file.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.file -====================== - -.. automodule:: pystac.extensions.file - :members: - :undoc-members: diff --git a/docs/api/extensions/grid.rst b/docs/api/extensions/grid.rst deleted file mode 100644 index b8f31ca6e..000000000 --- a/docs/api/extensions/grid.rst +++ /dev/null @@ -1,6 +0,0 @@ -pytac.extensions.grid -===================== - -.. automodule:: pystac.extensions.grid - :members: - :undoc-members: diff --git a/docs/api/extensions/hooks.rst b/docs/api/extensions/hooks.rst deleted file mode 100644 index 949847bb4..000000000 --- a/docs/api/extensions/hooks.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.hooks -======================= - -.. automodule:: pystac.extensions.hooks - :members: - :undoc-members: diff --git a/docs/api/extensions/item_assets.rst b/docs/api/extensions/item_assets.rst deleted file mode 100644 index c46ce768d..000000000 --- a/docs/api/extensions/item_assets.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.item\_assets -============================== - -.. automodule:: pystac.extensions.item_assets - :members: - :undoc-members: diff --git a/docs/api/extensions/mgrs.rst b/docs/api/extensions/mgrs.rst deleted file mode 100644 index 5bcb6b389..000000000 --- a/docs/api/extensions/mgrs.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.mgrs -============================ - -.. automodule:: pystac.extensions.mgrs - :members: - :undoc-members: diff --git a/docs/api/extensions/mlm.rst b/docs/api/extensions/mlm.rst deleted file mode 100644 index 8a4e3531d..000000000 --- a/docs/api/extensions/mlm.rst +++ /dev/null @@ -1,8 +0,0 @@ -pystac.extensions.mlm -============================ - -.. automodule:: pystac.extensions.mlm - :members: - :inherited-members: StringEnum, Generic, PropertiesExtension, ExtensionManagementMixin - :undoc-members: - diff --git a/docs/api/extensions/pointcloud.rst b/docs/api/extensions/pointcloud.rst deleted file mode 100644 index 24dde5031..000000000 --- a/docs/api/extensions/pointcloud.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.pointcloud -============================ - -.. automodule:: pystac.extensions.pointcloud - :members: - :undoc-members: diff --git a/docs/api/extensions/projection.rst b/docs/api/extensions/projection.rst deleted file mode 100644 index 65f079f8d..000000000 --- a/docs/api/extensions/projection.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.projection -============================ - -.. automodule:: pystac.extensions.projection - :members: - :undoc-members: diff --git a/docs/api/extensions/raster.rst b/docs/api/extensions/raster.rst deleted file mode 100644 index 7f67d824d..000000000 --- a/docs/api/extensions/raster.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.raster -======================== - -.. automodule:: pystac.extensions.raster - :members: - :undoc-members: diff --git a/docs/api/extensions/render.rst b/docs/api/extensions/render.rst deleted file mode 100644 index b7b13ba1e..000000000 --- a/docs/api/extensions/render.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.render -======================== - -.. automodule:: pystac.extensions.render - :members: - :undoc-members: diff --git a/docs/api/extensions/sar.rst b/docs/api/extensions/sar.rst deleted file mode 100644 index 9f48cc5c8..000000000 --- a/docs/api/extensions/sar.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.sar -===================== - -.. automodule:: pystac.extensions.sar - :members: - :undoc-members: diff --git a/docs/api/extensions/sat.rst b/docs/api/extensions/sat.rst deleted file mode 100644 index 793a47862..000000000 --- a/docs/api/extensions/sat.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.sat -===================== - -.. automodule:: pystac.extensions.sat - :members: - :undoc-members: diff --git a/docs/api/extensions/scientific.rst b/docs/api/extensions/scientific.rst deleted file mode 100644 index 83d06361e..000000000 --- a/docs/api/extensions/scientific.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.scientific -============================ - -.. automodule:: pystac.extensions.scientific - :members: - :undoc-members: diff --git a/docs/api/extensions/storage.rst b/docs/api/extensions/storage.rst deleted file mode 100644 index 420e5dfb1..000000000 --- a/docs/api/extensions/storage.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.storage -============================ - -.. automodule:: pystac.extensions.storage - :members: - :undoc-members: diff --git a/docs/api/extensions/table.rst b/docs/api/extensions/table.rst deleted file mode 100644 index 80abd7dc1..000000000 --- a/docs/api/extensions/table.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.table -======================= - -.. automodule:: pystac.extensions.table - :members: - :undoc-members: diff --git a/docs/api/extensions/timestamps.rst b/docs/api/extensions/timestamps.rst deleted file mode 100644 index da5758255..000000000 --- a/docs/api/extensions/timestamps.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.timestamps -============================ - -.. automodule:: pystac.extensions.timestamps - :members: - :undoc-members: diff --git a/docs/api/extensions/version.rst b/docs/api/extensions/version.rst deleted file mode 100644 index 4c606ce75..000000000 --- a/docs/api/extensions/version.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.version -========================= - -.. automodule:: pystac.extensions.version - :members: - :undoc-members: diff --git a/docs/api/extensions/view.rst b/docs/api/extensions/view.rst deleted file mode 100644 index 1b395df28..000000000 --- a/docs/api/extensions/view.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.view -====================== - -.. automodule:: pystac.extensions.view - :members: - :undoc-members: diff --git a/docs/api/extensions/xarray_assets.rst b/docs/api/extensions/xarray_assets.rst deleted file mode 100644 index 494093068..000000000 --- a/docs/api/extensions/xarray_assets.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.extensions.xarray_assets -=============================== - -.. automodule:: pystac.extensions.xarray_assets - :members: - :undoc-members: diff --git a/docs/api/item.rst b/docs/api/item.rst deleted file mode 100644 index aedf84bcd..000000000 --- a/docs/api/item.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.item -=========== - -.. autoclass:: pystac.item.Item - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.item - :members: - :undoc-members: - :exclude-members: Item diff --git a/docs/api/item_assets.rst b/docs/api/item_assets.rst deleted file mode 100644 index 501c56420..000000000 --- a/docs/api/item_assets.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.item_assets -================== - -.. automodule:: pystac.item_assets - :members: - :undoc-members: - :noindex: diff --git a/docs/api/item_collection.rst b/docs/api/item_collection.rst deleted file mode 100644 index 9b01f7afa..000000000 --- a/docs/api/item_collection.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.item_collection -====================== - -.. autoclass:: pystac.item_collection.ItemCollection - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.item_collection - :members: - :undoc-members: - :exclude-members: ItemCollection diff --git a/docs/api/layout.rst b/docs/api/layout.rst deleted file mode 100644 index 08853cf26..000000000 --- a/docs/api/layout.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.layout -============= - -.. automodule:: pystac.layout - :members: - :undoc-members: diff --git a/docs/api/link.rst b/docs/api/link.rst deleted file mode 100644 index 9756ef0b6..000000000 --- a/docs/api/link.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.link -=========== - -.. autoclass:: pystac.link.Link - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.link - :members: - :undoc-members: - :exclude-members: Link diff --git a/docs/api/media_type.rst b/docs/api/media_type.rst deleted file mode 100644 index 174f42d0c..000000000 --- a/docs/api/media_type.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.media_type -================= - -.. automodule:: pystac.media_type - :members: - :undoc-members: - :noindex: diff --git a/docs/api/provider.rst b/docs/api/provider.rst deleted file mode 100644 index b1f090e7d..000000000 --- a/docs/api/provider.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.provider -=============== - -.. automodule:: pystac.provider - :members: - :undoc-members: - :noindex: diff --git a/docs/api/pystac.rst b/docs/api/pystac.rst deleted file mode 100644 index e16c4692d..000000000 --- a/docs/api/pystac.rst +++ /dev/null @@ -1,227 +0,0 @@ -pystac ------- - -.. automodule:: pystac - :members: read_file, write_file, read_dict, set_stac_version, get_stac_version - - .. autosummary:: - STACObject - Catalog - Collection - Extent - SpatialExtent - TemporalExtent - Provider - Summaries - Item - Asset - ItemAssetDefinition - CommonMetadata - ItemCollection - Link - StacIO - read_file - write_file - read_dict - set_stac_version - get_stac_version - - -STACObject ----------- - -.. autoclass:: pystac.STACObject - :members: - :inherited-members: - :undoc-members: - -.. autoclass:: pystac.STACObjectType - :members: - :undoc-members: - -Catalog -------- - -.. autoclass:: pystac.Catalog - :members: - :inherited-members: - :undoc-members: - -CatalogType ------------ - -.. autoclass:: pystac.CatalogType - :members: - :inherited-members: - :undoc-members: - -Collection ----------- - -.. autoclass:: pystac.Collection - :members: - :inherited-members: - :undoc-members: - -Extent ------- - -.. autoclass:: pystac.Extent - :members: - :undoc-members: - -SpatialExtent -------------- - -.. autoclass:: pystac.SpatialExtent - :members: - :undoc-members: - -TemporalExtent --------------- - -.. autoclass:: pystac.TemporalExtent - :members: - :undoc-members: - -ProviderRole ------------- - -.. autoclass:: pystac.ProviderRole - :members: - :undoc-members: - -Provider --------- - -.. autoclass:: pystac.Provider - :members: - :undoc-members: - -Summaries ---------- - -.. autoclass:: pystac.Summaries - :members: - :undoc-members: - -Item ----- - -.. autoclass:: pystac.Item - :members: - :inherited-members: - :undoc-members: - -Asset ------ - -.. autoclass:: pystac.Asset - :members: - :undoc-members: - -ItemAssetDefinition -------------------- - -.. autoclass:: pystac.ItemAssetDefinition - :members: - :undoc-members: - - -CommonMetadata --------------- - -.. autoclass:: pystac.CommonMetadata - :members: - :undoc-members: - -ItemCollection --------------- - -.. autoclass:: pystac.ItemCollection - :members: - :inherited-members: - :undoc-members: - -Link ----- - -.. autoclass:: pystac.Link - :members: - :inherited-members: - :undoc-members: - -MediaType ---------- - -.. autoclass:: pystac.MediaType - :members: - :undoc-members: - -RelType -------- - -.. autoclass:: pystac.RelType - :members: - :undoc-members: - -StacIO ------- - -.. autoclass:: pystac.StacIO - :members: - :undoc-members: - -Errors ------- - -STACError -~~~~~~~~~ - -.. autoclass:: pystac.STACError - -STACTypeError -~~~~~~~~~~~~~ - -.. autoclass:: pystac.STACTypeError - -DuplicateObjectKeyError -~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.DuplicateObjectKeyError - -ExtensionAlreadyExistsError -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.ExtensionAlreadyExistsError - -ExtensionTypeError -~~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.ExtensionTypeError - -ExtensionNotImplemented -~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.ExtensionNotImplemented - -RequiredPropertyMissing -~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.RequiredPropertyMissing - -STACValidationError -~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.STACValidationError - -TemplateError -~~~~~~~~~~~~~ - -.. autoclass:: pystac.TemplateError - - -DeprecatedWarning -~~~~~~~~~~~~~~~~~ - -.. autoclass:: pystac.DeprecatedWarning diff --git a/docs/api/rel_type.rst b/docs/api/rel_type.rst deleted file mode 100644 index a13c99f86..000000000 --- a/docs/api/rel_type.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.rel_type -=============== - -.. automodule:: pystac.rel_type - :members: - :undoc-members: - :noindex: diff --git a/docs/api/serialization.rst b/docs/api/serialization.rst deleted file mode 100644 index 4a21bea46..000000000 --- a/docs/api/serialization.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.serialization -==================== - -.. toctree:: - :hidden: - :maxdepth: 2 - :glob: - - serialization/* - -.. automodule:: pystac.serialization - :members: diff --git a/docs/api/serialization/common_properties.rst b/docs/api/serialization/common_properties.rst deleted file mode 100644 index 5666ca965..000000000 --- a/docs/api/serialization/common_properties.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.serialization.common\_properties -======================================= - -.. automodule:: pystac.serialization.common_properties - :members: - :undoc-members: diff --git a/docs/api/serialization/identify.rst b/docs/api/serialization/identify.rst deleted file mode 100644 index a54e38f62..000000000 --- a/docs/api/serialization/identify.rst +++ /dev/null @@ -1,20 +0,0 @@ -pystac.serialization.identify -============================= - - -.. automodule:: pystac.serialization.identify - :members: STACVersionRange, identify_stac_object, identify_stac_object_type - :undoc-members: - :noindex: - - -.. autoclass:: pystac.serialization.identify.STACVersionRange - :members: - :undoc-members: - :noindex: - - -.. automodule:: pystac.serialization.identify - :members: - :undoc-members: - :exclude-members: STACVersionRange, identify_stac_object, identify_stac_object_type diff --git a/docs/api/serialization/migrate.rst b/docs/api/serialization/migrate.rst deleted file mode 100644 index df03f05ec..000000000 --- a/docs/api/serialization/migrate.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.serialization.migrate -============================ - -.. automodule:: pystac.serialization.migrate - :members: - :undoc-members: diff --git a/docs/api/stac_io.rst b/docs/api/stac_io.rst deleted file mode 100644 index 611d276f0..000000000 --- a/docs/api/stac_io.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.stac_io -============== - -.. automodule:: pystac.stac_io - :members: - :undoc-members: diff --git a/docs/api/stac_object.rst b/docs/api/stac_object.rst deleted file mode 100644 index a5a37fcec..000000000 --- a/docs/api/stac_object.rst +++ /dev/null @@ -1,19 +0,0 @@ -pystac.stac_object -================== - -.. autoclass:: pystac.stac_object.STACObject - :members: - :undoc-members: - :noindex: - -.. autoclass:: pystac.stac_object.STACObjectType - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.stac_object - :members: - :undoc-members: - :exclude-members: STACObject, STACObjectType - -.. autoclass:: pystac.stac_object.S diff --git a/docs/api/summaries.rst b/docs/api/summaries.rst deleted file mode 100644 index e7a7191d8..000000000 --- a/docs/api/summaries.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.summaries -================ - -.. autoclass:: pystac.summaries.Summaries - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.summaries - :members: - :undoc-members: - :exclude-members: Summaries diff --git a/docs/api/utils.rst b/docs/api/utils.rst deleted file mode 100644 index 83f148ccf..000000000 --- a/docs/api/utils.rst +++ /dev/null @@ -1,10 +0,0 @@ -pystac.utils -============ - -.. automodule:: pystac.utils - :members: - :undoc-members: - -.. autoclass:: pystac.utils.T - -.. autoclass:: pystac.utils.U diff --git a/docs/api/validation.rst b/docs/api/validation.rst deleted file mode 100644 index 67f0ff09b..000000000 --- a/docs/api/validation.rst +++ /dev/null @@ -1,13 +0,0 @@ -pystac.validation -================= - -.. toctree:: - :hidden: - :maxdepth: 2 - :glob: - - validation/* - -.. automodule:: pystac.validation - :members: - :undoc-members: diff --git a/docs/api/validation/local_validator.rst b/docs/api/validation/local_validator.rst deleted file mode 100644 index 09096e349..000000000 --- a/docs/api/validation/local_validator.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.validation.local\_validator -================================== - -.. automodule:: pystac.validation.local_validator - :members: - :undoc-members: diff --git a/docs/api/validation/schema_uri_map.rst b/docs/api/validation/schema_uri_map.rst deleted file mode 100644 index 231bd2f28..000000000 --- a/docs/api/validation/schema_uri_map.rst +++ /dev/null @@ -1,6 +0,0 @@ -pystac.validation.schema\_uri\_map -================================== - -.. automodule:: pystac.validation.schema_uri_map - :members: - :undoc-members: diff --git a/docs/api/validation/stac_validator.rst b/docs/api/validation/stac_validator.rst deleted file mode 100644 index 651a5c669..000000000 --- a/docs/api/validation/stac_validator.rst +++ /dev/null @@ -1,12 +0,0 @@ -pystac.validation.stac\_validator -================================= - -.. autoclass:: pystac.validation.stac_validator.JsonSchemaSTACValidator - :members: - :undoc-members: - :noindex: - -.. automodule:: pystac.validation.stac_validator - :members: - :undoc-members: - :exclude-members: JsonSchemaSTACValidator diff --git a/docs/api/version.rst b/docs/api/version.rst deleted file mode 100644 index ea856258f..000000000 --- a/docs/api/version.rst +++ /dev/null @@ -1,7 +0,0 @@ -pystac.version -============== - -.. automodule:: pystac.version - :members: - :undoc-members: - :noindex: diff --git a/docs/concepts.rst b/docs/concepts.rst deleted file mode 100644 index 2c320f1e1..000000000 --- a/docs/concepts.rst +++ /dev/null @@ -1,1004 +0,0 @@ -Concepts -######## - -This page will give an overview of some important concepts to understand when working -with PySTAC. If you want to check code examples, see the :ref:`tutorials`. - -.. _stac_version_support: - -STAC Spec Version Support -========================= - -The latest version of PySTAC supports STAC Spec |stac_version| and will automatically -update any catalogs to this version. To work with older versions of the STAC Spec, -please use an older version of PySTAC: - -================= ============== -STAC Spec Version PySTAC Version -================= ============== ->=1.0 Latest -0.9 0.4.* -0.8 0.3.* -<0.8 *Not supported* -================= ============== - -Reading STACs -============= - -PySTAC can read STAC data from JSON. Generally users read in the root catalog, and then -use the python objects to crawl through the data. Once you read in the root of the STAC, -you can work with the STAC in memory. - -.. code-block:: python - - from pystac import Catalog - - catalog = Catalog.from_file('/some/example/catalog.json') - - for root, catalogs, items in catalog.walk(): - # Do interesting things with the STAC data. - -To see how to hook into PySTAC for reading from alternate URIs such as cloud object -storage, see :ref:`using stac_io`. - -Writing STACs -============= - -While working with STACs in-memory don't require setting file paths, in order to save a -STAC, you'll need to give each STAC object a ``self`` link that describes the location -of where it should be saved to. Luckily, PySTAC makes it easy to create a STAC catalog -with a :stac-spec:`canonical layout ` and with the -links that follow the :stac-spec:`best practices `. You -simply call ``normalize_hrefs`` with the root directory of where the STAC will be saved, -and then call ``save`` with the type of catalog (described in the :ref:`catalog types` -section) that matches your use case. - -.. code-block:: python - - from pystac import (Catalog, CatalogType) - - catalog = Catalog.from_file('/some/example/catalog.json') - catalog.normalize_hrefs('/some/copy/') - catalog.save(catalog_type=CatalogType.SELF_CONTAINED) - - copycat = Catalog.from_file('/some/copy/catalog.json') - - -Normalizing HREFs ------------------ - -The ``normalize_hrefs`` call sets HREFs for all the links in the STAC according to the -Catalog, Collection and Items, all based off of the root URI that is passed in: - -.. code-block:: python - - catalog.normalize_hrefs('/some/location') - catalog.save(catalog_type=CatalogType.SELF_CONTAINED) - -This will lay out the HREFs of the STAC according to the :stac-spec:`best practices -document `. - -Layouts -~~~~~~~ - -PySTAC provides a few different strategies for laying out the HREFs of a STAC. -To use them you can pass in a strategy when instantiating a catalog or when -calling `normalize_hrefs`. - -Using templates -''''''''''''''' - -You can utilize template strings to determine the file paths of HREFs set on Catalogs, -Collection or Items. These templates use python format strings, which can name -the property or attribute of the item you want to use for replacing the template -variable. For example: - -.. code-block:: python - - from pystac.layout import TemplateLayoutStrategy - - strategy = TemplateLayoutStrategy(item_template="${collection}/${year}/${month}") - catalog.normalize_hrefs('/some/location', strategy=strategy) - catalog.save(catalog_type=CatalogType.SELF_CONTAINED) - -The above code will save items in subfolders based on the collection ID, year and month -of it's datetime (or start_datetime if a date range is defined and no datetime is -defined). Note that the forward slash (``/``) should be used as path separator in the -template string regardless of the system path separator (thus both in POSIX-compliant -and Windows environments). - -You can use dot notation to specify attributes of objects or keys in dictionaries for -template variables. PySTAC will look at the object, it's ``properties`` and its -``extra_fields`` for property names or dictionary keys. Some special cases, like -``year``, ``month``, ``day`` and ``date`` exist for datetime on Items, as well as -``collection`` for Item's Collection's ID. - -See the documentation on :class:`~pystac.layout.LayoutTemplate` for more documentation -on how layout templates work. - -Using custom functions -'''''''''''''''''''''' - -If you want to build your own strategy, you can subclass ``HrefLayoutStrategy`` or use -:class:`~pystac.layout.CustomLayoutStrategy` to provide functions that work with -Catalogs, Collections or Items. Similar to the templating strategy, you can provide a -fallback strategy (which defaults to -:class:`~pystac.layout.BestPracticesLayoutStrategy`) for any stac object type that you -don't supply a function for. - - -Set a default catalog layout strategy -''''''''''''''''''''''''''''''''''''' - -Instead of fixing the HREFs of child objects retrospectively using `normalize_hrefs`, -you can also define a default strategy for a catalog. When instantiating a catalog, -pass in a custom strategy and base href. Consequently, the HREFs of all child -objects and items added to the catalog tree will be set correctly using that strategy. - - -.. code-block:: python - - from pystac import Catalog, Collection, Item - - catalog = Catalog(..., - href="/some/location/catalog.json", - strategy=custom_strategy) - collection = Collection(...) - item = Item(...) - catalog.add_child(collection) - collection.add_item(item) - catalog.save() - - -.. _catalog types: - -Catalog Types -------------- - -The STAC :stac-spec:`best practices document ` lays out different -catalog types, and how their links should be formatted. A brief description is below, -but check out the document for the official take on these types: - -The catalog types will also dictate the asset HREF formats. Asset HREFs in any catalog -type can be relative or absolute may be absolute depending on their location; see the -section on :ref:`rel vs abs asset` below. - - -Self-Contained Catalogs -~~~~~~~~~~~~~~~~~~~~~~~ - -A self-contained catalog (indicated by ``catalog_type=CatalogType.SELF_CONTAINED``) -applies to STACs that do not have a long term location, and can be moved around. These -STACs are useful for copying data to and from locations, without having to change any -link metadata. - -A self-contained catalog has two important properties: - -- It contains only relative links -- It contains **no** self links. - -For a catalog that is the most easy to copy around, it's recommended that item assets -use relative links, and reside in the same directory as the item's STAC metadata file. - -Relative Published Catalogs -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A relative published catalog (indicated by -``catalog_type=CatalogType.RELATIVE_PUBLISHED``) is one that is tied at it's root to a -specific location, but otherwise contains relative links. This is designed so that a -self-contained catalog can be 'published' online by just adding one field (the self -link) to its root catalog. - -A relative published catalog has the following properties: - -- It contains **only one** self link: the root of the catalog contains a (necessarily - absolute) link to it's published location. -- All other objects in the STAC contain relative links, and no self links. - - -Absolute Published Catalogs -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -An absolute published catalog (indicated by -``catalog_type=CatalogType.ABSOLUTE_PUBLISHED``) uses absolute links for everything. It -is preferable where possible, since it allows for the easiest provenance tracking out of -all the catalog types. - -An absolute published catalog has the following properties: - -- Each STAC object contains only absolute links. -- Each STAC object has a self link. - -It is not recommended to have relative asset HREFs in an absolute published catalog. - - -Relative vs Absolute HREFs --------------------------- - -HREFs inside a STAC for either links or assets can be relative or absolute. - -Relative vs Absolute Link HREFs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Absolute links point to their file locations in a fully described way. Relative links -are relative to the linking object's file location. For example, if a catalog at -``/some/location/catalog.json`` has a link to an item that has an HREF set to -``item-id/item-id.json``, then that link should resolve to the absolute path -``/some/location/item-id/item-id.json``. - -Links are set as absolute or relative HREFs at save time, as determine by the root -catalog's catalog_type :attr:`~pystac.Catalog.catalog_type`. This means that, even if -the stored HREF of the link is absolute, if the root -``catalog_type=CatalogType.RELATIVE_PUBLISHED`` or -``catalog_type=CatalogType.SELF_CONTAINED`` and subsequent serializing of the any links -in the catalog will produce a relative link, based on the self link of the parent -object. - -You can make all the links of a catalog relative or absolute by setting the -:func:`~pystac.Catalog.catalog_type` field then resaving the entire catalog. - -.. _rel vs abs asset: - -Relative vs Absolute Asset HREFs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Asset HREFs can also be relative or absolute. If an asset HREF is relative, then it is -relative to the Item's metadata file. For example, if the item at -``/some/location/item-id/item-id.json`` had an asset with an HREF of ``./image.tif``, -then the fully resolved path for that image would be -``/some/location/item-id/image.tif`` - -You can make all the asset HREFs of a catalog relative or absolute using the -:func:`Catalog.make_all_asset_hrefs_relative -` and -:func:`Catalog.make_all_asset_hrefs_absolute -` methods. Note that these will not move -any files around, and if the file location does not share a common parent with the -asset's item's self HREF, then the asset HREF will remain absolute as no relative path -is possible. - -Including a ``self`` link -------------------------- - -Every stac object has a :func:`~pystac.STACObject.save_object` method, that takes as an -argument whether or not to include the object's self link. As noted in the section on -:ref:`catalog types`, a self link is necessarily absolute; if an object only contains -relative links, then it cannot contain the self link. PySTAC uses self links as a way of -tracking the object's file location, either what it was read from or it's pending save -location, so each object can have a self link even if you don't ever want that self link -written (e.g. if you are working with self-contained catalogs). - -.. _using stac_io: - -I/O in PySTAC -============= - -The :class:`pystac.StacIO` class defines fundamental methods for I/O -operations within PySTAC, including serialization and deserialization to and from -JSON files and conversion to and from Python dictionaries. This is an abstract class -and should not be instantiated directly. However, PySTAC provides a -:class:`pystac.stac_io.DefaultStacIO` class with minimal implementations of these -methods. This default implementation provides support for reading and writing files -from the local filesystem as well as HTTP URIs (using ``urllib``). This class is -created automatically by all of the object-specific I/O methods (e.g. -:meth:`pystac.Catalog.from_file`), so most users will not need to instantiate this -class themselves. - -If you are dealing with a STAC catalog with URIs that require authentication. -It is possible provide auth headers (or any other customer headers) to the -:class:`pystac.stac_io.DefaultStacIO`. - -.. code-block:: python - - from pystac import Catalog - from pystac import StacIO - - stac_io = StacIO.default() - stac_io.headers = {"Authorization": ""} - - catalog = Catalog.from_file("", stac_io=stac_io) - -You can double check that requests PySTAC is making by adjusting logging level so -that you see all API calls. - -.. code-block:: python - - import logging - - logging.basicConfig() - logger = logging.getLogger('pystac') - logger.setLevel(logging.DEBUG) - -If you require more custom logic for I/O operations or would like to use a -3rd-party library for I/O operations (e.g. ``requests``), -you can create a sub-class of :class:`pystac.StacIO` -(or :class:`pystac.stac_io.DefaultStacIO`) and customize the methods as -you see fit. You can then pass instances of this custom sub-class into the ``stac_io`` -argument of most object-specific I/O methods. You can also use -:meth:`pystac.StacIO.set_default` in your client's ``__init__.py`` file to make this -sub-class the default :class:`pystac.StacIO` implementation throughout the library. - -For example, the following code examples will allow -for reading from AWS's S3 cloud object storage using `boto3 -`__ -or Azure Blob Storage using the `Azure SDK for Python -`__: - -.. tab-set:: - .. tab-item:: AWS S3 - - .. code-block:: python - - from urllib.parse import urlparse - import boto3 - from pystac import Link - from pystac.stac_io import DefaultStacIO, StacIO - from typing import Union, Any - - class CustomStacIO(DefaultStacIO): - def __init__(self): - self.s3 = boto3.resource("s3") - super().__init__() - - def read_text( - self, source: Union[str, Link], *args: Any, **kwargs: Any - ) -> str: - parsed = urlparse(source) - if parsed.scheme == "s3": - bucket = parsed.netloc - key = parsed.path[1:] - - obj = self.s3.Object(bucket, key) - return obj.get()["Body"].read().decode("utf-8") - else: - return super().read_text(source, *args, **kwargs) - - def write_text( - self, dest: Union[str, Link], txt: str, *args: Any, **kwargs: Any - ) -> None: - parsed = urlparse(dest) - if parsed.scheme == "s3": - bucket = parsed.netloc - key = parsed.path[1:] - self.s3.Object(bucket, key).put(Body=txt, ContentEncoding="utf-8") - else: - super().write_text(dest, txt, *args, **kwargs) - - StacIO.set_default(CustomStacIO) - - .. tab-item:: Azure Blob Storage - - .. code-block:: python - - import os - import re - from typing import Any, Dict, Optional, Tuple, Union - from urllib.parse import urlparse - - from azure.core.credentials import ( - AzureNamedKeyCredential, - AzureSasCredential, - TokenCredential, - ) - from azure.storage.blob import BlobClient, ContentSettings - from pystac import Link - from pystac.stac_io import DefaultStacIO - - BLOB_HTTPS_URI_PATTERN = r"https:\/\/(.+?)\.blob\.core\.windows\.net" - - AzureCredentialType = Union[ - str, - Dict[str, str], - AzureNamedKeyCredential, - AzureSasCredential, - TokenCredential, - ] - - - class BlobStacIO(DefaultStacIO): - """A custom StacIO class for reading and writing STAC objects - from/to Azure Blob storage. - """ - - conn_str: Optional[str] = os.getenv("AZURE_STORAGE_CONNECTION_STRING") - account_url: Optional[str] = None - credential: Optional[AzureCredentialType] = None - overwrite: bool = True - - def _is_blob_uri(self, href: str) -> bool: - """Check if href matches Blob URI pattern.""" - if re.search( - re.compile(BLOB_HTTPS_URI_PATTERN), href - ) is not None or href.startswith("abfs://"): - return True - else: - return False - - def _parse_blob_uri(self, uri: str) -> Tuple[str, str]: - """Parse the container and blob name from a Blob URI. - - Parameters - ---------- - uri - An Azure Blob URI. - - Returns - ------- - The container and blob names. - """ - if uri.startswith("abfs://"): - path = uri.replace("abfs://", "/") - else: - path = urlparse(uri).path - - parts = path.split("/") - container = parts[1] - blob = "/".join(parts[2:]) - return container, blob - - def _get_blob_client(self, uri: str) -> BlobClient: - """Instantiate a `BlobClient` given a container and blob. - - Parameters - ---------- - uri - An Azure Blob URI. - - Returns - ------- - A `BlobClient` for interacting with `blob` in `container`. - """ - container, blob = self._parse_blob_uri(uri) - - if self.conn_str: - return BlobClient.from_connection_string( - self.conn_str, - container_name=container, - blob_name=blob, - ) - elif self.account_url: - return BlobClient( - account_url=self.account_url, - container_name=container, - blob_name=blob, - credential=self.credential, - ) - else: - raise ValueError( - "Must set conn_str or account_url (and credential if required)" - ) - - def read_text(self, source: Union[str, Link], *args: Any, **kwargs: Any) -> str: - if isinstance(source, Link): - source = source.href - if self._is_blob_uri(source): - blob_client = self._get_blob_client(source) - obj = blob_client.download_blob().readall().decode() - return obj - else: - return super().read_text(source, *args, **kwargs) - - def write_text( - self, dest: Union[str, Link], txt: str, *args: Any, **kwargs: Any - ) -> None: - """Write STAC Objects to Blob storage. Note: overwrites by default.""" - if isinstance(dest, Link): - dest = dest.href - if self._is_blob_uri(dest): - blob_client = self._get_blob_client(dest) - blob_client.upload_blob( - txt, - overwrite=self.overwrite, - content_settings=ContentSettings(content_type="application/json"), - ) - else: - super().write_text(dest, txt, *args, **kwargs) - - - # set Blob storage connection string - BlobStacIO.conn_str = "my-storage-connection-string" - - # OR set Blob account URL, credential - BlobStacIO.account_url = "https://myblobstorageaccount.blob.core.windows.net" - BlobStacIO.credential = AzureSasCredential("my-sas-token") - - # modify overwrite behavior - BlobStacIO.overwrite = False - - # set BlobStacIO as default StacIO - StacIO.set_default(BlobStacIO) - -If you only need to customize read operations you can inherit from -:class:`~pystac.stac_io.DefaultStacIO` and only overwrite the read method. For example, -to take advantage of connection pooling using a `requests.Session -`__: - -.. code-block:: python - - from urllib.parse import urlparse - import requests - from pystac.stac_io import DefaultStacIO, StacIO - from typing import Union, Any - - class ConnectionPoolingIO(DefaultStacIO): - def __init__(self): - self.session = requests.Session() - - def read_text( - self, source: Union[str, Link], *args: Any, **kwargs: Any - ) -> str: - parsed = urlparse(uri) - if parsed.scheme.startswith("http"): - return self.session.get(uri).text - else: - return super().read_text(source, *args, **kwargs) - - StacIO.set_default(ConnectionPoolingIO) - - -.. _validation_concepts: - -Validation -========== - -PySTAC includes validation functionality that allows users to validate PySTAC objects as -well JSON-encoded STAC objects from STAC versions `0.8.0` and later. - -Enabling validation -------------------- - -To enable the validation feature you'll need to have installed PySTAC with the optional -dependency via: - -.. code-block:: bash - - > pip install pystac[validation] - -This installs the ``jsonschema`` package which is used with the default validator. If -you define your own validation class as described below, you are not required to have -this extra dependency. - -Validating PySTAC objects -------------------------- - -You can validate any :class:`~pystac.Catalog`, :class:`~pystac.Collection` or -:class:`~pystac.Item` by calling the :meth:`~pystac.STACObject.validate` method: - -.. code-block:: python - - item.validate() - -This validates against the latest set of JSON schemas (which are included with the -PySTAC package) or older versions (which are hosted at https://schemas.stacspec.org). -This validation includes any extensions that the object extends (these are always -accessed remotely based on their URIs). - -If there are validation errors, a :class:`~pystac.STACValidationError` -is raised. - -You can also call :meth:`~pystac.Catalog.validate_all` on a Catalog or Collection to -recursively walk through a catalog and validate all objects within it. - -.. code-block:: python - - catalog.validate_all() - -Validating STAC JSON --------------------- - -You can validate STAC JSON represented as a ``dict`` using the -:func:`pystac.validation.validate_dict` method: - -.. code-block:: python - - import json - from pystac.validation import validate_dict - - with open('/path/to/item.json') as f: - js = json.load(f) - validate_dict(js) - -You can also recursively validate all of the catalogs, collections and items across STAC -versions using the :func:`pystac.validation.validate_all` method: - -.. code-block:: python - - import json - from pystac.validation import validate_all - - with open('/path/to/catalog.json') as f: - js = json.load(f) - validate_all(js) - -Using your own validator ------------------------- - -By default PySTAC uses the :class:`~pystac.validation.JsonSchemaSTACValidator` -implementation for validation. Users can define their own implementations of -:class:`~pystac.validation.stac_validator.STACValidator` and register it with pystac -using :func:`pystac.validation.set_validator`. - -The :class:`~pystac.validation.JsonSchemaSTACValidator` takes a -:class:`~pystac.validation.schema_uri_map.SchemaUriMap`, which by default uses the -:class:`~pystac.validation.schema_uri_map.DefaultSchemaUriMap`. If desirable, users can -create their own implementation of -:class:`~pystac.validation.schema_uri_map.SchemaUriMap` and register -a new instance of :class:`~pystac.validation.JsonSchemaSTACValidator` using that schema -map with :func:`pystac.validation.set_validator`. - -Extensions -========== - -From the documentation on :stac-spec:`STAC Spec Extensions `: - - Extensions to the core STAC specification provide additional JSON fields that can be - used to better describe the data. Most tend to be about describing a particular - domain or type of data, but some imply functionality. - -This library makes an effort to support all extensions that are part of the -`stac-extensions GitHub org -`__, and -we are committed to supporting all STAC Extensions at the "Candidate" maturity level or -above (see the `Extension Maturity -`__ documentation for details). - -Accessing Extension Functionality ---------------------------------- - -Extension functionality is encapsulated in classes that are specific to the STAC -Extension (e.g. Electro-Optical, Projection, etc.) and STAC Object -(:class:`~pystac.Collection`, :class:`pystac.Item`, or :class:`pystac.Asset`). All -classes that extend these objects inherit from -:class:`pystac.extensions.base.PropertiesExtension`, and you can use the -``ext`` accessor on the object to access the extension fields. - -For instance, if you have an item that implements the :stac-ext:`Electro-Optical -Extension `, you can access the fields associated with that extension using -:meth:`Item.ext `: - -.. code-block:: python - - import pystac - - item = pystac.Item.from_file("tests/data-files/eo/eo-landsat-example.json") - - # As long as the Item implements the EO Extension you can access all the - # EO properties directly - bands = item.ext.eo.bands - cloud_cover = item.ext.eo.cloud_cover - ... - -.. note:: ``ext`` will raise an :exc:`~pystac.ExtensionNotImplemented` - exception if the object does not implement that extension (e.g. if the extension - URI is not in that object's :attr:`~pystac.STACObject.stac_extensions` list). See - the `Adding an Extension`_ section below for details on adding an extension to an - object. - -If you don't want to raise an error you can use -:meth:`Item.ext.has ` -to first check if the extension is implemented on your pystac object: - -.. code-block:: python - - if item.ext.has("eo"): - bands = item.ext.eo.bands - -See the documentation for each extension implementation for details on the supported -properties and other functionality. - -Extensions have access to the properties of the object. *This attribute is a reference -to the properties of the* :class:`~pystac.Collection`, :class:`~pystac.Item` *or* -:class:`~pystac.Asset` *being extended and can therefore mutate those properties.* -For instance: - -.. code-block:: python - - item = pystac.Item.from_file("tests/data-files/eo/eo-landsat-example.json") - print(item.properties["eo:cloud_cover"]) - # 78 - - print(item.ext.eo.cloud_cover) - # 78 - - item.ext.eo.cloud_cover = 45 - print(item.properties["eo:cloud_cover"]) - # 45 - -There is also a -:attr:`~pystac.extensions.base.PropertiesExtension.additional_read_properties` attribute -that, if present, gives read-only access to properties of any objects that own the -extended object. For instance, an extended :class:`pystac.Asset` instance would have -read access to the properties of the :class:`pystac.Item` that owns it (if there is -one). If a property exists in both additional_read_properties and properties, the value -in additional_read_properties will take precedence. - - -An ``apply`` method is available on extended objects. This allows you to pass in -property values pertaining to the extension. Properties that are required by the -extension will be required arguments to the ``apply`` method. Optional properties will -have a default value of ``None``: - -.. code-block:: python - - # Can also omit cloud_cover entirely... - item.ext.eo.apply(0.5, bands, cloud_cover=None) - - -Adding an Extension -------------------- - -You can add an extension to a STAC object that does not already implement that extension -using the :meth:`Item.ext.add ` method. -The :meth:`Item.ext.add ` method adds the correct -schema URI to the :attr:`~pystac.Item.stac_extensions` list for the STAC object. - -.. code-block:: python - - # Load a basic item without any extensions - item = pystac.Item.from_file("tests/data-files/item/sample-item.json") - print(item.stac_extensions) - # [] - - # Add the Electro-Optical extension - item.ext.add("eo") - print(item.stac_extensions) - # ['https://stac-extensions.github.io/eo/v1.1.0/schema.json'] - -Extended Summaries ------------------- - -Extension classes like :class:`~pystac.extensions.projection.ProjectionExtension` may -also provide a ``summaries`` static method that can be used to extend the Collection -summaries. This method returns a class inheriting from -:class:`pystac.extensions.base.SummariesExtension` that provides tools for summarizing -the properties defined by that extension. These classes also hold a reference to the -Collection's :class:`pystac.Summaries` instance in the ``summaries`` attribute. - - -.. code-block:: python - - import pystac - from pystac.extensions.projection import ProjectionExtension - - # Load a collection that does not implement the Projection extension - collection = pystac.Collection.from_file( - "tests/data-files/examples/1.0.0/collection.json" - ) - - # Add Projection extension summaries to the collection - proj = ProjectionExtension.summaries(collection, add_if_missing=True) - print(collection.stac_extensions) - # [ - # ...., - # 'https://stac-extensions.github.io/projection/v1.1.0/schema.json' - # ] - - # Set the values for various extension fields - proj.epsg = [4326] - collection_as_dict = collection.to_dict() - collection_as_dict["summaries"]["proj:epsg"] - # [4326] - - -Item Asset properties -===================== - -Properties that apply to Items can be found in two places: the Item's properties or in -any of an Item's Assets. If the property is on an Asset, it applies only to that specific -asset. For example, gsd defined for an Item represents the best Ground Sample Distance -(resolution) for the data within the Item. However, some assets may be lower resolution -and thus have a higher gsd. In that case, the `gsd` can be found on the Asset. - -See the STAC documentation on :stac-spec:`Additional Fields for Assets -` and the relevant :stac-spec:`Best -Practices ` for more -information. - -The implementation of this feature in PySTAC uses the method described here and is -consistent across Item and ItemExtensions. The bare property names represent values for -the Item only, but for each property where it is possible to set on both the Item or the -Asset there is a ``get_`` and ``set_`` methods that optionally take an Asset. For the -``get_`` methods, if the property is found on the Asset, the Asset's value is used; -otherwise the Item's value will be used. For the ``set_`` method, if an Asset is passed -in the value will be applied to the Asset and not the Item. - -For example, if we have an Item with a ``gsd`` of 10 with three bands, and only asset -"band3" having a ``gsd`` of 20, the ``get_gsd`` method will behave in the following way: - - .. code-block:: python - - assert item.common_metadata.gsd == 10 - assert item.common_metadata.get_gsd() == 10 - assert item.common_metadata.get_gsd(item.asset['band1']) == 10 - assert item.common_metadata.get_gsd(item.asset['band3']) == 20 - -Similarly, if we set the asset at 'band2' to have a ``gsd`` of 30, it will only affect -that asset: - - .. code-block:: python - - item.common_metadata.set_gsd(30, item.assets['band2'] - assert item.common_metadata.gsd == 10 - assert item.common_metadata.get_gsd(item.asset['band2']) == 30 - -Manipulating STACs -================== - -PySTAC is designed to allow for STACs to be manipulated in-memory. This includes -:ref:`copy stacs`, walking over all objects in a STAC and mutating their properties, or -using collection-style `map` methods for mapping over items. - - -Walking over a STAC -------------------- - -You can walk through all sub-catalogs and items of a catalog with a method inspired -by the Python Standard Library `os.walk() -`_ method: :func:`Catalog.walk() -`: - -.. code-block:: python - - for root, subcats, items in catalog.walk(): - # Root represents a catalog currently being walked in the tree - root.title = '{} has been walked!'.format(root.id) - - # subcats represents any catalogs or collections owned by root - for cat in subcats: - cat.title = 'About to be walked!' - - # items represent all items that are contained by root - for item in items: - item.title = '{} - owned by {}'.format(item.id, root.id) - -Mapping over Items ------------------- - -The :func:`Catalog.map_items ` method is useful for -into smaller chunks (e.g. tiling out large image items). -item, you can return multiple items, in case you are generating new objects, or splitting items -manipulating items in a STAC. This will create a full copy of the STAC, so will leave -the original catalog unmodified. In the method that manipulates and returns the modified - -.. code-block:: python - - def modify_item_title(item): - item.title = 'Some new title' - return item - - def duplicate_item(item): - duplicated_item = item.clone() - duplicated_item.id += "-duplicated" - return [item, duplicated_item] - - - c = catalog.map_items(modify_item_title) - c = c.map_items(duplicate_item) - new_catalog = c - -.. _copy stacs: - -Copying STACs in-memory ------------------------ - -The in-memory copying of STACs to create new ones is crucial to correct manipulations -and mutations of STAC data. The :func:`STACObject.full_copy -` mechanism handles this in a way that ties the elements of -the copies STAC together correctly. This includes situations where there might be cycles -in the graph of connected objects of the STAC (which otherwise would be `a tree -`_). - -Resolving STAC objects -====================== - -PySTAC tries to only "resolve" STAC Objects - that is, load the metadata contained by -STAC files pointed to by links into Python objects in-memory - when necessary. It also -ensures that two links that point to the same object resolve to the same in-memory -object. - -Lazy resolution of STAC objects -------------------------------- - -Links are read only when they need to be. For instance, when you load a catalog using -:func:`Catalog.from_file `, the catalog and all of its links -are read into a :class:`~pystac.Catalog` instance. If you iterate through -:attr:`Catalog.links `, you'll see the :attr:`~pystac.Link.target` -of the :class:`~pystac.Link` will refer to a string - that is the HREF of the link. -However, if you call :func:`Catalog.get_items `, for instance, -you'll get back the actual :class:`~pystac.Item` instances that are referred to by each -item link in the Catalog. That's because at the time you call ``get_items``, PySTAC is -"resolving" the links for any link that represents an item in the catalog. - -The resolution mechanism is accomplished through :func:`Link.resolve_stac_object -`. Though this method is used extensively internally to -PySTAC, ideally this is completely transparent to users of PySTAC, and you won't have to -worry about how and when links get resolved. However, one important aspect to understand -is how object resolution caching happens. - -Resolution Caching ------------------- - -The root :class:`~pystac.Catalog` instance of a STAC (the Catalog which is linked to by -every associated object's ``root`` link) contains a cache of resolved objects. This -cache points to in-memory instances of :class:`~pystac.STACObject` s that have already -been resolved through PySTAC crawling links associated with that root catalog. The cache -works off of the stac object's ID, which is why **it is necessary for every STAC object -in the catalog to have a unique identifier, which is unique across the entire STAC**. - -When a link is being resolved from a STACObject that has it's root set, that root is -passed into the :func:`Link.resolve_stac_object ` call. -That root's :class:`~pystac.cache.ResolvedObjectCache` will be used to -ensure that if the link is pointing to an object that has already been resolved, then -that link will point to the same, single instance in the cache. This ensures working -with STAC objects in memory doesn't create a situation where multiple copies of the same -STAC objects are created from different links, manipulated, and written over each other. - -Working with STAC JSON -====================== - -The ``pystac.serialization`` package has some functionality around working directly with -STAC JSON objects, without utilizing PySTAC object types. This is used internally by -PySTAC, but might also be useful to users working directly with JSON (e.g. on -validation). - - -Identifying STAC objects from JSON ----------------------------------- - -Users can identify STAC information, including the object type, version and extensions, -from JSON. The main method for this is -:func:`~pystac.serialization.identify_stac_object`, which returns an object that -contains the object type, the range of versions this object is valid for (according to -PySTAC's best guess), the common extensions implemented by this object, and any custom -extensions (represented by URIs to JSON Schemas). - -.. code-block:: python - - from pystac.serialization import identify_stac_object - - json_dict = ... - - info = identify_stac_object(json_dict) - - # The object type - info.object_type - - # The version range - info.version_range - - # The common extensions - info.common_extensions - - # The custom Extensions - info.custom_extensions - -Merging common properties -------------------------- - -For pre-1.0.0 STAC, The :func:`~pystac.serialization.merge_common_properties` will take -a JSON dict that represents an item, and if it is associated with a collection, merge in -the collection's properties. You can pass in a dict that contains previously read -collections that caches collections by the HREF of the collection link and/or the -collection ID, which can help avoid multiple reads of -collection links. - -Note that this feature was dropped in STAC 1.0.0-beta.1 - -Geo interface -============= - -:class:`~pystac.Item` implements ``__geo_interface__``, a de-facto standard for -describing geospatial objects in Python: -https://gist.github.com/sgillies/2217756. Many packages can automatically use -objects that implement this protocol, e.g. `shapely -`_: - -.. code-block:: python - - >>> from pystac import Item - >>> from shapely.geometry import mapping, shape - >>> item = Item.from_file("data-files/item/sample-item.json") - >>> print(shape(item)) - POLYGON ((-122.308150179 37.488035566, -122.597502109 37.538869539, - -122.576687533 37.613537207, -122.2880486 37.562818007, -122.308150179 - 37.488035566)) diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 9af5c5707..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,258 +0,0 @@ -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import subprocess -import sys -from typing import Any - -sys.path.insert(0, os.path.abspath(".")) -sys.path.insert(0, os.path.abspath("../")) -from pystac.version import STACVersion, __version__ # noqa:E402 - -git_branch = ( - subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - .decode("utf-8") - .strip() -) - -# -- Project information ----------------------------------------------------- - -project = "pystac" -copyright = "2019, Azavea" -author = "stac-utils" - -# The short X.Y version -version = __version__ -# The full version, including alpha/beta/rc tags -release = __version__ - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx_design", - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.viewcode", - "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", - "sphinx.ext.githubpages", - "sphinx.ext.extlinks", - "nbsphinx", -] - -extlinks = { - "tutorial": ( - "https://github.com/stac-utils/pystac/tree/{}/docs/tutorials/%s".format( - git_branch - ), - "%s tutorial", - ), - "stac-spec": ( - "https://github.com/radiantearth/stac-spec/tree/v{}/%s".format( - STACVersion.DEFAULT_STAC_VERSION - ), - "%s path", - ), - "stac-api-spec": ( - "https://github.com/radiantearth/stac-api-spec/tree/v{}/%s".format( - STACVersion.DEFAULT_STAC_API_VERSION - ), - "%s path", - ), - "stac-ext": ("https://github.com/stac-extensions/%s", "%s extension"), -} - -# Add any paths that contain templates here, relative to this directory. -# templates_path = ["_templates"] - -# Static CSS files -# html_css_files = ["custom.css"] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = None - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "pydata_sphinx_theme" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -html_theme_options = { - "icon_links": [ - { - "name": "GitHub", - "url": "https://github.com/stac-utils/pystac", - "icon": "fab fa-github-square", - }, - { - "name": "Gitter", - "url": "https://gitter.im/SpatioTemporal-Asset-Catalog/" - "python?utm_source=share-link&utm_medium=link&utm_campaign=share-link", - "icon": "fab fa-gitter", - }, - ], - "external_links": [ - {"name": "STAC Spec", "url": "https://github.com/radiantearth/stac-spec"} - ], - "header_links_before_dropdown": 7, - "navigation_with_keys": False, - # "navbar_end": ["navbar-icon-links.html", "search-field.html"] -} - -html_logo = "_static/STAC-03.png" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -html_sidebars: dict[str, list[str]] = {"index": []} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "pystacdoc" - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements: dict[str, Any] = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, "pystac.tex", "pystac Documentation", "stac-utils", "manual"), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "pystac", "pystac Documentation", [author], 1)] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "pystac", - "pystac Documentation", - author, - "pystac", - "Python library for SpatioTemporal Asset Catalogs (STAC).", - "Miscellaneous", - ), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ["search.html"] - - -# -- Extension configuration ------------------------------------------------- - -intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), - "dateutil": ("https://dateutil.readthedocs.io/en/stable", None), - "urllib3": ("https://urllib3.readthedocs.io/en/stable", None), -} - -# -- Substutition variables - -rst_epilog = f".. |stac_version| replace:: {STACVersion.DEFAULT_STAC_VERSION}" - -nitpick_ignore = [ - ("py:class", "Datetime"), - ("py:class", "L"), - ("py:class", "pystac.summaries.T"), - ("py:class", "HREF"), # this one partially works - ("py:class", "jsonschema.validators.Draft7Validator"), -] diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index 13029ec00..000000000 --- a/docs/contributing.rst +++ /dev/null @@ -1,154 +0,0 @@ -Contributing -============ - -A list of issues and ongoing work is available on the PySTAC `issues page -`_. If you want to contribute code, the best -way is to coordinate with the core developers via an issue or pull request conversation. - -Development installation -^^^^^^^^^^^^^^^^^^^^^^^^ -Fork PySTAC into your GitHub account. Then, clone the repo and install it locally with -`uv ` as follows: - -.. code-block:: bash - - git clone git@github.com:your_user_name/pystac.git - cd pystac - uv sync - source .venv/bin/activate - -Testing -^^^^^^^ - -PySTAC runs tests using `pytest `_. You can -find unit tests in the ``tests/`` directory. - -To run the tests: - -.. code-block:: bash - - $ pytest - -To run the tests and generate the coverage report: - -.. code-block:: bash - - $ pytest -v -s --cov pystac --cov-report term-missing - -To view the coverage report, you can run -`coverage report` (to view the report in the terminal) or `coverage html` (to generate -an HTML report that can be opened in a browser). - -The PySTAC tests use `vcrpy `_ to mock API calls -with "pre-recorded" API responses. This often comes up when testing validation. - -When adding new tests that require pulling remote files use the ``@pytest.mark.vcr`` -decorator. Record the new responses and commit them to the repository. - -.. code-block:: bash - - $ pytest -v -s --record-mode new_episodes - $ git add - $ git commit -a -m 'new test episodes' - -Code quality checks -^^^^^^^^^^^^^^^^^^^ - -tl;dr: Run ``pre-commit install --overwrite`` to perform checks when committing, and -``pytest`` to run all tests. - -PySTAC uses - -- `ruff `_ for Python code linting -- `codespell `_ to check code for common misspellings -- `doc8 `__ for style checking on RST files in the docs -- `mypy `_ for Python type annotation checks - -Run all of these with ``pre-commit run --all-files`` or a single one using -``pre-commit run --all-files ID``, where ``ID`` is one of the command names above. For -example, to lint all the Python code, run ``pre-commit run --all-files ruff``. - -You can also install a Git pre-commit hook which will run the relevant linters and -formatters on any staged code when committing. This will be much faster than running on -all files, which is usually [#]_ only required when changing the pre-commit version or -configuration. Once installed you can bypass this check by adding the ``--no-verify`` -flag to Git commit commands, as in ``git commit --no-verify``. - -.. [#] In rare cases changes to one file might invalidate an unchanged file, such as - when modifying the return type of a function used in another file. - -Documentation -^^^^^^^^^^^^^ - -All new features or changes should include API documentation, in the form of -docstrings. Additionally, if you are updating an extension version, check to -see if that extension is used in the ``examples/`` STAC objects at the top level -of the repository. If so, update the extension version, then re-run -``docs/quickstart.ipynb`` to include the new extension versions in the notebook -cell output. - -Benchmarks -^^^^^^^^^^ - -PySTAC uses `asv `_ for benchmarking. Benchmarks are -defined in the ``./benchmarks`` directory. Due to the inherent uncertainty in -the environment of GitHub workflow runners, benchmarks are not executed in CI. -If your changes may affect performance, use the provided script to run the -benchmark suite locally. You'll need to install the benchmark dependencies -first. This script will compare your current ``HEAD`` with the **main** branch -and report any improvements or regressions. - -.. code-block:: bash - - asv continuous --split -e --interleave-rounds --factor 1.25 main HEAD - -The benchmark suite takes a while to run, and will report any significant -changes to standard output. For example, here's a benchmark comparison between -v1.0.0 and v1.6.1 (from `@gadomski's `_ computer):: - - before after ratio - [eee06027] [579c071b] - - - 533±20μs 416±10μs 0.78 collection.CollectionBench.time_collection_from_file [gadomski/virtualenv-py3.10-orjson] - - 329±8μs 235±10μs 0.72 collection.CollectionBench.time_collection_from_dict [gadomski/virtualenv-py3.10-orjson] - - 332±10μs 231±4μs 0.70 collection.CollectionBench.time_collection_from_dict [gadomski/virtualenv-py3.10] - - 174±4μs 106±2μs 0.61 item.ItemBench.time_item_from_dict [gadomski/virtualenv-py3.10] - - 174±4μs 106±2μs 0.61 item.ItemBench.time_item_from_dict [gadomski/virtualenv-py3.10-orjson] - before after ratio - [eee06027] [579c071b] - - + 87.1±3μs 124±5μs 1.42 catalog.CatalogBench.time_catalog_from_dict [gadomski/virtualenv-py3.10] - + 87.1±4μs 122±5μs 1.40 catalog.CatalogBench.time_catalog_from_dict [gadomski/virtualenv-py3.10-orjson] - -When developing new benchmarks, you can run a shortened version of the benchmark suite: - -.. code-block:: bash - - asv dev - - -CHANGELOG -^^^^^^^^^ - -PySTAC maintains a `changelog `_ -to track changes between releases. This changelog is automatically kept up-to-date by -`release-please `_, specifically on an unmerged -release PR. - -Style -^^^^^ - -In an effort to maintain a consistent codebase, PySTAC conforms to the following rules: - -.. code-block:: python - - # DO - from datetime import datetime - - # DON't - import datetime - import datetime as dt - -The exception to this rule is when ``datetime`` is only imported for type checking and -using the class directly interferes with another variable name. In this case, in the -TYPE_CHECKING block you should do ``from datetime import datetime as Datetime``. diff --git a/docs/example-catalog/catalog.json b/docs/example-catalog/catalog.json deleted file mode 100644 index 285d412b6..000000000 --- a/docs/example-catalog/catalog.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "Catalog", - "stac_version": "1.1.0", - "stac_extensions": [], - "id": "landsat-stac-collection-catalog", - "title": "STAC for Landsat data", - "description": "STAC for Landsat data", - "links": [ - { - "href": "./catalog.json", - "rel": "self" - }, - { - "href": "./catalog.json", - "rel": "root" - }, - { - "href": "./landsat-8-l1/collection.json", - "rel": "child" - } - ] -} \ No newline at end of file diff --git a/docs/example-catalog/landsat-8-l1/2018-05/LC80150322018141LGN00.json b/docs/example-catalog/landsat-8-l1/2018-05/LC80150322018141LGN00.json deleted file mode 100644 index bf6a8d4d5..000000000 --- a/docs/example-catalog/landsat-8-l1/2018-05/LC80150322018141LGN00.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "type": "Feature", - "id": "LC80150322018141LGN00", - "stac_version" : "1.0.0", - "stac_extensions" : [ - "https://stac-extensions.github.io/eo/v1.1.0/schema.json", - "https://stac-extensions.github.io/view/v1.0.0/schema.json", - "https://stac-extensions.github.io/projection/v1.1.0/schema.json" - ], - "bbox": [ - -77.88298, - 39.23073, - -75.07535, - 41.41022 - ], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -77.28911976020206, - 41.40912394323429 - ], - [ - -75.07576783500748, - 40.97162247589133 - ], - [ - -75.66872631473827, - 39.23210949585851 - ], - [ - -77.87946700654118, - 39.67679918442899 - ], - [ - -77.28911976020206, - 41.40912394323429 - ] - ] - ] - }, - "collection": "landsat-8-l1", - "properties": { - "collection": "landsat-8-l1", - "datetime": "2018-05-21T15:44:59Z", - "view:sun_azimuth": 134.8082647, - "view:sun_elevation": 64.00406717, - "eo:cloud_cover": 4, - "instruments": ["OLI_TIRS"], - "view:off_nadir": 0, - "platform": "landsat-8", - "gsd": 30, - "proj:epsg": 32618, - "proj:transform": [258885.0, 30.0, 0.0, 4584315.0, 0.0, -30.0], - "proj:geometry": { - "type": "Polygon", - "coordinates": [ - [ - [258885.0, 4346085.0], - [258885.0, 4584315.0], - [493515.0, 4584315.0], - [493515.0, 4346085.0], - [258885.0, 4346085.0] - ] - ] - }, - "proj:shape": [7821, 7941] - }, - "assets": { - "index": { - "type": "text/html", - "title": "HTML index page", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/index.html", - "roles": [] - }, - "thumbnail": { - "title": "Thumbnail image", - "type": "image/jpeg", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_thumb_large.jpg", - "roles" : [ - "thumbnail" - ] - }, - "B1": { - "type": "image/tiff", - "title": "Band 1 (coastal)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B1.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B1", - "full_width_half_max" : 0.02, - "center_wavelength" : 0.44, - "common_name" : "coastal" - } - ] - }, - "B2": { - "type": "image/tiff", - "title": "Band 2 (blue)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B2.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B2", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.48, - "common_name" : "blue" - } - ] - }, - "B3": { - "type": "image/tiff", - "title": "Band 3 (green)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B3.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B3", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.56, - "common_name" : "green" - } - ] - }, - "B4": { - "type": "image/tiff", - "title": "Band 4 (red)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B4.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B4", - "full_width_half_max" : 0.04, - "center_wavelength" : 0.65, - "common_name" : "red" - } - ] - }, - "B5": { - "type": "image/tiff", - "title": "Band 5 (nir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B5.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B5", - "full_width_half_max" : 0.03, - "center_wavelength" : 0.86, - "common_name" : "nir" - } - ] - }, - "B6": { - "type": "image/tiff", - "title": "Band 6 (swir16)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B6.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B6", - "full_width_half_max" : 0.08, - "center_wavelength" : 1.6, - "common_name" : "swir16" - } - ] - }, - "B7": { - "type": "image/tiff", - "title": "Band 7 (swir22)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B7.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B7", - "full_width_half_max" : 0.22, - "center_wavelength" : 2.2, - "common_name" : "swir22" - } - ] - }, - "B8": { - "type": "image/tiff", - "title": "Band 8 (pan)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B8.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B8", - "full_width_half_max" : 0.18, - "center_wavelength" : 0.59, - "common_name" : "pan" - } - ] - }, - "B9": { - "type": "image/tiff", - "title": "Band 9 (cirrus)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B9.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B9", - "full_width_half_max" : 0.02, - "center_wavelength" : 1.37, - "common_name" : "cirrus" - } - ] - }, - "B10": { - "type": "image/tiff", - "title": "Band 10 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B10.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B10", - "full_width_half_max" : 0.8, - "center_wavelength" : 10.9, - "common_name" : "lwir11" - } - ] - }, - "B11": { - "type": "image/tiff", - "title": "Band 11 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_B11.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B11", - "full_width_half_max" : 1, - "center_wavelength" : 12, - "common_name" : "lwir2" - } - ] - }, - "ANG": { - "title": "Angle coefficients file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_ANG.txt", - "roles": [] - }, - "MTL": { - "title": "original metadata file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_MTL.txt", - "roles": [] - }, - "BQA": { - "title": "Band quality data", - "type": "image/tiff", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/032/LC08_L1TP_015032_20180521_20180605_01_T1/LC08_L1TP_015032_20180521_20180605_01_T1_BQA.TIF", - "roles": [] - } - }, - "links": [ - { - "rel": "self", - "href": "./LC80150322018141LGN00.json" - }, - { - "rel": "parent", - "href": "../collection.json" - }, - { - "rel": "collection", - "href": "../collection.json" - }, - { - "rel": "root", - "href": "../../catalog.json" - } - ] -} diff --git a/docs/example-catalog/landsat-8-l1/2018-06/LC80140332018166LGN00.json b/docs/example-catalog/landsat-8-l1/2018-06/LC80140332018166LGN00.json deleted file mode 100644 index e4eecc3c5..000000000 --- a/docs/example-catalog/landsat-8-l1/2018-06/LC80140332018166LGN00.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "type": "Feature", - "id": "LC80140332018166LGN00", - "stac_version" : "1.0.0", - "stac_extensions" : [ - "https://stac-extensions.github.io/eo/v1.1.0/schema.json", - "https://stac-extensions.github.io/view/v1.0.0/schema.json", - "https://stac-extensions.github.io/projection/v1.1.0/schema.json" - ], - "bbox": [ - -76.66703, - 37.82561, - -73.94861, - 39.95958 - ], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -76.12180471942207, - 39.95810181489563 - ], - [ - -73.94910518227414, - 39.55117185146004 - ], - [ - -74.49564725552679, - 37.826064511480496 - ], - [ - -76.66550404911956, - 38.240699151776084 - ], - [ - -76.12180471942207, - 39.95810181489563 - ] - ] - ] - }, - "collection": "landsat-8-l1", - "properties": { - "collection": "landsat-8-l1", - "datetime": "2018-06-15T15:39:09Z", - "view:sun_azimuth": 125.59055137, - "view:sun_elevation": 66.54485226, - "eo:cloud_cover": 22, - "instruments": ["OLI_TIRS"], - "view:off_nadir": 0, - "platform": "landsat-8", - "gsd": 30, - "proj:epsg": 32618, - "proj:transform": [357585.0, 30.0, 0.0, 4423815.0, 0.0, -30.0], - "proj:geometry": { - "type": "Polygon", - "coordinates": [ - [ - [357585.0, 4187685.0], - [357585.0, 4423815.0], - [589815.0, 4423815.0], - [589815.0, 4187685.0], - [357585.0, 4187685.0] - ] - ] - }, - "proj:shape": [7741, 7871] - }, - "assets": { - "index": { - "type": "text/html", - "title": "HTML index page", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/index.html", - "roles": [] - }, - "thumbnail": { - "title": "Thumbnail image", - "type": "image/jpeg", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_thumb_large.jpg", - "roles" : [ - "thumbnail" - ] - }, - "B1": { - "type": "image/tiff", - "title": "Band 1 (coastal)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B1.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B1", - "full_width_half_max" : 0.02, - "center_wavelength" : 0.44, - "common_name" : "coastal" - } - ] - }, - "B2": { - "type": "image/tiff", - "title": "Band 2 (blue)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B2.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B2", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.48, - "common_name" : "blue" - } - ] - }, - "B3": { - "type": "image/tiff", - "title": "Band 3 (green)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B3.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B3", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.56, - "common_name" : "green" - } - ] - }, - "B4": { - "type": "image/tiff", - "title": "Band 4 (red)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B4.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B4", - "full_width_half_max" : 0.04, - "center_wavelength" : 0.65, - "common_name" : "red" - } - ] - }, - "B5": { - "type": "image/tiff", - "title": "Band 5 (nir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B5.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B5", - "full_width_half_max" : 0.03, - "center_wavelength" : 0.86, - "common_name" : "nir" - } - ] - }, - "B6": { - "type": "image/tiff", - "title": "Band 6 (swir16)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B6.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B6", - "full_width_half_max" : 0.08, - "center_wavelength" : 1.6, - "common_name" : "swir16" - } - ] - }, - "B7": { - "type": "image/tiff", - "title": "Band 7 (swir22)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B7.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B7", - "full_width_half_max" : 0.22, - "center_wavelength" : 2.2, - "common_name" : "swir22" - } - ] - }, - "B8": { - "type": "image/tiff", - "title": "Band 8 (pan)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B8.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B8", - "full_width_half_max" : 0.18, - "center_wavelength" : 0.59, - "common_name" : "pan" - } - ] - }, - "B9": { - "type": "image/tiff", - "title": "Band 9 (cirrus)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B9.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B9", - "full_width_half_max" : 0.02, - "center_wavelength" : 1.37, - "common_name" : "cirrus" - } - ] - }, - "B10": { - "type": "image/tiff", - "title": "Band 10 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B10.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B10", - "full_width_half_max" : 0.8, - "center_wavelength" : 10.9, - "common_name" : "lwir11" - } - ] - }, - "B11": { - "type": "image/tiff", - "title": "Band 11 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B11.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B11", - "full_width_half_max" : 1, - "center_wavelength" : 12, - "common_name" : "lwir2" - } - ] - }, - "ANG": { - "title": "Angle coefficients file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_ANG.txt", - "roles": [] - }, - "MTL": { - "title": "original metadata file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_MTL.txt", - "roles": [] - }, - "BQA": { - "title": "Band quality data", - "type": "image/tiff", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_BQA.TIF", - "roles": [] - } - }, - "links": [ - { - "rel": "self", - "href": "./LC80140332018166LGN00.json" - }, - { - "rel": "parent", - "href": "../collection.json" - }, - { - "rel": "collection", - "href": "../collection.json" - }, - { - "rel": "root", - "href": "../../catalog.json" - } - ] -} diff --git a/docs/example-catalog/landsat-8-l1/2018-06/LC80300332018166LGN00.json b/docs/example-catalog/landsat-8-l1/2018-06/LC80300332018166LGN00.json deleted file mode 100644 index 4f62ac898..000000000 --- a/docs/example-catalog/landsat-8-l1/2018-06/LC80300332018166LGN00.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "type": "Feature", - "stac_version" : "1.0.0", - "stac_extensions" : [ - "https://stac-extensions.github.io/eo/v1.1.0/schema.json", - "https://stac-extensions.github.io/view/v1.0.0/schema.json", - "https://stac-extensions.github.io/projection/v1.1.0/schema.json" - ], - "id": "LC80300332018166LGN00", - "bbox": [ - -101.40793, - 37.81084, - -98.6721, - 39.97469 - ], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -100.84368079413701, - 39.97210491033466 - ], - [ - -98.67492641719046, - 39.54833037653145 - ], - [ - -99.23946071016417, - 37.81370881408165 - ], - [ - -101.40560438472555, - 38.24476872678675 - ], - [ - -100.84368079413701, - 39.97210491033466 - ] - ] - ] - }, - "collection": "landsat-8-l1", - "properties": { - "collection": "landsat-8-l1", - "datetime": "2018-06-15T17:18:03Z", - "view:sun_azimuth": 125.5799919, - "view:sun_elevation": 66.54407242, - "eo:cloud_cover": 0, - "instruments": ["OLI_TIRS"], - "view:off_nadir": 0, - "platform": "landsat-8", - "gsd": 30, - "proj:epsg": 32614, - "proj:transform": [294285.0, 30.0, 0.0, 4425015.0, 0.0, -30], - "proj:geometry": { - "type": "Polygon", - "coordinates": [ - [ - [294285.0, 4187385.0], - [294285.0, 4425015.0], - [528015.0, 4425015.0], - [528015.0, 4187385.0], - [294285.0, 4187385.0] - ] - ] - }, - "proj:shape": [7791, 7921] - }, - "assets": { - "index": { - "type": "text/html", - "title": "HTML index page", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/index.html", - "roles" : [] - }, - "thumbnail": { - "title": "Thumbnail image", - "type": "image/jpeg", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_thumb_large.jpg", - "roles" : [ - "thumbnail" - ] - }, - "B1": { - "type": "image/tiff", - "title": "Band 1 (coastal)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B1.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B1", - "full_width_half_max" : 0.02, - "center_wavelength" : 0.44, - "common_name" : "coastal" - } - ] - }, - "B2": { - "type": "image/tiff", - "title": "Band 2 (blue)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B2.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B2", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.48, - "common_name" : "blue" - } - ] - }, - "B3": { - "type": "image/tiff", - "title": "Band 3 (green)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B3.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B3", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.56, - "common_name" : "green" - } - ] - }, - "B4": { - "type": "image/tiff", - "title": "Band 4 (red)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B4.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B4", - "full_width_half_max" : 0.04, - "center_wavelength" : 0.65, - "common_name" : "red" - } - ] - }, - "B5": { - "type": "image/tiff", - "title": "Band 5 (nir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B5.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B5", - "full_width_half_max" : 0.03, - "center_wavelength" : 0.86, - "common_name" : "nir" - } - ] - }, - "B6": { - "type": "image/tiff", - "title": "Band 6 (swir16)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B6.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B6", - "full_width_half_max" : 0.08, - "center_wavelength" : 1.6, - "common_name" : "swir16" - } - ] - }, - "B7": { - "type": "image/tiff", - "title": "Band 7 (swir22)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B7.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B7", - "full_width_half_max" : 0.22, - "center_wavelength" : 2.2, - "common_name" : "swir22" - } - ] - }, - "B8": { - "type": "image/tiff", - "title": "Band 8 (pan)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B8.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B8", - "full_width_half_max" : 0.18, - "center_wavelength" : 0.59, - "common_name" : "pan" - } - ] - }, - "B9": { - "type": "image/tiff", - "title": "Band 9 (cirrus)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B9.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B9", - "full_width_half_max" : 0.02, - "center_wavelength" : 1.37, - "common_name" : "cirrus" - } - ] - }, - "B10": { - "type": "image/tiff", - "title": "Band 10 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B10.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B10", - "full_width_half_max" : 0.8, - "center_wavelength" : 10.9, - "common_name" : "lwir11" - } - ] - }, - "B11": { - "type": "image/tiff", - "title": "Band 11 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_B11.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B11", - "full_width_half_max" : 1, - "center_wavelength" : 12, - "common_name" : "lwir2" - } - ] - }, - "ANG": { - "title": "Angle coefficients file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_ANG.txt", - "roles": [] - }, - "MTL": { - "title": "original metadata file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_MTL.txt", - "roles": [] - }, - "BQA": { - "title": "Band quality data", - "type": "image/tiff", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/030/033/LC08_L1TP_030033_20180615_20180703_01_T1/LC08_L1TP_030033_20180615_20180703_01_T1_BQA.TIF", - "roles": [] - } - }, - "links": [ - { - "rel": "self", - "href": "./LC80300332018166LGN00.json" - }, - { - "rel": "parent", - "href": "../collection.json" - }, - { - "rel": "collection", - "href": "../collection.json" - }, - { - "rel": "root", - "href": "../../catalog.json" - } - ] -} diff --git a/docs/example-catalog/landsat-8-l1/2018-07/LC80150332018189LGN00.json b/docs/example-catalog/landsat-8-l1/2018-07/LC80150332018189LGN00.json deleted file mode 100644 index b74e84eef..000000000 --- a/docs/example-catalog/landsat-8-l1/2018-07/LC80150332018189LGN00.json +++ /dev/null @@ -1,275 +0,0 @@ -{ - "type": "Feature", - "id": "LC80150332018189LGN00", - "stac_version" : "1.0.0", - "stac_extensions" : [ - "https://stac-extensions.github.io/eo/v1.1.0/schema.json", - "https://stac-extensions.github.io/view/v1.0.0/schema.json", - "https://stac-extensions.github.io/projection/v1.1.0/schema.json" - ], - "bbox": [ - -78.25028, - 37.79719, - -75.48983, - 39.98757 - ], - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -77.66532657556414, - 39.987421383364385 - ], - [ - -75.49021499188945, - 39.54442448711656 - ], - [ - -76.07747288135147, - 37.799167045362736 - ], - [ - -78.25025639728777, - 38.24897728816149 - ], - [ - -77.66532657556414, - 39.987421383364385 - ] - ] - ] - }, - "collection": "landsat-8-l1", - "properties": { - "collection": "landsat-8-l1", - "datetime": "2018-07-08T15:45:34Z", - "view:sun_azimuth": 125.31095515, - "view:sun_elevation": 65.2014335, - "eo:cloud_cover": 0, - "instruments": ["OLI_TIRS"], - "view:off_nadir": 0, - "platform": "landsat-8", - "gsd": 30, - "proj:epsg": 32618, - "proj:transform": [222285.0, 30.0, 0.0, 4426515.0, 0.0, -30.0], - "proj:geometry": { - "type": "Polygon", - "coordinates": [ - [ - [222285.0, 4187985.0], - [222285.0, 4426515.0], - [456915.0, 4426515.0], - [456915.0, 4187985.0], - [222285.0,4187985.0]] - ] - }, - "proj:shape": [7821, 7951] - }, - "assets": { - "index": { - "type": "text/html", - "title": "HTML index page", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/index.html", - "roles": [] - }, - "thumbnail": { - "title": "Thumbnail image", - "type": "image/jpeg", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_thumb_large.jpg", - "roles" : [ - "thumbnail" - ] - }, - "B1": { - "type": "image/tiff", - "title": "Band 1 (coastal)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B1.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B1", - "full_width_half_max" : 0.02, - "center_wavelength" : 0.44, - "common_name" : "coastal" - } - ] - }, - "B2": { - "type": "image/tiff", - "title": "Band 2 (blue)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B2.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B2", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.48, - "common_name" : "blue" - } - ] - }, - "B3": { - "type": "image/tiff", - "title": "Band 3 (green)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B3.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B3", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.56, - "common_name" : "green" - } - ] - }, - "B4": { - "type": "image/tiff", - "title": "Band 4 (red)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B4.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B4", - "full_width_half_max" : 0.04, - "center_wavelength" : 0.65, - "common_name" : "red" - } - ] - }, - "B5": { - "type": "image/tiff", - "title": "Band 5 (nir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B5.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B5", - "full_width_half_max" : 0.03, - "center_wavelength" : 0.86, - "common_name" : "nir" - } - ] - }, - "B6": { - "type": "image/tiff", - "title": "Band 6 (swir16)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B6.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B6", - "full_width_half_max" : 0.08, - "center_wavelength" : 1.6, - "common_name" : "swir16" - } - ] - }, - "B7": { - "type": "image/tiff", - "title": "Band 7 (swir22)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B7.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B7", - "full_width_half_max" : 0.22, - "center_wavelength" : 2.2, - "common_name" : "swir22" - } - ] - }, - "B8": { - "type": "image/tiff", - "title": "Band 8 (pan)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B8.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B8", - "full_width_half_max" : 0.18, - "center_wavelength" : 0.59, - "common_name" : "pan" - } - ] - }, - "B9": { - "type": "image/tiff", - "title": "Band 9 (cirrus)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B9.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B9", - "full_width_half_max" : 0.02, - "center_wavelength" : 1.37, - "common_name" : "cirrus" - } - ] - }, - "B10": { - "type": "image/tiff", - "title": "Band 10 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B10.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B10", - "full_width_half_max" : 0.8, - "center_wavelength" : 10.9, - "common_name" : "lwir11" - } - ] - }, - "B11": { - "type": "image/tiff", - "title": "Band 11 (lwir)", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_B11.TIF", - "roles": [], - "eo:bands": [ - { - "name" : "B11", - "full_width_half_max" : 1, - "center_wavelength" : 12, - "common_name" : "lwir2" - } - ] - }, - "ANG": { - "title": "Angle coefficients file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_ANG.txt", - "roles": [] - }, - "MTL": { - "title": "original metadata file", - "type": "text/plain", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_MTL.txt", - "roles": [] - }, - "BQA": { - "title": "Band quality data", - "type": "image/tiff", - "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/015/033/LC08_L1TP_015033_20180708_20180717_01_T1/LC08_L1TP_015033_20180708_20180717_01_T1_BQA.TIF", - "roles": [] - } - }, - "links": [ - { - "rel": "self", - "href": "./LC80150332018189LGN00.json" - }, - { - "rel": "parent", - "href": "../collection.json" - }, - { - "rel": "collection", - "href": "../collection.json" - }, - { - "rel": "root", - "href": "../../catalog.json" - } - ] -} diff --git a/docs/example-catalog/landsat-8-l1/collection.json b/docs/example-catalog/landsat-8-l1/collection.json deleted file mode 100644 index 173a33e4e..000000000 --- a/docs/example-catalog/landsat-8-l1/collection.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "type": "Collection", - "stac_version" : "1.0.0", - "stac_extensions" : [ - "eo", - "view", - "https://example.com/stac/landsat-extension/1.0/schema.json" - ], - "id" : "landsat-8-l1", - "title" : "Landsat 8 L1", - "description" : "Landsat 8 imagery radiometrically calibrated and orthorectified using ground points and Digital Elevation Model (DEM) data to correct relief displacement.", - "keywords" : [ - "landsat", - "earth observation", - "usgs" - ], - "license" : "proprietary", - "providers" : [ - { - "name" : "Development Seed", - "roles" : [ - "processor" - ], - "url" : "https://github.com/sat-utils/sat-api" - } - ], - "extent" : { - "spatial" : { - "bbox" : [ - [ - -180.0, - -90.0, - 180.0, - 90.0 - ] - ] - }, - "temporal" : { - "interval" : [ - [ - "2018-05-21T15:44:59Z", - "2018-07-08T15:45:34Z" - ] - ] - } - }, - "summaries": {}, - "properties" : { - "collection" : "landsat-8-l1", - "instruments" : ["OLI_TIRS"], - "view:sun_azimuth" : 149.01607154, - "eo:bands" : [ - { - "name" : "B1", - "full_width_half_max" : 0.02, - "center_wavelength" : 0.44, - "common_name" : "coastal" - }, - { - "name" : "B2", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.48, - "common_name" : "blue" - }, - { - "name" : "B3", - "full_width_half_max" : 0.06, - "center_wavelength" : 0.56, - "common_name" : "green" - }, - { - "name" : "B4", - "full_width_half_max" : 0.04, - "center_wavelength" : 0.65, - "common_name" : "red" - }, - { - "name" : "B5", - "full_width_half_max" : 0.03, - "center_wavelength" : 0.86, - "common_name" : "nir" - }, - { - "name" : "B6", - "full_width_half_max" : 0.08, - "center_wavelength" : 1.6, - "common_name" : "swir16" - }, - { - "name" : "B7", - "full_width_half_max" : 0.22, - "center_wavelength" : 2.2, - "common_name" : "swir22" - }, - { - "name" : "B8", - "full_width_half_max" : 0.18, - "center_wavelength" : 0.59, - "common_name" : "pan" - }, - { - "name" : "B9", - "full_width_half_max" : 0.02, - "center_wavelength" : 1.37, - "common_name" : "cirrus" - }, - { - "name" : "B10", - "full_width_half_max" : 0.8, - "center_wavelength" : 10.9, - "common_name" : "lwir11" - }, - { - "name" : "B11", - "full_width_half_max" : 1, - "center_wavelength" : 12, - "common_name" : "lwir2" - } - ], - "view:off_nadir" : 0, - "view:azimuth" : 0, - "platform" : "landsat-8", - "gsd" : 15, - "view:sun_elevation" : 59.214247 - }, - "links" : [ - { - "href" : "../catalog.json", - "rel" : "root" - }, - { - "href" : "../catalog.json", - "rel" : "parent" - }, - { - "href" : "./collection.json", - "rel" : "self" - }, - { - "href" : "./2018-06/LC80140332018166LGN00.json", - "rel" : "item" - }, - { - "href" : "./2018-05/LC80150322018141LGN00.json", - "rel" : "item" - }, - { - "href" : "./2018-07/LC80150332018189LGN00.json", - "rel" : "item" - }, - { - "href" : "./2018-06/LC80300332018166LGN00.json", - "rel" : "item" - } - ] -} diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index e44fb74fa..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,57 +0,0 @@ -PySTAC Documentation -#################### - -PySTAC is a library for working with `SpatioTemporal Asset Catalogs (STAC) -`_ in `Python 3 `_. Some nice features -of PySTAC are: - -* Reading and writing STAC version 1.0. Future versions will read older versions of - STAC, but always write the latest supported version. See :ref:`stac_version_support` - for details. -* In-memory manipulations of STAC catalogs. -* Extend the I/O of STAC metadata to provide support for other platforms (e.g. cloud - providers). -* Easy, efficient crawling of STAC catalogs. STAC objects are only read in when needed. -* Easily write "absolute published", "relative published" and "self-contained" catalogs - as :stac-spec:`described in the best practices documentation - `. - -.. grid:: 1 2 2 2 - :gutter: 2 - - .. grid-item-card:: Get Started - - * :doc:`installation`: Instructions for installing the basic package as well as - extras. - * :doc:`quickstart`: Jupyter notebook tutorial on using PySTAC for reading & - writing STAC catalogs. - - .. grid-item-card:: Go Deeper - - * :doc:`concepts`: Overview of how various concepts and structures from the STAC - Specification are implemented within PySTAC. - * :doc:`tutorials`: In-depth tutorials on using PySTAC for a number of different - applications. - * :doc:`api`: Detailed API documentation of PySTAC classes, methods, and functions. - -Related Projects -================ - -* `pystac-client `__: A Python client for - working with STAC Catalogs and APIs. -* `stactools `__: A command line tool and - library for working with STAC. -* `sat-stac `__: A Python 3 library for reading - and working with existing Spatio-Temporal Asset Catalogs (STAC). *Much of PySTAC - builds on the code and concepts of* ``sat-stac``. - -.. toctree:: - :maxdepth: 2 - :hidden: - - installation - quickstart - concepts - api - tutorials - contributing diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 1f87bab4a..000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,121 +0,0 @@ -Installation -############ - -Install from PyPi (recommended) -=============================== - -.. code-block:: bash - - pip install pystac - -Install from conda-forge -======================== - -.. code-block:: bash - - conda install -c conda-forge pystac - -Install from source -=================== - -.. code-block:: bash - - pip install git+https://github.com/stac-utils/pystac.git - -.. _installation_dependencies: - -Dependencies -============ - -PySTAC requires Python >= 3.10. This project follows the recommendations of -`NEP-29 `__ in deprecating support -for Python versions. This means that users can expect support for Python 3.10 to be -removed from the ``main`` branch after Apr 04, 2025 and therefore from the next release -after that date. - -As a foundational component of the Python STAC ecosystem used in a number of downstream -libraries, PySTAC aims to minimize its dependencies. As a result, the only dependency -for the basic PySTAC library is `python-dateutil -`__. - -PySTAC also has the following extras, which can be optionally installed to provide -additional functionality: - -* ``validation`` - - Installs the additional `jsonschema - `__ dependency. When this - dependency is installed, the :ref:`validation methods ` may be - used to validate STAC objects against the appropriate JSON schemas. - - To install: - - .. code-block:: bash - - pip install pystac[validation] - -* ``orjson`` - - Installs the additional `orjson `__ dependency. When - this dependency is installed, `orjson` will be used as the default JSON - serialization/deserialization for all operations in PySTAC. - - To install: - - .. code-block:: bash - - pip install pystac[orjson] - -* ``urllib3`` - - Installs the additional `urllib3 `__ dependency. - For now, this is only used in :py:class:`pystac.stac_io.RetryStacIO`, but it - may be used more extensively in the future. - - To install: - - .. code-block:: bash - - pip install pystac[urllib3] - -* ``jinja2`` - - Installs the additional `jinja2 `__ dependency. - When this dependency is installed, jupyter notebooks display pretty representations - of PySTAC objects - - To install: - - .. code-block:: bash - - pip install pystac[jinja2] - -Versions -======== - -To install a version of PySTAC that works with a specific versions of the STAC -specification, install the matching version of PySTAC from the following table. - -.. list-table:: - :widths: 50 50 - :header-rows: 1 - - * - PySTAC - - STAC - * - 1.x - - 1.0.x - * - 0.5.x - - 1.0.0-beta.* - * - 0.4.x - - 0.9.x - * - 0.3.x - - 0.8.x - -For instance, to work with STAC v0.9.x: - - .. code-block:: bash - - pip install pystac==0.4.0 - - -STAC spec versions below 0.8 are not supported by PySTAC. diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 451f805a5..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/quickstart.ipynb b/docs/quickstart.ipynb deleted file mode 100644 index 430411d2c..000000000 --- a/docs/quickstart.ipynb +++ /dev/null @@ -1,2401 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Quickstart\n", - "\n", - "This notebook is a quick introduction to using PySTAC for reading an existing STAC catalog. For more in-depth examples check out the other tutorials.\n", - "\n", - "## Dependencies\n", - "\n", - "- PySTAC" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Reading a Catalog\n", - "\n", - "[A STAC Catalog](https://github.com/radiantearth/stac-spec/tree/master/catalog-spec) is used to group other STAC objects like Items, Collections, or even other Catalogs.\n", - "\n", - "We will be using a small example catalog adapted from the [example Landsat Collection](https://github.com/geotrellis/geotrellis-server/tree/977bad7a64c409341479c281c8c72222008861fd/stac-example/catalog/landsat-stac-collection) in the [GeoTrellis](https://geotrellis.io) repository. All STAC Items and Collections can be found in the [docs/example-catalog](https://github.com/stac-utils/pystac/tree/main/docs/example-catalog) directory of this repo; all Assets are hosted in the Landsat S3 bucket.\n", - "\n", - "First, we import the PySTAC classes we will be working with." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:53.360681Z", - "iopub.status.busy": "2025-09-11T16:53:53.360397Z", - "iopub.status.idle": "2025-09-11T16:53:54.333411Z", - "shell.execute_reply": "2025-09-11T16:53:54.332865Z", - "shell.execute_reply.started": "2025-09-11T16:53:53.360653Z" - } - }, - "outputs": [], - "source": [ - "import shutil\n", - "import tempfile\n", - "from pathlib import Path\n", - "\n", - "from pystac import Catalog, get_stac_version" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we read the example catalog and print some basic metadata." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:54.334813Z", - "iopub.status.busy": "2025-09-11T16:53:54.334715Z", - "iopub.status.idle": "2025-09-11T16:53:54.337619Z", - "shell.execute_reply": "2025-09-11T16:53:54.337301Z", - "shell.execute_reply.started": "2025-09-11T16:53:54.334802Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ID: landsat-stac-collection-catalog\n", - "Title: STAC for Landsat data\n", - "Description: STAC for Landsat data\n" - ] - } - ], - "source": [ - "root_catalog = Catalog.from_file(\"./example-catalog/catalog.json\")\n", - "print(f\"ID: {root_catalog.id}\")\n", - "print(f\"Title: {root_catalog.title or 'N/A'}\")\n", - "print(f\"Description: {root_catalog.description or 'N/A'}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*Note that we do not print the \"stac_version\" here. PySTAC automatically updates any Catalogs to the most recent supported STAC version and will automatically write this to the JSON object during serialization.*\n", - "\n", - "Let's confirm the latest STAC Spec version supported by PySTAC." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:54.338227Z", - "iopub.status.busy": "2025-09-11T16:53:54.338113Z", - "iopub.status.idle": "2025-09-11T16:53:54.355305Z", - "shell.execute_reply": "2025-09-11T16:53:54.354458Z", - "shell.execute_reply.started": "2025-09-11T16:53:54.338215Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.1.0\n" - ] - } - ], - "source": [ - "print(get_stac_version())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Crawling Child Catalogs/Collections\n", - "\n", - "[STAC Collections](https://github.com/radiantearth/stac-spec/tree/master/collection-spec) are used to group related Items and provide aggregate or summary metadata for those Items.\n", - "\n", - "STAC Catalogs may have many nested layers of Catalogs or Collections within the top-level collection. Our example catalog has one Collection within the main Catalog at [landsat-8-l1/collection.json](./example-catalog/landsat-8-l1/collection.json). We can list the Collections in a given Catalog using the [Catalog.get_collections](https://pystac.readthedocs.io/en/latest/api.html#pystac.Catalog.get_collections) method. This method returns an iterable of PySTAC [Collection](https://pystac.readthedocs.io/en/latest/api.html#collection) instances, which we will turn into a `list`." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:54.355878Z", - "iopub.status.busy": "2025-09-11T16:53:54.355747Z", - "iopub.status.idle": "2025-09-11T16:53:54.359285Z", - "shell.execute_reply": "2025-09-11T16:53:54.358889Z", - "shell.execute_reply.started": "2025-09-11T16:53:54.355865Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of collections: 1\n", - "Collections IDs:\n", - "- landsat-8-l1\n" - ] - } - ], - "source": [ - "collections = list(root_catalog.get_collections())\n", - "\n", - "print(f\"Number of collections: {len(collections)}\")\n", - "print(\"Collections IDs:\")\n", - "for collection in collections:\n", - " print(f\"- {collection.id}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's grab that Collection as a PySTAC [Collection](https://pystac.readthedocs.io/en/latest/api.html#collection) instance using the [Catalog.get_child method](https://pystac.readthedocs.io/en/latest/api.html#pystac.Catalog.get_child) so we can look at it in more detail. This method gets a child Catalog or Collection by ID, so we'll use the Collection ID that we printed above. Since this method returns `None` if no child exists with the given ID, we'll check to make sure we actually got the `Collection`." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:54.885476Z", - "iopub.status.busy": "2025-09-11T16:53:54.885235Z", - "iopub.status.idle": "2025-09-11T16:53:54.888030Z", - "shell.execute_reply": "2025-09-11T16:53:54.887496Z", - "shell.execute_reply.started": "2025-09-11T16:53:54.885463Z" - } - }, - "outputs": [], - "source": [ - "collection = root_catalog.get_child(\"landsat-8-l1\")\n", - "assert collection is not None" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Crawling Items\n", - "\n", - "[STAC Items](https://github.com/radiantearth/stac-spec/tree/master/item-spec) are the fundamental building blocks of a STAC Catalog. Each Item represents a single spatiotemporal resource (e.g. a satellite scene).\n", - "\n", - "Both Catalogs and Collections may have Items associated with them. Let's crawl our catalog, starting at the root, to see what Items we have. The [Catalog.get_items method](https://pystac.readthedocs.io/en/latest/api.html#pystac.Catalog.get_items) provides a convenient way of recursively listing all Items associated with a Catalog and all of its sub-Catalogs by including the `recursive=True` option." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:55.501459Z", - "iopub.status.busy": "2025-09-11T16:53:55.500987Z", - "iopub.status.idle": "2025-09-11T16:53:55.512529Z", - "shell.execute_reply": "2025-09-11T16:53:55.511976Z", - "shell.execute_reply.started": "2025-09-11T16:53:55.501423Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of items: 4\n", - "- LC80140332018166LGN00\n", - "- LC80150322018141LGN00\n", - "- LC80150332018189LGN00\n", - "- LC80300332018166LGN00\n" - ] - } - ], - "source": [ - "items = list(root_catalog.get_items(recursive=True))\n", - "\n", - "print(f\"Number of items: {len(items)}\")\n", - "for item in items:\n", - " print(f\"- {item.id}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These IDs are not very descriptive; in the next section, we will take a look at how we can access the rich metadata associated with each Item." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Item Metadata\n", - "\n", - "Items can have *a lot* of metadata. This can be a bit overwhelming at first, but break the metadata fields down into a few categories:\n", - "\n", - "- Core Item Metadata\n", - "- Common Metadata\n", - "- STAC Extensions\n", - "\n", - "We will walk through each of these metadata categories in the following sections. \n", - "\n", - "First, let's grab one of the Items using the [Catalog.get_items method](https://pystac.readthedocs.io/en/latest/api.html#pystac.Catalog.get_items). We will use `recursive=True` to recursively crawl all child Catalogs and/or Collections to find the Item." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:56.581966Z", - "iopub.status.busy": "2025-09-11T16:53:56.581591Z", - "iopub.status.idle": "2025-09-11T16:53:56.586208Z", - "shell.execute_reply": "2025-09-11T16:53:56.585346Z", - "shell.execute_reply.started": "2025-09-11T16:53:56.581933Z" - } - }, - "outputs": [], - "source": [ - "item = next(root_catalog.get_items(\"LC80140332018166LGN00\", recursive=True))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Core Item Metadata\n", - "\n", - "The core Item metadata fields include spatiotemporal information and the ID of the collection to which the Item belongs. These fields are all at the top level of the Item JSON and we can access them through attributes on the [PySTAC Item](https://pystac.readthedocs.io/en/latest/api.html#item) instance." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:57.893519Z", - "iopub.status.busy": "2025-09-11T16:53:57.893024Z", - "iopub.status.idle": "2025-09-11T16:53:57.902625Z", - "shell.execute_reply": "2025-09-11T16:53:57.901939Z", - "shell.execute_reply.started": "2025-09-11T16:53:57.893480Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'type': 'Polygon',\n", - " 'coordinates': [[[-76.12180471942207, 39.95810181489563],\n", - " [-73.94910518227414, 39.55117185146004],\n", - " [-74.49564725552679, 37.826064511480496],\n", - " [-76.66550404911956, 38.240699151776084],\n", - " [-76.12180471942207, 39.95810181489563]]]}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.geometry" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:58.207488Z", - "iopub.status.busy": "2025-09-11T16:53:58.207041Z", - "iopub.status.idle": "2025-09-11T16:53:58.213346Z", - "shell.execute_reply": "2025-09-11T16:53:58.212399Z", - "shell.execute_reply.started": "2025-09-11T16:53:58.207451Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[-76.66703, 37.82561, -73.94861, 39.95958]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.bbox" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:58.485290Z", - "iopub.status.busy": "2025-09-11T16:53:58.484894Z", - "iopub.status.idle": "2025-09-11T16:53:58.489053Z", - "shell.execute_reply": "2025-09-11T16:53:58.488573Z", - "shell.execute_reply.started": "2025-09-11T16:53:58.485269Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "datetime.datetime(2018, 6, 15, 15, 39, 9, tzinfo=tzutc())" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.datetime" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:58.703103Z", - "iopub.status.busy": "2025-09-11T16:53:58.702620Z", - "iopub.status.idle": "2025-09-11T16:53:58.709779Z", - "shell.execute_reply": "2025-09-11T16:53:58.708528Z", - "shell.execute_reply.started": "2025-09-11T16:53:58.703063Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'landsat-8-l1'" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.collection_id" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we want the actual `Collection` instance instead of just the ID, we can use the [Item.get_collection](https://pystac.readthedocs.io/en/latest/api.html#pystac.Item.get_collection) method." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:53:59.325563Z", - "iopub.status.busy": "2025-09-11T16:53:59.325121Z", - "iopub.status.idle": "2025-09-11T16:53:59.354690Z", - "shell.execute_reply": "2025-09-11T16:53:59.354062Z", - "shell.execute_reply.started": "2025-09-11T16:53:59.325525Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "

\n", - "
\n", - " <Collection id=landsat-8-l1>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.get_collection()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Common Metadata\n", - "\n", - "Certain fields that are commonly used in Items, but may also be found in other objects (e.g. Assets) are defined in the [Common Metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) section of the spec. These include licensing and instrument information, descriptions of datetime ranges, and some other common fields. These properties can be found as attributes of the `Item.common_metadata` property, which is an instance of the [CommonMetadata class](https://pystac.readthedocs.io/en/latest/api.html#pystac.CommonMetadata)." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:12.242671Z", - "iopub.status.busy": "2025-09-11T16:54:12.241943Z", - "iopub.status.idle": "2025-09-11T16:54:12.249301Z", - "shell.execute_reply": "2025-09-11T16:54:12.248687Z", - "shell.execute_reply.started": "2025-09-11T16:54:12.242599Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['OLI_TIRS']" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.common_metadata.instruments" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:12.740988Z", - "iopub.status.busy": "2025-09-11T16:54:12.740804Z", - "iopub.status.idle": "2025-09-11T16:54:12.744503Z", - "shell.execute_reply": "2025-09-11T16:54:12.744071Z", - "shell.execute_reply.started": "2025-09-11T16:54:12.740974Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'landsat-8'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.common_metadata.platform" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:13.012531Z", - "iopub.status.busy": "2025-09-11T16:54:13.012152Z", - "iopub.status.idle": "2025-09-11T16:54:13.018730Z", - "shell.execute_reply": "2025-09-11T16:54:13.017766Z", - "shell.execute_reply.started": "2025-09-11T16:54:13.012500Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "30" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.common_metadata.gsd" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### STAC Extensions\n", - "\n", - "[STAC Extensions](https://stac-extensions.github.io/) are a mechanism for providing additional metadata not covered by the core STAC Spec. We can see which STAC Extensions are implemented by this particular Item by examining the list of extension URIs in the `stac_extensions` field." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:13.867162Z", - "iopub.status.busy": "2025-09-11T16:54:13.866693Z", - "iopub.status.idle": "2025-09-11T16:54:13.874348Z", - "shell.execute_reply": "2025-09-11T16:54:13.872895Z", - "shell.execute_reply.started": "2025-09-11T16:54:13.867127Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['https://stac-extensions.github.io/eo/v1.1.0/schema.json',\n", - " 'https://stac-extensions.github.io/view/v1.0.0/schema.json',\n", - " 'https://stac-extensions.github.io/projection/v2.0.0/schema.json']" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.stac_extensions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This Item implements the [Electro-Optical](https://github.com/stac-extensions/eo), [View Geometry](https://github.com/stac-extensions/view), and [Projection](https://github.com/stac-extensions/projection) Extensions. \n", - "\n", - "We can also check if a specific extension is implemented using [ext.has](https://pystac.readthedocs.io/en/latest/api.html#pystac.item.ext.has) with the name of that extension." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:14.391585Z", - "iopub.status.busy": "2025-09-11T16:54:14.391134Z", - "iopub.status.idle": "2025-09-11T16:54:14.404483Z", - "shell.execute_reply": "2025-09-11T16:54:14.403721Z", - "shell.execute_reply.started": "2025-09-11T16:54:14.391548Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.ext.has(\"eo\")" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:14.648775Z", - "iopub.status.busy": "2025-09-11T16:54:14.648176Z", - "iopub.status.idle": "2025-09-11T16:54:14.656069Z", - "shell.execute_reply": "2025-09-11T16:54:14.654806Z", - "shell.execute_reply.started": "2025-09-11T16:54:14.648726Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.ext.has(\"raster\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can access fields associated with the extension as attributes on the extension instance. For instance, the [\"eo:cloud_cover\" field](https://github.com/stac-extensions/eo#item-properties-or-asset-fields) defined in the Electro-Optical Extension can be accessed using the [item.ext.eo.cloud_cover](https://pystac.readthedocs.io/en/latest/api.html#pystac.extensions.eo.EOExtension.cloud_cover) attribute." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:15.194408Z", - "iopub.status.busy": "2025-09-11T16:54:15.193992Z", - "iopub.status.idle": "2025-09-11T16:54:15.198657Z", - "shell.execute_reply": "2025-09-11T16:54:15.198163Z", - "shell.execute_reply.started": "2025-09-11T16:54:15.194374Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "22" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.ext.eo.cloud_cover" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also access the cloud cover field directly in the Item properties." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:15.791443Z", - "iopub.status.busy": "2025-09-11T16:54:15.791262Z", - "iopub.status.idle": "2025-09-11T16:54:15.795077Z", - "shell.execute_reply": "2025-09-11T16:54:15.794406Z", - "shell.execute_reply.started": "2025-09-11T16:54:15.791428Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "22" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.properties[\"eo:cloud_cover\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can access the Item's assets through the `assets` attribute, which is a dictionary:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:16.454670Z", - "iopub.status.busy": "2025-09-11T16:54:16.454490Z", - "iopub.status.idle": "2025-09-11T16:54:16.458930Z", - "shell.execute_reply": "2025-09-11T16:54:16.457847Z", - "shell.execute_reply.started": "2025-09-11T16:54:16.454656Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "index: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/index.html (text/html)\n", - "thumbnail: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_thumb_large.jpg (image/jpeg)\n", - "B1: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B1.TIF (image/tiff)\n", - "B2: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B2.TIF (image/tiff)\n", - "B3: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B3.TIF (image/tiff)\n", - "B4: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B4.TIF (image/tiff)\n", - "B5: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B5.TIF (image/tiff)\n", - "B6: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B6.TIF (image/tiff)\n", - "B7: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B7.TIF (image/tiff)\n", - "B8: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B8.TIF (image/tiff)\n", - "B9: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B9.TIF (image/tiff)\n", - "B10: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B10.TIF (image/tiff)\n", - "B11: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B11.TIF (image/tiff)\n", - "ANG: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_ANG.txt (text/plain)\n", - "MTL: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_MTL.txt (text/plain)\n", - "BQA: https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_BQA.TIF (image/tiff)\n" - ] - } - ], - "source": [ - "for asset_key in item.assets:\n", - " asset = item.assets[asset_key]\n", - " print(\"{}: {} ({})\".format(asset_key, asset.href, asset.media_type))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use the `to_dict()` method to convert an Asset, or any PySTAC object, into a dictionary:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:17.378612Z", - "iopub.status.busy": "2025-09-11T16:54:17.378438Z", - "iopub.status.idle": "2025-09-11T16:54:17.382463Z", - "shell.execute_reply": "2025-09-11T16:54:17.382127Z", - "shell.execute_reply.started": "2025-09-11T16:54:17.378598Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'href': 'https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20180615_20180703_01_T1/LC08_L1TP_014033_20180615_20180703_01_T1_B3.TIF',\n", - " 'type': 'image/tiff',\n", - " 'title': 'Band 3 (green)',\n", - " 'eo:bands': [{'name': 'B3',\n", - " 'full_width_half_max': 0.06,\n", - " 'center_wavelength': 0.56,\n", - " 'common_name': 'green'}],\n", - " 'roles': []}" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "asset = item.assets[\"B3\"]\n", - "asset.to_dict()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we use the eo extension to get the band information for the asset:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:17.948428Z", - "iopub.status.busy": "2025-09-11T16:54:17.948185Z", - "iopub.status.idle": "2025-09-11T16:54:17.951893Z", - "shell.execute_reply": "2025-09-11T16:54:17.951535Z", - "shell.execute_reply.started": "2025-09-11T16:54:17.948409Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bands = asset.ext.eo.bands\n", - "bands" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:18.228225Z", - "iopub.status.busy": "2025-09-11T16:54:18.227811Z", - "iopub.status.idle": "2025-09-11T16:54:18.234103Z", - "shell.execute_reply": "2025-09-11T16:54:18.233361Z", - "shell.execute_reply.started": "2025-09-11T16:54:18.228191Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'name': 'B3',\n", - " 'full_width_half_max': 0.06,\n", - " 'center_wavelength': 0.56,\n", - " 'common_name': 'green'}" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bands[0].to_dict()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Writing STAC Objects\n", - "\n", - "We can also use PySTAC to create and/or update STAC objects and write them to disk. This Quickstart Tutorial will introduce you to some very basic concepts in writing STAC objects; for a more thorough tutorial, please see the [\"How to create STAC Catalogs\"](./tutorials/how-to-create-stac-catalogs.ipynb) tutorial.\n", - "\n", - "Suppose there was a mistake in the cloud cover value that we looked at earlier and that we would like to add a value for the `instrument` field, which is currently null. We can update these values using the same attributes and properties as before, then save the entire catalog to our local drive." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:18.881422Z", - "iopub.status.busy": "2025-09-11T16:54:18.880964Z", - "iopub.status.idle": "2025-09-11T16:54:18.885045Z", - "shell.execute_reply": "2025-09-11T16:54:18.884240Z", - "shell.execute_reply.started": "2025-09-11T16:54:18.881397Z" - } - }, - "outputs": [], - "source": [ - "new_catalog = root_catalog.clone()" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:19.363337Z", - "iopub.status.busy": "2025-09-11T16:54:19.362517Z", - "iopub.status.idle": "2025-09-11T16:54:19.370157Z", - "shell.execute_reply": "2025-09-11T16:54:19.368415Z", - "shell.execute_reply.started": "2025-09-11T16:54:19.363224Z" - } - }, - "outputs": [], - "source": [ - "item_to_update = next(root_catalog.get_items(\"LC80140332018166LGN00\", recursive=True))\n", - "\n", - "# Update the cloud cover\n", - "item_to_update.ext.eo.cloud_cover = 30\n", - "\n", - "# Add the instrument field\n", - "item_to_update.common_metadata.instruments = [\"LANDSAT\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can examine the Item properties directly to verify that the changes have taken effect." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:20.012978Z", - "iopub.status.busy": "2025-09-11T16:54:20.012710Z", - "iopub.status.idle": "2025-09-11T16:54:20.016632Z", - "shell.execute_reply": "2025-09-11T16:54:20.015924Z", - "shell.execute_reply.started": "2025-09-11T16:54:20.012957Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "New Cloud Cover: 30\n", - "New Instruments: ['LANDSAT']\n" - ] - } - ], - "source": [ - "print(f\"New Cloud Cover: {item_to_update.properties['eo:cloud_cover']}\")\n", - "print(f\"New Instruments: {item_to_update.properties['instruments']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will write this updated catalog to a temporary directory in our local drive using the [Catalog.normalize_and_save](https://pystac.readthedocs.io/en/latest/api.html#pystac.Catalog.normalize_and_save) method." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:20.831120Z", - "iopub.status.busy": "2025-09-11T16:54:20.830559Z", - "iopub.status.idle": "2025-09-11T16:54:20.837226Z", - "shell.execute_reply": "2025-09-11T16:54:20.835531Z", - "shell.execute_reply.started": "2025-09-11T16:54:20.831076Z" - } - }, - "outputs": [], - "source": [ - "# Create a temporary directory\n", - "tmp_dir = tempfile.mkdtemp()" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:21.086173Z", - "iopub.status.busy": "2025-09-11T16:54:21.085645Z", - "iopub.status.idle": "2025-09-11T16:54:21.095138Z", - "shell.execute_reply": "2025-09-11T16:54:21.094659Z", - "shell.execute_reply.started": "2025-09-11T16:54:21.086132Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Catalog saved to: /tmp/tmpmj8y75bk/catalog.json\n" - ] - } - ], - "source": [ - "# Save the catalog and normalize all paths\n", - "new_catalog.normalize_and_save(tmp_dir)\n", - "print(f\"Catalog saved to: {new_catalog.get_self_href()}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can open up Item that we just updated to verify that the new values were written to disk." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:22.007363Z", - "iopub.status.busy": "2025-09-11T16:54:22.006940Z", - "iopub.status.idle": "2025-09-11T16:54:22.009943Z", - "shell.execute_reply": "2025-09-11T16:54:22.009436Z", - "shell.execute_reply.started": "2025-09-11T16:54:22.007328Z" - } - }, - "outputs": [], - "source": [ - "item_path = Path(tmp_dir) / \"landsat-8-l1\" / \"LC80140332018166LGN00\" / \"\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we clean up the temporary directory." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T16:54:25.139751Z", - "iopub.status.busy": "2025-09-11T16:54:25.139555Z", - "iopub.status.idle": "2025-09-11T16:54:25.143090Z", - "shell.execute_reply": "2025-09-11T16:54:25.141941Z", - "shell.execute_reply.started": "2025-09-11T16:54:25.139737Z" - } - }, - "outputs": [], - "source": [ - "shutil.rmtree(tmp_dir, ignore_errors=True)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - }, - "vscode": { - "interpreter": { - "hash": "28618a729221ed2dc6301bcedf20e90b9d193b9b884dd15c675da71a09b73fa8" - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorials.rst b/docs/tutorials.rst deleted file mode 100644 index 91b5159b9..000000000 --- a/docs/tutorials.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. _tutorials: - -Tutorials -######### - -PySTAC Introduction -------------------- - -- :tutorial:`GitHub version ` -- :ref:`Docs version ` - -This tutorial gives an introduction to PySTAC concepts through code examples. - -How to read data from STAC --------------------------- - -- :tutorial:`GitHub version ` -- :ref:`Docs version ` - -This tutorial shows how to read data from PySTAC into xarray. - -PySTAC SpaceNet tutorial ------------------------- - -- :tutorial:`GitHub version ` -- :ref:`Docs version ` - -This tutorial shows how to create and manipulate a STAC of `SpaceNet `_ data. - -How to create STAC Catalogs with PySTAC ---------------------------------------- - -- :tutorial:`GitHub version ` -- :ref:`Docs version ` - -This was a tutorial that was part of a 30 minute presentation at the `community STAC -sprint -`_ -in Arlington, VA in November 2019. It runs through creating a STAC of images -from the `SpaceNet 5 `_ dataset. - -Creating a Landsat 8 STAC -------------------------- - -- :tutorial:`GitHub version ` -- :ref:`Docs version ` - -This tutorial was presented at [Cloud Native Geospatial Outreach -Day](https://sites.google.com/radiant.earth/cng-agenda/) on September 8th, 2020. It -shows how to create a STAC collection from a subset of Landsat 8 scenes over a location. - -Adding New and Custom Extensions --------------------------------- - -- :tutorial:`GitHub version ` -- :ref:`Docs version ` - -This tutorial goes over how to contribute new extensions to PySTAC as well as how to -implement your own custom extensions. - -.. toctree:: - :hidden: - :maxdepth: 2 - :glob: - - tutorials/pystac-introduction.ipynb - tutorials/how-to-read-data-from-stac.ipynb - tutorials/pystac-spacenet-tutorial.ipynb - tutorials/how-to-create-stac-catalogs.ipynb - tutorials/creating-a-landsat-stac.ipynb - tutorials/adding-new-and-custom-extensions.ipynb diff --git a/docs/tutorials/adding-new-and-custom-extensions.ipynb b/docs/tutorials/adding-new-and-custom-extensions.ipynb deleted file mode 100644 index df1ee2c3c..000000000 --- a/docs/tutorials/adding-new-and-custom-extensions.ipynb +++ /dev/null @@ -1,444 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding New and Custom Extensions\n", - "\n", - "This tutorial will cover using the `PropertiesExtension` and `ExtensionManagementMixin` classes in `pystac.extensions.base` to implement a new extension in PySTAC, and how to make that class accessible via the `pystac.Item.ext` interface.\n", - "\n", - "For this exercise, we will implement an imaginary Order Request Extension that allows us to track an internal order ID associated with a given satellite image, as well as the history of that imagery order. This use-case is specific enough that it would probably not be a good candidate for an actual STAC Extension, but it gives us an opportunity to highlight some of the key aspects and patterns used in implementing STAC Extensions in PySTAC." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we import the PySTAC modules and classes that we will be using throughout the tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime, timedelta\n", - "from pprint import pprint\n", - "from typing import Any, Dict, List, Literal, Optional, Union\n", - "from uuid import uuid4\n", - "\n", - "import pystac\n", - "from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension\n", - "from pystac.utils import (\n", - " StringEnum,\n", - " datetime_to_str,\n", - " get_required,\n", - " map_opt,\n", - " str_to_datetime,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define the Extension" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our extension will extend STAC Items by adding the following properties:\n", - "\n", - "- `order:id`: A unique string ID associated with the internal order for this image. This field will be required.\n", - "- `order:history`: A chronological list of events associated with this order. Each of these \"events\" will have a timestamp and an event type, which will be one of the following: `submitted`, `started_processing`, `delivered`, `cancelled`. This field will be optional." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create Extension Classes\n", - "\n", - "Let's start by creating a class to represent the order history events." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class OrderEventType(StringEnum):\n", - " SUBMITTED = \"submitted\"\n", - " STARTED_PROCESSING = \"started_processing\"\n", - " DELIVERED = \"delivered\"\n", - " CANCELLED = \"cancelled\"\n", - "\n", - "\n", - "class OrderEvent:\n", - " properties: Dict[str, Any]\n", - "\n", - " def __init__(self, properties: Dict[str, Any]) -> None:\n", - " self.properties = properties\n", - "\n", - " @property\n", - " def event_type(self) -> OrderEventType:\n", - " return get_required(self.properties.get(\"type\"), self, \"event_type\")\n", - "\n", - " @event_type.setter\n", - " def event_type(self, v: OrderEventType) -> None:\n", - " self.properties[\"type\"] = str(v)\n", - "\n", - " @property\n", - " def timestamp(self) -> datetime:\n", - " return str_to_datetime(\n", - " get_required(self.properties.get(\"timestamp\"), self, \"timestamp\")\n", - " )\n", - "\n", - " @timestamp.setter\n", - " def timestamp(self, v: datetime) -> None:\n", - " self.properties[\"timestamp\"] = datetime_to_str(v)\n", - "\n", - " def __repr__(self) -> str:\n", - " return f\"\"\n", - "\n", - " def apply(\n", - " self,\n", - " event_type: OrderEventType,\n", - " timestamp: datetime,\n", - " ) -> None:\n", - " self.event_type = event_type\n", - " self.timestamp = timestamp\n", - "\n", - " @classmethod\n", - " def create(\n", - " cls,\n", - " event_type: OrderEventType,\n", - " timestamp: datetime,\n", - " ) -> \"OrderEvent\":\n", - " oe = cls({})\n", - " oe.apply(event_type=event_type, timestamp=timestamp)\n", - " return oe\n", - "\n", - " def to_dict(self) -> Dict[str, Any]:\n", - " return self.properties" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A few important notes about how we constructed this:\n", - "\n", - "- We used PySTAC's [StringEnum class](https://pystac.readthedocs.io/en/latest/api/utils.html#pystac.utils.StringEnum), which inherits from the Python [Enum](https://docs.python.org/3/library/enum.html) class, to capture the allowed event type values. This class has built-in methods that will convert these instances to strings when serializing STAC Items to JSON.\n", - "- We use property getters and setters to manipulate a `properties` dictionary in our `OrderEvent` class. We will see later how this pattern allows us to mutate Item property dictionaries in-place so that updates to the `OrderEvent` object are synced to the Item they extend.\n", - "- The `timestamp` property is converted to a string before it is saved in the `properties` dictionary. This ensures that dictionary is always JSON-serializable but allows us to work with the values as a Python `datetime` instance when using the property getter.\n", - "- We use `event_type` as our property name so that we do not shadow the built-in `type` function in the `apply` method. However, this values is stored under the desired `\"type\"` key in the underlying `properties` dictionary." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we will create a new class inheriting from `PropertiesExtension` and `ExtensionManagementMixin`. Since this class only extends `pystac.Item` instance, we do not need to make it [generic](https://docs.python.org/3/library/typing.html#typing.Generic). If you were creating an extension that applied to multiple object types (e.g. `pystac.Item` and `pystac.Asset`) then you would need to inherit from `typing.Generic` as well and create concrete extension classed for each of these object types (see the [EOExtension](https://github.com/stac-utils/pystac/blob/3c5176f178a4345cb50d5dab83f1dab504ed2682/pystac/extensions/eo.py#L279), [ItemEOExtension](https://github.com/stac-utils/pystac/blob/3c5176f178a4345cb50d5dab83f1dab504ed2682/pystac/extensions/eo.py#L385), and [AssetEOExtension](https://github.com/stac-utils/pystac/blob/3c5176f178a4345cb50d5dab83f1dab504ed2682/pystac/extensions/eo.py#L429) classes for an example of this implementation)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "SCHEMA_URI: str = \"https://example.com/image-order/v1.0.0/schema.json\"\n", - "PREFIX: str = \"order:\"\n", - "ID_PROP: str = PREFIX + \"id\"\n", - "HISTORY_PROP: str = PREFIX + \"history\"\n", - "\n", - "\n", - "class OrderExtension(\n", - " PropertiesExtension, ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]]\n", - "):\n", - " name: Literal[\"order\"] = \"order\"\n", - "\n", - " def __init__(self, item: pystac.Item):\n", - " self.item = item\n", - " self.properties = item.properties\n", - "\n", - " def apply(\n", - " self, order_id: str = None, history: Optional[List[OrderEvent]] = None\n", - " ) -> None:\n", - " self.order_id = order_id\n", - " self.history = history\n", - "\n", - " @property\n", - " def order_id(self) -> str:\n", - " return get_required(self._get_property(ID_PROP, str), self, ID_PROP)\n", - "\n", - " @order_id.setter\n", - " def order_id(self, v: str) -> None:\n", - " self._set_property(ID_PROP, v, pop_if_none=False)\n", - "\n", - " @property\n", - " def history(self) -> Optional[List[OrderEvent]]:\n", - " return map_opt(\n", - " lambda history: [OrderEvent(d) for d in history],\n", - " self._get_property(HISTORY_PROP, List[OrderEvent]),\n", - " )\n", - "\n", - " @history.setter\n", - " def history(self, v: Optional[List[OrderEvent]]) -> None:\n", - " self._set_property(\n", - " HISTORY_PROP,\n", - " map_opt(lambda history: [event.to_dict() for event in history], v),\n", - " pop_if_none=True,\n", - " )\n", - "\n", - " @classmethod\n", - " def get_schema_uri(cls) -> str:\n", - " return SCHEMA_URI\n", - "\n", - " @classmethod\n", - " def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> \"OrderExtension\":\n", - " if isinstance(obj, pystac.Item):\n", - " cls.ensure_has_extension(obj, add_if_missing)\n", - " return OrderExtension(obj)\n", - " else:\n", - " raise pystac.ExtensionTypeError(\n", - " f\"OrderExtension does not apply to type '{type(obj).__name__}'\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As with the `OrderEvent` class, we use property getters and setters for our extension fields (the `PropertiesExtension` class has a `properties` attribute where these are stored). Rather than setting these values directly in the dictionary, we use the `_get_property` and `_set_property` methods that are built into the `PropertiesExtension` class). We also add an `ext` method that will be used to extend `pystac.Item` instances, and a `get_schema_uri` method that is required for all `PropertiesExtension` classes." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the Extension\n", - "\n", - "Let's try using our new classes to extend an `Item` and access the extension properties. We'll start by loading the core Item example from the STAC spec examples [here](https://github.com/radiantearth/stac-spec/blob/master/examples/core-item.json) and printing the existing properties." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'title': 'Core Item',\n", - " 'description': 'A sample STAC Item that includes examples of all common metadata',\n", - " 'datetime': None,\n", - " 'start_datetime': '2020-12-11T22:38:32.125Z',\n", - " 'end_datetime': '2020-12-11T22:38:32.327Z',\n", - " 'created': '2020-12-12T01:48:13.725Z',\n", - " 'updated': '2020-12-12T01:48:13.725Z',\n", - " 'platform': 'cool_sat1',\n", - " 'instruments': ['cool_sensor_v1'],\n", - " 'constellation': 'ion',\n", - " 'mission': 'collection 5624',\n", - " 'gsd': 0.512}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item = pystac.read_file(\n", - " \"https://raw.githubusercontent.com/radiantearth/stac-spec/master/examples/core-item.json\"\n", - ")\n", - "item.properties" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's verify that this Item does not implement our new Order Extension yet and that it does not already contain any of our Order Extension properties." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Implements Extension: False\n", - "Order ID: None\n", - "History:\n" - ] - } - ], - "source": [ - "print(f\"Implements Extension: {OrderExtension.has_extension(item)}\")\n", - "print(f\"Order ID: {item.properties.get(ID_PROP)}\")\n", - "print(\"History:\")\n", - "for event in item.properties.get(HISTORY_PROP, []):\n", - " pprint(event)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As expected, this Item does not implement the extension (i.e. the schema URI is not in the Item's `stac_extensions` list). Let's add it, create an instance of `OrderExtension` that extends the `Item`, and add some values for our extension fields." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "order_ext = OrderExtension.ext(item, add_if_missing=True)\n", - "\n", - "# Create a unique string ID for the order ID\n", - "order_ext.order_id = str(uuid4())\n", - "\n", - "# Create some fake order history and set it using the extension\n", - "event_1 = OrderEvent.create(\n", - " event_type=OrderEventType.SUBMITTED, timestamp=datetime.now() - timedelta(days=1)\n", - ")\n", - "event_2 = OrderEvent.create(\n", - " event_type=OrderEventType.STARTED_PROCESSING,\n", - " timestamp=datetime.now() - timedelta(hours=12),\n", - ")\n", - "event_3 = OrderEvent.create(\n", - " event_type=OrderEventType.DELIVERED, timestamp=datetime.now() - timedelta(hours=1)\n", - ")\n", - "order_ext.history = [event_1, event_2, event_3]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's check to see if these values were written to our Item properties." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Implements Extension: True\n", - "Order ID: 7a206229-78f0-46cb-afc2-acf45e14afab\n", - "History:\n", - "{'timestamp': '2023-10-11T11:21:50.989315Z', 'type': 'submitted'}\n", - "{'timestamp': '2023-10-11T23:21:50.989372Z', 'type': 'started_processing'}\n", - "{'timestamp': '2023-10-12T10:21:50.989403Z', 'type': 'delivered'}\n" - ] - } - ], - "source": [ - "print(f\"Implements Extension: {OrderExtension.has_extension(item)}\")\n", - "print(f\"Order ID: {item.properties.get(ID_PROP)}\")\n", - "print(\"History:\")\n", - "for event in item.properties.get(HISTORY_PROP, []):\n", - " pprint(event)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## (Optional) Add access via `Item.ext`\n", - "\n", - "_This applies if you are planning on opening a Pull Request to add this implementation of the extension class to the pystac library_\n", - "\n", - "Now that you have a complete extension class, you can add access to it via the `pystac.Item.ext` interface by following these steps:\n", - "\n", - "1) Make sure that your Extension class has a `name` attribute with `Literal()` as the type.\n", - "2) Import your Extension class in `pystac/extensions/ext.py`\n", - "3) Add the `name` to `EXTENSION_NAMES`\n", - "4) Add the mapping from name to class to `EXTENSION_NAME_MAPPING`\n", - "5) Add a getter method to the Ext class for any object type that this extension works with.\n", - "\n", - "Here is an example of the diff:\n", - "\n", - "```diff\n", - "diff --git a/pystac/extensions/ext.py b/pystac/extensions/ext.py\n", - "index 93a30fe..2dbe5ca 100644\n", - "--- a/pystac/extensions/ext.py\n", - "+++ b/pystac/extensions/ext.py\n", - "@@ -9,6 +9,7 @@ from pystac.extensions.file import FileExtension\n", - " from pystac.extensions.grid import GridExtension\n", - " from pystac.extensions.item_assets import AssetDefinition, ItemAssetsExtension\n", - " from pystac.extensions.mgrs import MgrsExtension\n", - "+from pystac.extensions.order import OrderExtension\n", - " from pystac.extensions.pointcloud import PointcloudExtension\n", - " from pystac.extensions.projection import ProjectionExtension\n", - " from pystac.extensions.raster import RasterExtension\n", - "@@ -32,6 +33,7 @@ EXTENSION_NAMES = Literal[\n", - " \"grid\",\n", - " \"item_assets\",\n", - " \"mgrs\",\n", - "+ \"order\",\n", - " \"pc\",\n", - " \"proj\",\n", - " \"raster\",\n", - "@@ -54,6 +56,7 @@ EXTENSION_NAME_MAPPING: Dict[EXTENSION_NAMES, Any] = {\n", - " GridExtension.name: GridExtension,\n", - " ItemAssetsExtension.name: ItemAssetsExtension,\n", - " MgrsExtension.name: MgrsExtension,\n", - "+ OrderExtension.name: OrderExtension,\n", - " PointcloudExtension.name: PointcloudExtension,\n", - " ProjectionExtension.name: ProjectionExtension,\n", - " RasterExtension.name: RasterExtension,\n", - "@@ -150,6 +153,10 @@ class ItemExt:\n", - " def mgrs(self) -> MgrsExtension:\n", - " return MgrsExtension.ext(self.stac_object)\n", - " \n", - "+ @property\n", - "+ def order(self) -> OrderExtension:\n", - "+ return OrderExtension.ext(self.stac_object)\n", - "+\n", - " @property\n", - " def pc(self) -> PointcloudExtension[pystac.Item]:\n", - " return PointcloudExtension.ext(self.stac_object)\n", - "```\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/tutorials/creating-a-landsat-stac.ipynb b/docs/tutorials/creating-a-landsat-stac.ipynb deleted file mode 100644 index 0aad54ffb..000000000 --- a/docs/tutorials/creating-a-landsat-stac.ipynb +++ /dev/null @@ -1,2542 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Creating a STAC of Landsat data\n", - "\n", - "In this tutorial we create a STAC of Landsat data provided by Microsoft's [Planetary Computer](https://planetarycomputer.microsoft.com/dataset/landsat-c2-l2). There's a lot of Landsat scenes, so we'll only take a subset of scenes that are from a specific year and over a specific location. We'll translate existing metadata about each scene to STAC information, utilizing the `eo`, `view`, `proj`, `raster` and `classification` extensions. Finally we'll write out the STAC catalog to our local machine, allowing us to use [stac-browser](https://github.com/radiantearth/stac-browser) to preview the images.\n", - "\n", - "### Requirements\n", - "\n", - "To run this tutorial you'll need to have installed PySTAC with the validation extra and the Planetary Computer package. To do this, use:\n", - "\n", - "```\n", - "pip install 'pystac[validation]' planetary-computer\n", - "```\n", - "\n", - "Also to run this notebook you'll need [jupyter](https://jupyter.org/) installed locally as well. If you're running in a docker container, make sure that port `5555` is exposed if you want to run the server at the end of the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "from datetime import datetime\n", - "from functools import partial\n", - "from os.path import dirname, join\n", - "from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast\n", - "from urllib.parse import urlparse, urlunparse\n", - "\n", - "import planetary_computer as pc\n", - "from dateutil.parser import parse\n", - "from typing_extensions import TypedDict\n", - "\n", - "import pystac\n", - "from pystac.extensions.classification import (\n", - " Bitfield,\n", - " Classification,\n", - " ClassificationExtension,\n", - ")\n", - "from pystac.extensions.eo import Band as EOBand\n", - "from pystac.extensions.eo import EOExtension\n", - "from pystac.extensions.projection import ProjectionExtension\n", - "from pystac.extensions.raster import RasterBand, RasterExtension\n", - "from pystac.extensions.view import ViewExtension" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Identify target scenes\n", - "\n", - "The Planetary Computer provides a STAC API that we could use to search for data within an area and time of interest, but since this notebook is intended to be a tutorial on creating STAC in the first place, doing so would put the cart ahead of the horse. Instead, we supply a list of metadata files for Landsat-8 and Landsat-9 scenes covering the center of Philadelphia, Pennsylvania in autumn of 2022 that we will work with:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "location_name = \"Philly\"\n", - "scene_mtls = [\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC09_L2SP_014032_20221211_20221213_02_T2/LC09_L2SP_014032_20221211_20221213_02_T2_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221203_20221212_02_T2/LC08_L2SP_014032_20221203_20221212_02_T2_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC09_L2SP_014032_20221125_20230320_02_T2/LC09_L2SP_014032_20221125_20230320_02_T2_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221117_20221128_02_T1/LC08_L2SP_014032_20221117_20221128_02_T1_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC09_L2SP_014032_20221109_20221111_02_T1/LC09_L2SP_014032_20221109_20221111_02_T1_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221101_20221114_02_T1/LC08_L2SP_014032_20221101_20221114_02_T1_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC09_L2SP_014032_20221024_20221026_02_T2/LC09_L2SP_014032_20221024_20221026_02_T2_MTL.xml\",\n", - " \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC09_L2SP_014032_20221008_20221010_02_T1/LC09_L2SP_014032_20221008_20221010_02_T1_MTL.xml\",\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read metadata from the MTL file\n", - "\n", - "Landsat metadata is contained in an `MTL` file that comes in either `.txt` or `.xml` formats. We'll rely on the XML version since it is more consistently available. This will require that we provide some facility for parsing the XML into a more usable format:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Taken from https://stackoverflow.com/questions/2148119/how-to-convert-an-xml-string-to-a-dictionary\n", - "from xml.etree import cElementTree as ElementTree\n", - "\n", - "\n", - "class XmlListConfig(list):\n", - " def __init__(self, aList):\n", - " for element in aList:\n", - " if element:\n", - " if len(element) == 1 or element[0].tag != element[1].tag:\n", - " self.append(XmlDictConfig(element))\n", - " elif element[0].tag == element[1].tag:\n", - " self.append(XmlListConfig(element))\n", - " elif element.text:\n", - " text = element.text.strip()\n", - " if text:\n", - " self.append(text)\n", - "\n", - "\n", - "class XmlDictConfig(dict):\n", - " def __init__(self, parent_element):\n", - " if parent_element.items():\n", - " self.update(dict(parent_element.items()))\n", - " for element in parent_element:\n", - " if element:\n", - " if len(element) == 1 or element[0].tag != element[1].tag:\n", - " aDict = XmlDictConfig(element)\n", - " else:\n", - " aDict = {element[0].tag: XmlListConfig(element)}\n", - " if element.items():\n", - " aDict.update(dict(element.items()))\n", - " self.update({element.tag: aDict})\n", - " elif element.items():\n", - " self.update({element.tag: dict(element.items())})\n", - " else:\n", - " self.update({element.tag: element.text})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can then use these classes to get the MTL file for our scene. Notice we use `pystac.STAC_IO.read_text`; this is the method that PySTAC uses to read text as it crawls a STAC. It can read from the local filesystem or HTTP/HTTPS by default. Also, it can be extended to read from other sources such as cloud providers—[see the documentation here](https://pystac.readthedocs.io/en/latest/concepts.html#using-stac-io). For now we'll use it directly as an easy way to read a text file from an HTTPS source." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "stac_io = pystac.StacIO.default()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since we're reading our files from the Planetary Computer's blob storage, we're also going to have to take the additional step of signing our requests using the `planetary-computer` package's `sign()` function. The raw URL is passed in, and the result has a shared access token applied. See the [planetary-computer Python package](https://github.com/microsoft/planetary-computer-sdk-for-python) for more details. We'll see the use of `pc.sign()` throughout the code below, and it will be necessary for asset HREFs to be passed through this function by the user of the catalog as well." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "def get_metadata(xml_url: str) -> Dict[str, Any]:\n", - " result = XmlDictConfig(ElementTree.XML(stac_io.read_text(pc.sign(xml_url))))\n", - " result[\"ORIGINAL_URL\"] = (\n", - " xml_url # Include the original URL in the metadata for use later\n", - " )\n", - " return result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's read the MTL file for the first scene and see what it looks like." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"PRODUCT_CONTENTS\": {\n", - " \"ORIGIN\": \"Image courtesy of the U.S. Geological Survey\",\n", - " \"DIGITAL_OBJECT_IDENTIFIER\": \"https://doi.org/10.5066/P9OGBGM6\",\n", - " \"LANDSAT_PRODUCT_ID\": \"LC08_L2SP_014032_20221219_20230113_02_T1\",\n", - " \"PROCESSING_LEVEL\": \"L2SP\",\n", - " \"COLLECTION_NUMBER\": \"02\",\n", - " \"COLLECTION_CATEGORY\": \"T1\",\n", - " \"OUTPUT_FORMAT\": \"GEOTIFF\",\n", - " \"FILE_NAME_BAND_1\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B1.TIF\",\n", - " \"FILE_NAME_BAND_2\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B2.TIF\",\n", - " \"FILE_NAME_BAND_3\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B3.TIF\",\n", - " \"FILE_NAME_BAND_4\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B4.TIF\",\n", - " \"FILE_NAME_BAND_5\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B5.TIF\",\n", - " \"FILE_NAME_BAND_6\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B6.TIF\",\n", - " \"FILE_NAME_BAND_7\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_B7.TIF\",\n", - " \"FILE_NAME_BAND_ST_B10\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_B10.TIF\",\n", - " \"FILE_NAME_THERMAL_RADIANCE\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_TRAD.TIF\",\n", - " \"FILE_NAME_UPWELL_RADIANCE\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_URAD.TIF\",\n", - " \"FILE_NAME_DOWNWELL_RADIANCE\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_DRAD.TIF\",\n", - " \"FILE_NAME_ATMOSPHERIC_TRANSMITTANCE\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_ATRAN.TIF\",\n", - " \"FILE_NAME_EMISSIVITY\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_EMIS.TIF\",\n", - " \"FILE_NAME_EMISSIVITY_STDEV\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_EMSD.TIF\",\n", - " \"FILE_NAME_CLOUD_DISTANCE\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_CDIST.TIF\",\n", - " \"FILE_NAME_QUALITY_L2_AEROSOL\": \"LC08_L2SP_014032_20221219_20230113_02_T1_SR_QA_AEROSOL.TIF\",\n", - " \"FILE_NAME_QUALITY_L2_SURFACE_TEMPERATURE\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ST_QA.TIF\",\n", - " \"FILE_NAME_QUALITY_L1_PIXEL\": \"LC08_L2SP_014032_20221219_20230113_02_T1_QA_PIXEL.TIF\",\n", - " \"FILE_NAME_QUALITY_L1_RADIOMETRIC_SATURATION\": \"LC08_L2SP_014032_20221219_20230113_02_T1_QA_RADSAT.TIF\",\n", - " \"FILE_NAME_ANGLE_COEFFICIENT\": \"LC08_L2SP_014032_20221219_20230113_02_T1_ANG.txt\",\n", - " \"FILE_NAME_METADATA_ODL\": \"LC08_L2SP_014032_20221219_20230113_02_T1_MTL.txt\",\n", - " \"FILE_NAME_METADATA_XML\": \"LC08_L2SP_014032_20221219_20230113_02_T1_MTL.xml\",\n", - " \"DATA_TYPE_BAND_1\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_2\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_3\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_4\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_5\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_6\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_7\": \"UINT16\",\n", - " \"DATA_TYPE_BAND_ST_B10\": \"UINT16\",\n", - " \"DATA_TYPE_THERMAL_RADIANCE\": \"INT16\",\n", - " \"DATA_TYPE_UPWELL_RADIANCE\": \"INT16\",\n", - " \"DATA_TYPE_DOWNWELL_RADIANCE\": \"INT16\",\n", - " \"DATA_TYPE_ATMOSPHERIC_TRANSMITTANCE\": \"INT16\",\n", - " \"DATA_TYPE_EMISSIVITY\": \"INT16\",\n", - " \"DATA_TYPE_EMISSIVITY_STDEV\": \"INT16\",\n", - " \"DATA_TYPE_CLOUD_DISTANCE\": \"INT16\",\n", - " \"DATA_TYPE_QUALITY_L2_AEROSOL\": \"UINT8\",\n", - " \"DATA_TYPE_QUALITY_L2_SURFACE_TEMPERATURE\": \"INT16\",\n", - " \"DATA_TYPE_QUALITY_L1_PIXEL\": \"UINT16\",\n", - " \"DATA_TYPE_QUALITY_L1_RADIOMETRIC_SATURATION\": \"UINT16\"\n", - " },\n", - " \"IMAGE_ATTRIBUTES\": {\n", - " \"SPACECRAFT_ID\": \"LANDSAT_8\",\n", - " \"SENSOR_ID\": \"OLI_TIRS\",\n", - " \"WRS_TYPE\": \"2\",\n", - " \"WRS_PATH\": \"14\",\n", - " \"WRS_ROW\": \"32\",\n", - " \"NADIR_OFFNADIR\": \"NADIR\",\n", - " \"TARGET_WRS_PATH\": \"14\",\n", - " \"TARGET_WRS_ROW\": \"32\",\n", - " \"DATE_ACQUIRED\": \"2022-12-19\",\n", - " \"SCENE_CENTER_TIME\": \"15:40:17.7299160Z\",\n", - " \"STATION_ID\": \"LGN\",\n", - " \"CLOUD_COVER\": \"43.42\",\n", - " \"CLOUD_COVER_LAND\": \"48.41\",\n", - " \"IMAGE_QUALITY_OLI\": \"9\",\n", - " \"IMAGE_QUALITY_TIRS\": \"9\",\n", - " \"SATURATION_BAND_1\": \"N\",\n", - " \"SATURATION_BAND_2\": \"Y\",\n", - " \"SATURATION_BAND_3\": \"N\",\n", - " \"SATURATION_BAND_4\": \"Y\",\n", - " \"SATURATION_BAND_5\": \"Y\",\n", - " \"SATURATION_BAND_6\": \"Y\",\n", - " \"SATURATION_BAND_7\": \"Y\",\n", - " \"SATURATION_BAND_8\": \"N\",\n", - " \"SATURATION_BAND_9\": \"N\",\n", - " \"ROLL_ANGLE\": \"-0.001\",\n", - " \"SUN_AZIMUTH\": \"160.86021018\",\n", - " \"SUN_ELEVATION\": \"23.81656674\",\n", - " \"EARTH_SUN_DISTANCE\": \"0.9839500\",\n", - " \"TRUNCATION_OLI\": \"UPPER\",\n", - " \"TIRS_SSM_MODEL\": \"FINAL\",\n", - " \"TIRS_SSM_POSITION_STATUS\": \"ESTIMATED\"\n", - " },\n", - " \"PROJECTION_ATTRIBUTES\": {\n", - " \"MAP_PROJECTION\": \"UTM\",\n", - " \"DATUM\": \"WGS84\",\n", - " \"ELLIPSOID\": \"WGS84\",\n", - " \"UTM_ZONE\": \"18\",\n", - " \"GRID_CELL_SIZE_REFLECTIVE\": \"30.00\",\n", - " \"GRID_CELL_SIZE_THERMAL\": \"30.00\",\n", - " \"REFLECTIVE_LINES\": \"7861\",\n", - " \"REFLECTIVE_SAMPLES\": \"7731\",\n", - " \"THERMAL_LINES\": \"7861\",\n", - " \"THERMAL_SAMPLES\": \"7731\",\n", - " \"ORIENTATION\": \"NORTH_UP\",\n", - " \"CORNER_UL_LAT_PRODUCT\": \"41.38441\",\n", - " \"CORNER_UL_LON_PRODUCT\": \"-76.26178\",\n", - " \"CORNER_UR_LAT_PRODUCT\": \"41.38140\",\n", - " \"CORNER_UR_LON_PRODUCT\": \"-73.48833\",\n", - " \"CORNER_LL_LAT_PRODUCT\": \"39.26052\",\n", - " \"CORNER_LL_LON_PRODUCT\": \"-76.22284\",\n", - " \"CORNER_LR_LAT_PRODUCT\": \"39.25773\",\n", - " \"CORNER_LR_LON_PRODUCT\": \"-73.53498\",\n", - " \"CORNER_UL_PROJECTION_X_PRODUCT\": \"394500.000\",\n", - " \"CORNER_UL_PROJECTION_Y_PRODUCT\": \"4582200.000\",\n", - " \"CORNER_UR_PROJECTION_X_PRODUCT\": \"626400.000\",\n", - " \"CORNER_UR_PROJECTION_Y_PRODUCT\": \"4582200.000\",\n", - " \"CORNER_LL_PROJECTION_X_PRODUCT\": \"394500.000\",\n", - " \"CORNER_LL_PROJECTION_Y_PRODUCT\": \"4346400.000\",\n", - " \"CORNER_LR_PROJECTION_X_PRODUCT\": \"626400.000\",\n", - " \"CORNER_LR_PROJECTION_Y_PRODUCT\": \"4346400.000\"\n", - " },\n", - " \"LEVEL2_PROCESSING_RECORD\": {\n", - " \"ORIGIN\": \"Image courtesy of the U.S. Geological Survey\",\n", - " \"DIGITAL_OBJECT_IDENTIFIER\": \"https://doi.org/10.5066/P9OGBGM6\",\n", - " \"REQUEST_ID\": \"1626123_00008\",\n", - " \"LANDSAT_PRODUCT_ID\": \"LC08_L2SP_014032_20221219_20230113_02_T1\",\n", - " \"PROCESSING_LEVEL\": \"L2SP\",\n", - " \"OUTPUT_FORMAT\": \"GEOTIFF\",\n", - " \"DATE_PRODUCT_GENERATED\": \"2023-01-13T02:53:40Z\",\n", - " \"PROCESSING_SOFTWARE_VERSION\": \"LPGS_16.1.0\",\n", - " \"ALGORITHM_SOURCE_SURFACE_REFLECTANCE\": \"LaSRC_1.5.0\",\n", - " \"DATA_SOURCE_OZONE\": \"MODIS\",\n", - " \"DATA_SOURCE_PRESSURE\": \"Calculated\",\n", - " \"DATA_SOURCE_WATER_VAPOR\": \"MODIS\",\n", - " \"DATA_SOURCE_AIR_TEMPERATURE\": \"MODIS\",\n", - " \"ALGORITHM_SOURCE_SURFACE_TEMPERATURE\": \"st_1.3.0\",\n", - " \"DATA_SOURCE_REANALYSIS\": \"GEOS-5 FP-IT\"\n", - " },\n", - " \"LEVEL2_SURFACE_REFLECTANCE_PARAMETERS\": {\n", - " \"REFLECTANCE_MAXIMUM_BAND_1\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_1\": \"-0.199972\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_2\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_2\": \"-0.199972\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_3\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_3\": \"-0.199972\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_4\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_4\": \"-0.199972\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_5\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_5\": \"-0.199972\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_6\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_6\": \"-0.199972\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_7\": \"1.602213\",\n", - " \"REFLECTANCE_MINIMUM_BAND_7\": \"-0.199972\",\n", - " \"QUANTIZE_CAL_MAX_BAND_1\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_1\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_2\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_2\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_3\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_3\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_4\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_4\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_5\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_5\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_6\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_6\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_7\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_7\": \"1\",\n", - " \"REFLECTANCE_MULT_BAND_1\": \"2.75e-05\",\n", - " \"REFLECTANCE_MULT_BAND_2\": \"2.75e-05\",\n", - " \"REFLECTANCE_MULT_BAND_3\": \"2.75e-05\",\n", - " \"REFLECTANCE_MULT_BAND_4\": \"2.75e-05\",\n", - " \"REFLECTANCE_MULT_BAND_5\": \"2.75e-05\",\n", - " \"REFLECTANCE_MULT_BAND_6\": \"2.75e-05\",\n", - " \"REFLECTANCE_MULT_BAND_7\": \"2.75e-05\",\n", - " \"REFLECTANCE_ADD_BAND_1\": \"-0.2\",\n", - " \"REFLECTANCE_ADD_BAND_2\": \"-0.2\",\n", - " \"REFLECTANCE_ADD_BAND_3\": \"-0.2\",\n", - " \"REFLECTANCE_ADD_BAND_4\": \"-0.2\",\n", - " \"REFLECTANCE_ADD_BAND_5\": \"-0.2\",\n", - " \"REFLECTANCE_ADD_BAND_6\": \"-0.2\",\n", - " \"REFLECTANCE_ADD_BAND_7\": \"-0.2\"\n", - " },\n", - " \"LEVEL2_SURFACE_TEMPERATURE_PARAMETERS\": {\n", - " \"TEMPERATURE_MAXIMUM_BAND_ST_B10\": \"372.999941\",\n", - " \"TEMPERATURE_MINIMUM_BAND_ST_B10\": \"149.003418\",\n", - " \"QUANTIZE_CAL_MAXIMUM_BAND_ST_B10\": \"65535\",\n", - " \"QUANTIZE_CAL_MINIMUM_BAND_ST_B10\": \"1\",\n", - " \"TEMPERATURE_MULT_BAND_ST_B10\": \"0.00341802\",\n", - " \"TEMPERATURE_ADD_BAND_ST_B10\": \"149.0\"\n", - " },\n", - " \"LEVEL1_PROCESSING_RECORD\": {\n", - " \"ORIGIN\": \"Image courtesy of the U.S. Geological Survey\",\n", - " \"DIGITAL_OBJECT_IDENTIFIER\": \"https://doi.org/10.5066/P975CC9B\",\n", - " \"REQUEST_ID\": \"1626123_00008\",\n", - " \"LANDSAT_SCENE_ID\": \"LC80140322022353LGN00\",\n", - " \"LANDSAT_PRODUCT_ID\": \"LC08_L1TP_014032_20221219_20230113_02_T1\",\n", - " \"PROCESSING_LEVEL\": \"L1TP\",\n", - " \"COLLECTION_CATEGORY\": \"T1\",\n", - " \"OUTPUT_FORMAT\": \"GEOTIFF\",\n", - " \"DATE_PRODUCT_GENERATED\": \"2023-01-13T02:38:55Z\",\n", - " \"PROCESSING_SOFTWARE_VERSION\": \"LPGS_16.1.0\",\n", - " \"FILE_NAME_BAND_1\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B1.TIF\",\n", - " \"FILE_NAME_BAND_2\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B2.TIF\",\n", - " \"FILE_NAME_BAND_3\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B3.TIF\",\n", - " \"FILE_NAME_BAND_4\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B4.TIF\",\n", - " \"FILE_NAME_BAND_5\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B5.TIF\",\n", - " \"FILE_NAME_BAND_6\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B6.TIF\",\n", - " \"FILE_NAME_BAND_7\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B7.TIF\",\n", - " \"FILE_NAME_BAND_8\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B8.TIF\",\n", - " \"FILE_NAME_BAND_9\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B9.TIF\",\n", - " \"FILE_NAME_BAND_10\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B10.TIF\",\n", - " \"FILE_NAME_BAND_11\": \"LC08_L1TP_014032_20221219_20230113_02_T1_B11.TIF\",\n", - " \"FILE_NAME_QUALITY_L1_PIXEL\": \"LC08_L1TP_014032_20221219_20230113_02_T1_QA_PIXEL.TIF\",\n", - " \"FILE_NAME_QUALITY_L1_RADIOMETRIC_SATURATION\": \"LC08_L1TP_014032_20221219_20230113_02_T1_QA_RADSAT.TIF\",\n", - " \"FILE_NAME_ANGLE_COEFFICIENT\": \"LC08_L1TP_014032_20221219_20230113_02_T1_ANG.txt\",\n", - " \"FILE_NAME_ANGLE_SENSOR_AZIMUTH_BAND_4\": \"LC08_L1TP_014032_20221219_20230113_02_T1_VAA.TIF\",\n", - " \"FILE_NAME_ANGLE_SENSOR_ZENITH_BAND_4\": \"LC08_L1TP_014032_20221219_20230113_02_T1_VZA.TIF\",\n", - " \"FILE_NAME_ANGLE_SOLAR_AZIMUTH_BAND_4\": \"LC08_L1TP_014032_20221219_20230113_02_T1_SAA.TIF\",\n", - " \"FILE_NAME_ANGLE_SOLAR_ZENITH_BAND_4\": \"LC08_L1TP_014032_20221219_20230113_02_T1_SZA.TIF\",\n", - " \"FILE_NAME_METADATA_ODL\": \"LC08_L1TP_014032_20221219_20230113_02_T1_MTL.txt\",\n", - " \"FILE_NAME_METADATA_XML\": \"LC08_L1TP_014032_20221219_20230113_02_T1_MTL.xml\",\n", - " \"FILE_NAME_CPF\": \"LC08CPF_20221001_20221231_02.03\",\n", - " \"FILE_NAME_BPF_OLI\": \"LO8BPF20221219152831_20221219170353.01\",\n", - " \"FILE_NAME_BPF_TIRS\": \"LT8BPF20221215135451_20221222101440.01\",\n", - " \"FILE_NAME_RLUT\": \"LC08RLUT_20150303_20431231_02_01.h5\",\n", - " \"DATA_SOURCE_TIRS_STRAY_LIGHT_CORRECTION\": \"TIRS\",\n", - " \"DATA_SOURCE_ELEVATION\": \"GLS2000\",\n", - " \"GROUND_CONTROL_POINTS_VERSION\": \"5\",\n", - " \"GROUND_CONTROL_POINTS_MODEL\": \"462\",\n", - " \"GEOMETRIC_RMSE_MODEL\": \"8.179\",\n", - " \"GEOMETRIC_RMSE_MODEL_Y\": \"7.213\",\n", - " \"GEOMETRIC_RMSE_MODEL_X\": \"3.856\",\n", - " \"GROUND_CONTROL_POINTS_VERIFY\": \"120\",\n", - " \"GEOMETRIC_RMSE_VERIFY\": \"7.426\"\n", - " },\n", - " \"LEVEL1_MIN_MAX_RADIANCE\": {\n", - " \"RADIANCE_MAXIMUM_BAND_1\": \"785.06079\",\n", - " \"RADIANCE_MINIMUM_BAND_1\": \"-64.83057\",\n", - " \"RADIANCE_MAXIMUM_BAND_2\": \"803.91187\",\n", - " \"RADIANCE_MINIMUM_BAND_2\": \"-66.38730\",\n", - " \"RADIANCE_MAXIMUM_BAND_3\": \"740.79791\",\n", - " \"RADIANCE_MINIMUM_BAND_3\": \"-61.17533\",\n", - " \"RADIANCE_MAXIMUM_BAND_4\": \"624.68250\",\n", - " \"RADIANCE_MINIMUM_BAND_4\": \"-51.58648\",\n", - " \"RADIANCE_MAXIMUM_BAND_5\": \"382.27454\",\n", - " \"RADIANCE_MINIMUM_BAND_5\": \"-31.56836\",\n", - " \"RADIANCE_MAXIMUM_BAND_6\": \"95.06820\",\n", - " \"RADIANCE_MINIMUM_BAND_6\": \"-7.85076\",\n", - " \"RADIANCE_MAXIMUM_BAND_7\": \"32.04307\",\n", - " \"RADIANCE_MINIMUM_BAND_7\": \"-2.64613\",\n", - " \"RADIANCE_MAXIMUM_BAND_8\": \"706.96869\",\n", - " \"RADIANCE_MINIMUM_BAND_8\": \"-58.38170\",\n", - " \"RADIANCE_MAXIMUM_BAND_9\": \"149.40157\",\n", - " \"RADIANCE_MINIMUM_BAND_9\": \"-12.33763\",\n", - " \"RADIANCE_MAXIMUM_BAND_10\": \"22.00180\",\n", - " \"RADIANCE_MINIMUM_BAND_10\": \"0.10033\",\n", - " \"RADIANCE_MAXIMUM_BAND_11\": \"22.00180\",\n", - " \"RADIANCE_MINIMUM_BAND_11\": \"0.10033\"\n", - " },\n", - " \"LEVEL1_MIN_MAX_REFLECTANCE\": {\n", - " \"REFLECTANCE_MAXIMUM_BAND_1\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_1\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_2\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_2\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_3\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_3\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_4\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_4\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_5\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_5\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_6\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_6\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_7\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_7\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_8\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_8\": \"-0.099980\",\n", - " \"REFLECTANCE_MAXIMUM_BAND_9\": \"1.210700\",\n", - " \"REFLECTANCE_MINIMUM_BAND_9\": \"-0.099980\"\n", - " },\n", - " \"LEVEL1_MIN_MAX_PIXEL_VALUE\": {\n", - " \"QUANTIZE_CAL_MAX_BAND_1\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_1\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_2\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_2\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_3\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_3\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_4\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_4\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_5\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_5\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_6\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_6\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_7\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_7\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_8\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_8\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_9\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_9\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_10\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_10\": \"1\",\n", - " \"QUANTIZE_CAL_MAX_BAND_11\": \"65535\",\n", - " \"QUANTIZE_CAL_MIN_BAND_11\": \"1\"\n", - " },\n", - " \"LEVEL1_RADIOMETRIC_RESCALING\": {\n", - " \"RADIANCE_MULT_BAND_1\": \"1.2969E-02\",\n", - " \"RADIANCE_MULT_BAND_2\": \"1.3280E-02\",\n", - " \"RADIANCE_MULT_BAND_3\": \"1.2238E-02\",\n", - " \"RADIANCE_MULT_BAND_4\": \"1.0319E-02\",\n", - " \"RADIANCE_MULT_BAND_5\": \"6.3149E-03\",\n", - " \"RADIANCE_MULT_BAND_6\": \"1.5705E-03\",\n", - " \"RADIANCE_MULT_BAND_7\": \"5.2933E-04\",\n", - " \"RADIANCE_MULT_BAND_8\": \"1.1679E-02\",\n", - " \"RADIANCE_MULT_BAND_9\": \"2.4680E-03\",\n", - " \"RADIANCE_MULT_BAND_10\": \"3.3420E-04\",\n", - " \"RADIANCE_MULT_BAND_11\": \"3.3420E-04\",\n", - " \"RADIANCE_ADD_BAND_1\": \"-64.84355\",\n", - " \"RADIANCE_ADD_BAND_2\": \"-66.40058\",\n", - " \"RADIANCE_ADD_BAND_3\": \"-61.18757\",\n", - " \"RADIANCE_ADD_BAND_4\": \"-51.59680\",\n", - " \"RADIANCE_ADD_BAND_5\": \"-31.57467\",\n", - " \"RADIANCE_ADD_BAND_6\": \"-7.85233\",\n", - " \"RADIANCE_ADD_BAND_7\": \"-2.64666\",\n", - " \"RADIANCE_ADD_BAND_8\": \"-58.39338\",\n", - " \"RADIANCE_ADD_BAND_9\": \"-12.34010\",\n", - " \"RADIANCE_ADD_BAND_10\": \"0.10000\",\n", - " \"RADIANCE_ADD_BAND_11\": \"0.10000\",\n", - " \"REFLECTANCE_MULT_BAND_1\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_2\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_3\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_4\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_5\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_6\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_7\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_8\": \"2.0000E-05\",\n", - " \"REFLECTANCE_MULT_BAND_9\": \"2.0000E-05\",\n", - " \"REFLECTANCE_ADD_BAND_1\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_2\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_3\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_4\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_5\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_6\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_7\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_8\": \"-0.100000\",\n", - " \"REFLECTANCE_ADD_BAND_9\": \"-0.100000\"\n", - " },\n", - " \"LEVEL1_THERMAL_CONSTANTS\": {\n", - " \"K1_CONSTANT_BAND_10\": \"774.8853\",\n", - " \"K2_CONSTANT_BAND_10\": \"1321.0789\",\n", - " \"K1_CONSTANT_BAND_11\": \"480.8883\",\n", - " \"K2_CONSTANT_BAND_11\": \"1201.1442\"\n", - " },\n", - " \"LEVEL1_PROJECTION_PARAMETERS\": {\n", - " \"MAP_PROJECTION\": \"UTM\",\n", - " \"DATUM\": \"WGS84\",\n", - " \"ELLIPSOID\": \"WGS84\",\n", - " \"UTM_ZONE\": \"18\",\n", - " \"GRID_CELL_SIZE_PANCHROMATIC\": \"15.00\",\n", - " \"GRID_CELL_SIZE_REFLECTIVE\": \"30.00\",\n", - " \"GRID_CELL_SIZE_THERMAL\": \"30.00\",\n", - " \"ORIENTATION\": \"NORTH_UP\",\n", - " \"RESAMPLING_OPTION\": \"CUBIC_CONVOLUTION\"\n", - " },\n", - " \"ORIGINAL_URL\": \"https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_MTL.xml\"\n", - "}\n" - ] - } - ], - "source": [ - "metadata = get_metadata(scene_mtls[0])\n", - "print(json.dumps(metadata, indent=4))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are a number of files referred to by this metadata file which are in the same tree in the cloud. We must provide an easy means for creating a URL for these sidecar files. We can then use `partial` to create a helper function to turn a file name into a URL." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "def download_sidecar(metadata: Dict[str, Any], filename: str) -> str:\n", - " parsed = urlparse(metadata[\"ORIGINAL_URL\"])\n", - " return urlunparse(parsed._replace(path=join(dirname(parsed.path), filename)))" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "download_url = partial(download_sidecar, metadata)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a STAC Item from a scene\n", - "\n", - "Now that we have metadata for the scene let's use it to create a [STAC Item](https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md).\n", - "\n", - "We can use the `help` method to see the signature of the `__init__` method on `pystac.Item`. You can also call `help` directly on `pystac.Item` for broader documentation, or check the [API docs for Item here](https://pystac.readthedocs.io/en/latest/api.html#item)." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function __init__ in module pystac.item:\n", - "\n", - "__init__(self, id: 'str', geometry: 'Optional[Dict[str, Any]]', bbox: 'Optional[List[float]]', datetime: 'Optional[Datetime]', properties: 'Dict[str, Any]', start_datetime: 'Optional[Datetime]' = None, end_datetime: 'Optional[Datetime]' = None, stac_extensions: 'Optional[List[str]]' = None, href: 'Optional[str]' = None, collection: 'Optional[Union[str, Collection]]' = None, extra_fields: 'Optional[Dict[str, Any]]' = None, assets: 'Optional[Dict[str, Asset]]' = None)\n", - " Initialize self. See help(type(self)) for accurate signature.\n", - "\n" - ] - } - ], - "source": [ - "help(pystac.Item.__init__)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see we'll need at least an `id`, `geometry`, `bbox`, and `datetime`. Properties is required, but can be an empty dictionary that we fill out on the Item once it's created.\n", - "\n", - "> Caution! The `Optional` type hint is used when None can be provided in place of a meaningful argument; it does not indicate that the argument does not need to be supplied—that is only true if a default value is indicated in the type signature." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Item `id`\n", - "\n", - "For the Item's `id`, we'll use the scene ID. We'll chop off the last 5 characters as they are repeated for each ID and so aren't necessary: " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "def get_item_id(metadata: Dict[str, Any]) -> str:\n", - " return cast(str, metadata[\"LEVEL1_PROCESSING_RECORD\"][\"LANDSAT_SCENE_ID\"][:-5])" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'LC80140322022353'" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_id = get_item_id(metadata)\n", - "item_id" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Item `datetime`\n", - "\n", - "Here we parse the datetime of the Item from two metadata fields that describe the date and time the scene was captured:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "def get_datetime(metadata: Dict[str, Any]) -> datetime:\n", - " return parse(\n", - " \"%sT%s\"\n", - " % (\n", - " metadata[\"IMAGE_ATTRIBUTES\"][\"DATE_ACQUIRED\"],\n", - " metadata[\"IMAGE_ATTRIBUTES\"][\"SCENE_CENTER_TIME\"],\n", - " )\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "datetime.datetime(2022, 12, 19, 15, 40, 17, 729916, tzinfo=tzutc())" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_datetime = get_datetime(metadata)\n", - "item_datetime" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Item `bbox`\n", - "\n", - "Here we read in the bounding box information from the scene and transform it into the format of the Item's `bbox` property:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "def get_bbox(metadata: Dict[str, Any]) -> List[float]:\n", - " metadata = metadata[\"PROJECTION_ATTRIBUTES\"]\n", - " coords = [\n", - " [\n", - " [\n", - " float(metadata[\"CORNER_UL_LON_PRODUCT\"]),\n", - " float(metadata[\"CORNER_UL_LAT_PRODUCT\"]),\n", - " ],\n", - " [\n", - " float(metadata[\"CORNER_UR_LON_PRODUCT\"]),\n", - " float(metadata[\"CORNER_UR_LAT_PRODUCT\"]),\n", - " ],\n", - " [\n", - " float(metadata[\"CORNER_LR_LON_PRODUCT\"]),\n", - " float(metadata[\"CORNER_LR_LAT_PRODUCT\"]),\n", - " ],\n", - " [\n", - " float(metadata[\"CORNER_LL_LON_PRODUCT\"]),\n", - " float(metadata[\"CORNER_LL_LAT_PRODUCT\"]),\n", - " ],\n", - " [\n", - " float(metadata[\"CORNER_UL_LON_PRODUCT\"]),\n", - " float(metadata[\"CORNER_UL_LAT_PRODUCT\"]),\n", - " ],\n", - " ]\n", - " ]\n", - " lats = [c[1] for c in coords[0]]\n", - " lons = [c[0] for c in coords[0]]\n", - " return [min(lons), min(lats), max(lons), max(lats)]" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[-76.26178, 39.25773, -73.48833, 41.38441]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_bbox = get_bbox(metadata)\n", - "item_bbox" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Item `geometry`\n", - "\n", - "Getting the geometry of the scene is a little more tricky. The bounding box will be a axis-aligned rectangle of the area the scene occupies, but will not represent the true footprint of the image - Landsat scenes are \"tilted\" according the the coordinate reference system, so there will be areas in the corner where no image data exists. When constructing a STAC Item it's best if you have the Item geometry represent the true footprint of the assets.\n", - "\n", - "To get the footprint of the scene we'll read in another metadata file that lives alongside the MTL - the `ANG.txt` file. This function uses the ANG file and the bbox to construct the GeoJSON polygon that represents the footprint of the scene:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "def get_geometry(metadata: Dict[str, Any], bbox: List[float]) -> Dict[str, Any]:\n", - " url = download_sidecar(\n", - " metadata, metadata[\"PRODUCT_CONTENTS\"][\"FILE_NAME_ANGLE_COEFFICIENT\"]\n", - " )\n", - " sz = []\n", - " coords = []\n", - " ang_text = stac_io.read_text(pc.sign(url))\n", - " if not ang_text.startswith(\"GROUP\"):\n", - " raise ValueError(f\"ANG file for url {url} is incorrectly formatted\")\n", - " for line in ang_text.split(\"\\n\"):\n", - " if \"BAND01_NUM_L1T_LINES\" in line or \"BAND01_NUM_L1T_SAMPS\" in line:\n", - " sz.append(float(line.split(\"=\")[1]))\n", - " if (\n", - " \"BAND01_L1T_IMAGE_CORNER_LINES\" in line\n", - " or \"BAND01_L1T_IMAGE_CORNER_SAMPS\" in line\n", - " ):\n", - " coords.append(\n", - " [float(v) for v in line.split(\"=\")[1].strip().strip(\"()\").split(\",\")]\n", - " )\n", - " if len(coords) == 2:\n", - " break\n", - " dlon = bbox[2] - bbox[0]\n", - " dlat = bbox[3] - bbox[1]\n", - " lons = [c / sz[1] * dlon + bbox[0] for c in coords[1]]\n", - " lats = [((sz[0] - c) / sz[0]) * dlat + bbox[1] for c in coords[0]]\n", - " coordinates = [\n", - " [\n", - " [lons[0], lats[0]],\n", - " [lons[1], lats[1]],\n", - " [lons[2], lats[2]],\n", - " [lons[3], lats[3]],\n", - " [lons[0], lats[0]],\n", - " ]\n", - " ]\n", - "\n", - " return {\"type\": \"Polygon\", \"coordinates\": coordinates}" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"type\": \"Polygon\",\n", - " \"coordinates\": [\n", - " [\n", - " [\n", - " -75.71075270108336,\n", - " 41.3823086369878\n", - " ],\n", - " [\n", - " -73.48924866988654,\n", - " 40.980654308234485\n", - " ],\n", - " [\n", - " -74.0425618957281,\n", - " 39.25823722657151\n", - " ],\n", - " [\n", - " -76.26093009667797,\n", - " 39.66800780107756\n", - " ],\n", - " [\n", - " -75.71075270108336,\n", - " 41.3823086369878\n", - " ]\n", - " ]\n", - " ]\n", - "}\n" - ] - } - ], - "source": [ - "item_geometry = get_geometry(metadata, item_bbox)\n", - "print(json.dumps(item_geometry, indent=2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This would be a good time to check our work - we can print out the GeoJSON and use [geojson.io](https://geojson.io/) to check and make sure we're using scenes that overlap our location. If this footprint is somewhere unexpected in the world, make sure the Lat/Long coordinates are correct and in the right order!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create the item\n", - "\n", - "Now that we have the required attributes for an Item we can create it:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "item = pystac.Item(\n", - " id=item_id,\n", - " datetime=item_datetime,\n", - " geometry=item_geometry,\n", - " bbox=item_bbox,\n", - " properties={},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "PySTAC has a `validate` method on STAC objects, which you can use to make sure you're constructing things correctly. If there's an issue the following line will throw an exception:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json']" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.validate()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Add Ground Sample Distance to common metadata\n", - "\n", - "We'll add the Ground Sample Distance that is defined as part of the Item [Common Metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md). We define this on the Item level as 30 meters, which is the GSD for most of the Landsat bands. However, if some bands have a different resolution; we can account for this by setting the GSD explicitly for each of those bands below." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "item.common_metadata.gsd = 30.0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Adding the EO extension\n", - "\n", - "STAC has a rich [set of extensions](https://stac-extensions.github.io/) that allow STAC objects to encode information that is not part of the core spec but is used widely and standardized. These extensions allow us to augment STAC objects with additional structured metadata that describe referenced data with semantically-meaningful fields. An example of this is the [eo extension](https://github.com/stac-extensions/eo), which captures fields needed for electro-optical data, like center wavelength and full-width half maximum values.\n", - "\n", - "This notebook will also rely on other extensions; but as they will apply to different objects, not just the item itself, they will be invoked later.\n", - "\n", - "For now, we will enable the EO extension for this item by using the `ext` property provided by the extension object:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "eo_ext = EOExtension.ext(item, add_if_missing=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Add cloud cover\n", - "\n", - "Here we add cloud cover from the metadata as part of the `eo` extension." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "def get_cloud_cover(metadata: Dict[str, Any]) -> float:\n", - " return float(metadata[\"IMAGE_ATTRIBUTES\"][\"CLOUD_COVER\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "43.42" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "eo_ext.cloud_cover = get_cloud_cover(metadata)\n", - "eo_ext.cloud_cover" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding assets\n", - "\n", - "STAC Items contain a list of [Assets](https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md#asset-object), which are a list of files that relate to the Item. In our case we'll be cataloging each file related to the scene, including the Landsat band files as well as the metadata files associated with the scene.\n", - "\n", - "Each asset will have a name, some basic properties, and then possibly some properties defined by the various extensions in use (`eo`, `raster`, and `classification`). So, we begin by defining a type alias for this package of information and some helper functions for creating them:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "class BandInfo(TypedDict):\n", - " name: str\n", - " asset_fields: Dict[str, str]\n", - " extensions: List[Union[EOBand, RasterBand, List[Bitfield]]]\n", - "\n", - "\n", - "def eo_band_info(\n", - " common_name: str,\n", - " href: str,\n", - " name: str,\n", - " description: str,\n", - " center: float,\n", - " fwhm: float,\n", - " default_raster_band: Optional[RasterBand] = None,\n", - "):\n", - " raster_band = (\n", - " RasterBand.create(\n", - " spatial_resolution=30.0,\n", - " scale=0.0000275,\n", - " nodata=0,\n", - " offset=-0.2,\n", - " data_type=\"uint16\",\n", - " )\n", - " if default_raster_band is None\n", - " else default_raster_band\n", - " )\n", - " return {\n", - " \"name\": common_name,\n", - " \"asset_fields\": {\n", - " \"href\": href,\n", - " \"media_type\": str(pystac.media_type.MediaType.COG),\n", - " },\n", - " \"extensions\": [\n", - " EOBand.create(\n", - " name=name,\n", - " common_name=common_name,\n", - " description=description,\n", - " center_wavelength=center,\n", - " full_width_half_max=fwhm,\n", - " ),\n", - " raster_band,\n", - " ],\n", - " }\n", - "\n", - "\n", - "def plain_band_info(name: str, href: str, title: str, ext: RasterBand):\n", - " return {\n", - " \"name\": name,\n", - " \"asset_fields\": {\n", - " \"href\": href,\n", - " \"media_type\": str(pystac.media_type.MediaType.COG),\n", - " \"title\": title,\n", - " },\n", - " \"extensions\": [ext],\n", - " }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some common raster band information definitions will also be useful." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "thermal_raster_band = RasterBand.create(\n", - " spatial_resolution=30.0,\n", - " scale=0.00341802,\n", - " nodata=0,\n", - " offset=149.0,\n", - " data_type=\"uint6\",\n", - " unit=\"kelvin\",\n", - ")\n", - "radiance_raster_band = RasterBand.create(\n", - " unit=\"watt/steradian/square_meter/micrometer\",\n", - " scale=1e-3,\n", - " nodata=-9999,\n", - " data_type=\"uint16\",\n", - " spatial_resolution=30.0,\n", - ")\n", - "emissivity_transmission_raster_band = RasterBand.create(\n", - " scale=1e-4,\n", - " nodata=-9999,\n", - " data_type=\"int16\",\n", - " spatial_resolution=30.0,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Several QA bands are provided that utilize bit-wise masks which we can define using the [classification extension](https://github.com/stac-extensions/classification). Because these definitions can be verbose, we provide some additional helper functions to minimize the length of their definition." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "def create_bitfield(\n", - " offset: int,\n", - " length: int,\n", - " name: str,\n", - " field_names_descriptions: List[Tuple[str, str]],\n", - " description: Optional[str] = None,\n", - ") -> Bitfield:\n", - " return Bitfield.create(\n", - " offset=offset,\n", - " length=length,\n", - " name=name,\n", - " description=description,\n", - " classes=[\n", - " Classification.create(value=i, name=n, description=d)\n", - " for (i, (n, d)) in enumerate(field_names_descriptions)\n", - " ],\n", - " )\n", - "\n", - "\n", - "def create_qa_bitfield(\n", - " offset: int,\n", - " class_name: str,\n", - " description: Optional[Union[str, Tuple[str, str]]] = None,\n", - ") -> Bitfield:\n", - " if description is None:\n", - " descr0 = f\"{class_name.replace('_', ' ').capitalize()} confidence is not high\"\n", - " descr1 = f\"High confidence {class_name.replace('_', ' ')}\"\n", - " elif isinstance(description, str):\n", - " descr0 = f\"{description.capitalize()} confidence is not high\"\n", - " descr1 = f\"High confidence {description.lower()}\"\n", - " else:\n", - " descr0 = description[0]\n", - " descr1 = description[1]\n", - "\n", - " return create_bitfield(\n", - " offset, 1, class_name, [(f\"not_{class_name}\", descr0), (class_name, descr1)]\n", - " )\n", - "\n", - "\n", - "def create_confidence_bitfield(\n", - " offset: int, class_name: str, use_medium: bool = False\n", - ") -> Bitfield:\n", - " label = class_name.replace(\"_\", \" \")\n", - " return create_bitfield(\n", - " offset,\n", - " 2,\n", - " f\"{class_name}_confidence\",\n", - " [\n", - " (\"not_set\", \"No confidence level set\"),\n", - " (\"low\", f\"Low confidence {label}\"),\n", - " (\n", - " (\"medium\", f\"Medium confidence {label}\")\n", - " if use_medium\n", - " else (\"reserved\", \"Reserved - value not used\")\n", - " ),\n", - " (\"high\", f\"High confidence {label}\"),\n", - " ],\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now create the `BandInfo` definitions for the Landsat scenes. This begins with the definition of a function to convert metadata into a list of `BandInfo` records, which is lengthy but ultimately straightforward." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "def landsat_band_info(\n", - " metadata: Dict[str, Any], downloader: Callable[[str], str]\n", - ") -> List[BandInfo]:\n", - " product_contents = metadata[\"PRODUCT_CONTENTS\"]\n", - " return [\n", - " {\n", - " \"name\": \"ang\",\n", - " \"asset_fields\": {\n", - " \"href\": downloader(product_contents[\"FILE_NAME_ANGLE_COEFFICIENT\"]),\n", - " \"media_type\": \"text/plain\",\n", - " \"title\": \"Angle coefficients\",\n", - " },\n", - " \"extensions\": [],\n", - " },\n", - " {\n", - " \"name\": \"mtl.txt\",\n", - " \"asset_fields\": {\n", - " \"href\": downloader(product_contents[\"FILE_NAME_METADATA_ODL\"]),\n", - " \"media_type\": \"text/plain\",\n", - " \"title\": \"Product metadata\",\n", - " },\n", - " \"extensions\": [],\n", - " },\n", - " eo_band_info(\n", - " \"coastal\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_1\"]),\n", - " \"OLI_B1\",\n", - " \"Coastal/Aerosol (Operational Land Imager)\",\n", - " 0.44,\n", - " 0.02,\n", - " ),\n", - " eo_band_info(\n", - " \"blue\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_2\"]),\n", - " \"OLI_B2\",\n", - " \"Visible blue (Operational Land Imager)\",\n", - " 0.48,\n", - " 0.06,\n", - " ),\n", - " eo_band_info(\n", - " \"green\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_3\"]),\n", - " \"OLI_B3\",\n", - " \"Visible green (Operational Land Imager)\",\n", - " 0.56,\n", - " 0.06,\n", - " ),\n", - " eo_band_info(\n", - " \"red\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_4\"]),\n", - " \"OLI_B4\",\n", - " \"Visible red (Operational Land Imager)\",\n", - " 0.65,\n", - " 0.04,\n", - " ),\n", - " eo_band_info(\n", - " \"nir08\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_5\"]),\n", - " \"OLI_B5\",\n", - " \"Near infrared (Operational Land Imager)\",\n", - " 0.87,\n", - " 0.03,\n", - " ),\n", - " eo_band_info(\n", - " \"swir16\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_6\"]),\n", - " \"OLI_B6\",\n", - " \"Short-wave infrared (Operational Land Imager)\",\n", - " 1.61,\n", - " 0.09,\n", - " ),\n", - " eo_band_info(\n", - " \"swir22\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_7\"]),\n", - " \"OLI_B7\",\n", - " \"Short-wave infrared (Operational Land Imager)\",\n", - " 2.2,\n", - " 0.19,\n", - " ),\n", - " eo_band_info(\n", - " \"lwir11\",\n", - " downloader(product_contents[\"FILE_NAME_BAND_ST_B10\"]),\n", - " \"TIRS_B10\",\n", - " \"Long-wave infrared (Thermal InfraRed Sensor)\",\n", - " 10.9,\n", - " 0.59,\n", - " thermal_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"trad\",\n", - " downloader(product_contents[\"FILE_NAME_THERMAL_RADIANCE\"]),\n", - " \"Thermal radiance\",\n", - " radiance_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"urad\",\n", - " downloader(product_contents[\"FILE_NAME_UPWELL_RADIANCE\"]),\n", - " \"Upwelled radiance\",\n", - " radiance_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"drad\",\n", - " downloader(product_contents[\"FILE_NAME_DOWNWELL_RADIANCE\"]),\n", - " \"Downwelled radiance\",\n", - " radiance_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"emis\",\n", - " downloader(product_contents[\"FILE_NAME_EMISSIVITY\"]),\n", - " \"Emissivity\",\n", - " emissivity_transmission_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"emsd\",\n", - " downloader(product_contents[\"FILE_NAME_EMISSIVITY_STDEV\"]),\n", - " \"Emissivity standard deviation\",\n", - " emissivity_transmission_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"atran\",\n", - " downloader(product_contents[\"FILE_NAME_ATMOSPHERIC_TRANSMITTANCE\"]),\n", - " \"Atmospheric transmission\",\n", - " emissivity_transmission_raster_band,\n", - " ),\n", - " plain_band_info(\n", - " \"cdist\",\n", - " downloader(product_contents[\"FILE_NAME_CLOUD_DISTANCE\"]),\n", - " \"Cloud distance\",\n", - " RasterBand.create(\n", - " unit=\"kilometer\",\n", - " scale=1e-2,\n", - " nodata=-9999,\n", - " data_type=\"uint16\",\n", - " spatial_resolution=30.0,\n", - " ),\n", - " ),\n", - " {\n", - " \"name\": \"qa\",\n", - " \"asset_fields\": {\n", - " \"href\": downloader(\n", - " product_contents[\"FILE_NAME_QUALITY_L2_SURFACE_TEMPERATURE\"]\n", - " ),\n", - " \"title\": \"Surface Temperature Quality Assessment Band\",\n", - " },\n", - " \"extensions\": [\n", - " RasterBand.create(\n", - " unit=\"kelvin\",\n", - " scale=1e-2,\n", - " nodata=-9999,\n", - " data_type=\"int16\",\n", - " spatial_resolution=30,\n", - " )\n", - " ],\n", - " },\n", - " {\n", - " \"name\": \"qa_pixel\",\n", - " \"asset_fields\": {\n", - " \"href\": downloader(product_contents[\"FILE_NAME_QUALITY_L1_PIXEL\"]),\n", - " \"media_type\": str(pystac.media_type.MediaType.COG),\n", - " \"title\": \"Pixel quality assessment\",\n", - " },\n", - " \"extensions\": [\n", - " [\n", - " create_qa_bitfield(0, \"fill\", (\"Image data\", \"Fill data\")),\n", - " create_qa_bitfield(\n", - " 1,\n", - " \"dilated_cloud\",\n", - " (\"Cloud is not dilated or no cloud\", \"Dilated cloud\"),\n", - " ),\n", - " create_qa_bitfield(2, \"cirrus\"),\n", - " create_qa_bitfield(3, \"cloud\"),\n", - " create_qa_bitfield(4, \"cloud_shadow\"),\n", - " create_qa_bitfield(5, \"snow\"),\n", - " create_qa_bitfield(6, \"clear\"),\n", - " create_qa_bitfield(7, \"water\"),\n", - " create_confidence_bitfield(8, \"cloud\", True),\n", - " create_confidence_bitfield(10, \"cloud_shadow\"),\n", - " create_confidence_bitfield(12, \"snow\"),\n", - " create_confidence_bitfield(14, \"cirrus\"),\n", - " ]\n", - " ],\n", - " },\n", - " {\n", - " \"name\": \"qa_radsat\",\n", - " \"asset_fields\": {\n", - " \"href\": downloader(\n", - " product_contents[\"FILE_NAME_QUALITY_L1_RADIOMETRIC_SATURATION\"]\n", - " ),\n", - " \"media_type\": str(pystac.media_type.MediaType.COG),\n", - " \"description\": (\n", - " \"Collection 2 Level-1 Radiometric Saturation and \"\n", - " \"Terrain Occlusion Quality Assessment \"\n", - " \"Band (QA_RADSAT)\"\n", - " ),\n", - " },\n", - " \"extensions\": [\n", - " [\n", - " Bitfield.create(\n", - " offset=i - 1,\n", - " length=1,\n", - " description=f\"Band {i} radiometric saturation\",\n", - " classes=[\n", - " Classification.create(\n", - " 0, f\"Band {i} not saturated\", \"not_saturated\"\n", - " ),\n", - " Classification.create(\n", - " 1, f\"Band {i} saturated\", \"saturated\"\n", - " ),\n", - " ],\n", - " )\n", - " for i in [1, 2, 3, 4, 5, 6, 7, 9]\n", - " ]\n", - " + [\n", - " Bitfield.create(\n", - " offset=11,\n", - " length=1,\n", - " description=(\n", - " \"Terrain not visible from sensor due to intervening terrain\"\n", - " ),\n", - " classes=[\n", - " Classification.create(\n", - " 0, \"Terrain is not occluded\", \"not_occluded\"\n", - " ),\n", - " Classification.create(1, \"Terrain is occluded\", \"occluded\"),\n", - " ],\n", - " )\n", - " ]\n", - " ],\n", - " },\n", - " {\n", - " \"name\": \"qa_aerosol\",\n", - " \"asset_fields\": {\n", - " \"href\": downloader(product_contents[\"FILE_NAME_QUALITY_L2_AEROSOL\"]),\n", - " \"media_type\": str(pystac.media_type.MediaType.COG),\n", - " \"title\": \"Aerosol Quality Assessment Band\",\n", - " },\n", - " \"extensions\": [\n", - " [\n", - " create_bitfield(\n", - " 0,\n", - " 1,\n", - " \"fill\",\n", - " [(\"not_fill\", \"Pixel is not fill\"), (\"fill\", \"Pixel is fill\")],\n", - " \"Image or fill data\",\n", - " ),\n", - " create_bitfield(\n", - " 1,\n", - " 1,\n", - " \"retrieval\",\n", - " [\n", - " (\"not_valid\", \"Pixel retrieval is not valid\"),\n", - " (\"valid\", \"Pixel retrieval is valid\"),\n", - " ],\n", - " \"Valid aerosol retrieval\",\n", - " ),\n", - " create_bitfield(\n", - " 2,\n", - " 1,\n", - " \"water\",\n", - " [\n", - " (\"not_water\", \"Pixel is not water\"),\n", - " (\"water\", \"Pixel is water\"),\n", - " ],\n", - " \"Water mask\",\n", - " ),\n", - " create_bitfield(\n", - " 5,\n", - " 1,\n", - " \"interpolated\",\n", - " [\n", - " (\"not_interpolated\", \"Pixel is not interpolated\"),\n", - " (\"interpolated\", \"Pixel is interpolated\"),\n", - " ],\n", - " \"Aerosol interpolation\",\n", - " ),\n", - " create_bitfield(\n", - " 6,\n", - " 2,\n", - " \"level\",\n", - " [\n", - " (\"climatology\", \"No aerosol correction applied\"),\n", - " (\"low\", \"Low aerosol level\"),\n", - " (\"medium\", \"Medium aerosol level\"),\n", - " (\"high\", \"High aerosol level\"),\n", - " ],\n", - " \"Aerosol level\",\n", - " ),\n", - " ]\n", - " ],\n", - " },\n", - " ]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For illustration purposes, we can look at the band info records for an example scene:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'name': 'ang',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ANG.txt',\n", - " 'media_type': 'text/plain',\n", - " 'title': 'Angle coefficients'},\n", - " 'extensions': []},\n", - " {'name': 'mtl.txt',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_MTL.txt',\n", - " 'media_type': 'text/plain',\n", - " 'title': 'Product metadata'},\n", - " 'extensions': []},\n", - " {'name': 'coastal',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B1.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'blue',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B2.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'green',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B3.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'red',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B4.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'nir08',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B5.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'swir16',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B6.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'swir22',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B7.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'lwir11',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_B10.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized'},\n", - " 'extensions': []},\n", - " {'name': 'trad',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_TRAD.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Thermal radiance'},\n", - " 'extensions': []},\n", - " {'name': 'urad',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_URAD.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Upwelled radiance'},\n", - " 'extensions': []},\n", - " {'name': 'drad',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_DRAD.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Downwelled radiance'},\n", - " 'extensions': []},\n", - " {'name': 'emis',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_EMIS.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Emissivity'},\n", - " 'extensions': []},\n", - " {'name': 'emsd',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_EMSD.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Emissivity standard deviation'},\n", - " 'extensions': []},\n", - " {'name': 'atran',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_ATRAN.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Atmospheric transmission'},\n", - " 'extensions': []},\n", - " {'name': 'cdist',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_CDIST.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Cloud distance'},\n", - " 'extensions': []},\n", - " {'name': 'qa',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_ST_QA.TIF',\n", - " 'title': 'Surface Temperature Quality Assessment Band'},\n", - " 'extensions': []},\n", - " {'name': 'qa_pixel',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_QA_PIXEL.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Pixel quality assessment'},\n", - " 'extensions': [[, ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , , , ]>,\n", - " , , , ]>,\n", - " , , , ]>,\n", - " , , , ]>]]},\n", - " {'name': 'qa_radsat',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_QA_RADSAT.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'description': 'Collection 2 Level-1 Radiometric Saturation and Terrain Occlusion Quality Assessment Band (QA_RADSAT)'},\n", - " 'extensions': [[, ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>]]},\n", - " {'name': 'qa_aerosol',\n", - " 'asset_fields': {'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_QA_AEROSOL.TIF',\n", - " 'media_type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Aerosol Quality Assessment Band'},\n", - " 'extensions': [[, ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , ]>,\n", - " , , , ]>]]}]" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "landsat_band_info(metadata, download_url)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With this information we can now define a method that adds all the relevant assets for a scene to an item:" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "def add_assets(item: pystac.Item, band_info: List[BandInfo]) -> None:\n", - " for band in band_info:\n", - " asset = pystac.Asset(**band[\"asset_fields\"])\n", - " asset.set_owner(item)\n", - " for ext_data in band[\"extensions\"]:\n", - " if isinstance(ext_data, EOBand):\n", - " EOExtension.ext(asset, add_if_missing=True).bands = [ext_data]\n", - " elif isinstance(ext_data, RasterBand):\n", - " RasterExtension.ext(asset, add_if_missing=True).bands = [ext_data]\n", - " elif isinstance(ext_data, list):\n", - " ClassificationExtension.ext(\n", - " asset, add_if_missing=True\n", - " ).bitfields = ext_data\n", - " item.add_asset(band[\"name\"], asset)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [], - "source": [ - "add_assets(item, landsat_band_info(metadata, download_url))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can examine the item to ensure that the assets appear as expected." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_B5.TIF',\n", - " 'type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'eo:bands': [{'name': 'OLI_B5',\n", - " 'common_name': 'nir08',\n", - " 'description': 'Near infrared (Operational Land Imager)',\n", - " 'center_wavelength': 0.87,\n", - " 'full_width_half_max': 0.03}]}" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.assets[\"nir08\"].to_dict()" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'href': 'https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC08_L2SP_014032_20221219_20230113_02_T1/LC08_L2SP_014032_20221219_20230113_02_T1_SR_QA_AEROSOL.TIF',\n", - " 'type': 'image/tiff; application=geotiff; profile=cloud-optimized',\n", - " 'title': 'Aerosol Quality Assessment Band',\n", - " 'classification:bitfields': [{'offset': 0,\n", - " 'length': 1,\n", - " 'classes': [{'value': 0,\n", - " 'name': 'not_fill',\n", - " 'description': 'Pixel is not fill'},\n", - " {'value': 1, 'name': 'fill', 'description': 'Pixel is fill'}],\n", - " 'description': 'Image or fill data',\n", - " 'name': 'fill'},\n", - " {'offset': 1,\n", - " 'length': 1,\n", - " 'classes': [{'value': 0,\n", - " 'name': 'not_valid',\n", - " 'description': 'Pixel retrieval is not valid'},\n", - " {'value': 1, 'name': 'valid', 'description': 'Pixel retrieval is valid'}],\n", - " 'description': 'Valid aerosol retrieval',\n", - " 'name': 'retrieval'},\n", - " {'offset': 2,\n", - " 'length': 1,\n", - " 'classes': [{'value': 0,\n", - " 'name': 'not_water',\n", - " 'description': 'Pixel is not water'},\n", - " {'value': 1, 'name': 'water', 'description': 'Pixel is water'}],\n", - " 'description': 'Water mask',\n", - " 'name': 'water'},\n", - " {'offset': 5,\n", - " 'length': 1,\n", - " 'classes': [{'value': 0,\n", - " 'name': 'not_interpolated',\n", - " 'description': 'Pixel is not interpolated'},\n", - " {'value': 1,\n", - " 'name': 'interpolated',\n", - " 'description': 'Pixel is interpolated'}],\n", - " 'description': 'Aerosol interpolation',\n", - " 'name': 'interpolated'},\n", - " {'offset': 6,\n", - " 'length': 2,\n", - " 'classes': [{'value': 0,\n", - " 'name': 'climatology',\n", - " 'description': 'No aerosol correction applied'},\n", - " {'value': 1, 'name': 'low', 'description': 'Low aerosol level'},\n", - " {'value': 2, 'name': 'medium', 'description': 'Medium aerosol level'},\n", - " {'value': 3, 'name': 'high', 'description': 'High aerosol level'}],\n", - " 'description': 'Aerosol level',\n", - " 'name': 'level'}]}" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.assets[\"qa_aerosol\"].to_dict()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add projection information\n", - "\n", - "We can specify the EPSG code for the scene as part of the [projection extension](https://github.com/stac-extensions/projection). The below method, adapted from [stactools](https://github.com/stactools-packages/landsat/blob/9f595a9d5ed6b62a2e96338e79f5bb502a7d90d0/src/stactools/landsat/mtl_metadata.py#L86-L109), figures out the correct UTM Zone EPSG:" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [], - "source": [ - "def get_epsg(metadata: Dict[str, Any], min_lat: float, max_lat: float) -> Optional[int]:\n", - " if \"UTM_ZONE\" in metadata[\"PROJECTION_ATTRIBUTES\"]:\n", - " utm_zone_integer = metadata[\"PROJECTION_ATTRIBUTES\"][\"UTM_ZONE\"].zfill(2)\n", - " return int(f\"326{utm_zone_integer}\")\n", - " else:\n", - " lat_ts = metadata[\"PROJECTION_ATTRIBUTES\"][\"TRUE_SCALE_LAT\"]\n", - " if lat_ts == \"-71.00000\":\n", - " # Antarctic\n", - " return 3031\n", - " elif lat_ts == \"71.00000\":\n", - " # Arctic\n", - " return 3995\n", - " else:\n", - " raise ValueError(\n", - " f\"Unexpeced value for PROJECTION_ATTRIBUTES/TRUE_SCALE_LAT: {lat_ts} \"\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "32618" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "proj_ext = ProjectionExtension.ext(item, add_if_missing=True)\n", - "assert item.bbox is not None\n", - "proj_ext.epsg = get_epsg(metadata, item.bbox[1], item.bbox[3])\n", - "proj_ext.epsg" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add view geometry information\n", - "\n", - "The [View Geometry](https://github.com/stac-extensions/view) extension specifies information related to angles of sensors and other radiance angles that affect the view of resulting data. The Landsat metadata specifies two of these parameters, so we add them to our Item:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [], - "source": [ - "def get_view_info(metadata: Dict[str, Any]) -> Dict[str, float]:\n", - " return {\n", - " \"sun_azimuth\": float(metadata[\"IMAGE_ATTRIBUTES\"][\"SUN_AZIMUTH\"]),\n", - " \"sun_elevation\": float(metadata[\"IMAGE_ATTRIBUTES\"][\"SUN_ELEVATION\"]),\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'datetime': '2022-12-19T15:40:17.729916Z',\n", - " 'gsd': 30.0,\n", - " 'eo:cloud_cover': 43.42,\n", - " 'proj:epsg': 32618,\n", - " 'view:sun_azimuth': 160.86021018,\n", - " 'view:sun_elevation': 23.81656674}" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "view_ext = ViewExtension.ext(item, add_if_missing=True)\n", - "view_info = get_view_info(metadata)\n", - "view_ext.sun_azimuth = view_info[\"sun_azimuth\"]\n", - "view_ext.sun_elevation = view_info[\"sun_elevation\"]\n", - "item.properties" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we've added all the metadata to the Item, let's check the validator to make sure we've specified everything correctly. The validation logic will take into account the new extensions that have been enabled and validate against the proper schemas for those extensions." - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json',\n", - " 'https://stac-extensions.github.io/eo/v1.1.0/schema.json',\n", - " 'https://stac-extensions.github.io/raster/v1.1.0/schema.json',\n", - " 'https://stac-extensions.github.io/classification/v1.1.0/schema.json',\n", - " 'https://stac-extensions.github.io/projection/v1.1.0/schema.json',\n", - " 'https://stac-extensions.github.io/view/v1.0.0/schema.json']" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.validate()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Building the Collection\n", - "\n", - "Now that we know how to build an Item for a scene, let's build the Collection that will contain all the Items.\n", - "\n", - "If we look at the `__init__` method for `pystac.Collection`, we can see what properties are required:" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function __init__ in module pystac.collection:\n", - "\n", - "__init__(self, id: 'str', description: 'str', extent: 'Extent', title: 'Optional[str]' = None, stac_extensions: 'Optional[List[str]]' = None, href: 'Optional[str]' = None, extra_fields: 'Optional[Dict[str, Any]]' = None, catalog_type: 'Optional[CatalogType]' = None, license: 'str' = 'proprietary', keywords: 'Optional[List[str]]' = None, providers: 'Optional[List[Provider]]' = None, summaries: 'Optional[Summaries]' = None, assets: 'Optional[Dict[str, Asset]]' = None)\n", - " Initialize self. See help(type(self)) for accurate signature.\n", - "\n" - ] - } - ], - "source": [ - "help(pystac.Collection.__init__)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Collection `id`\n", - "\n", - "We'll use the location name we defined above in the ID for our Collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'philly-landsat-collection-2'" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection_id = \"{}-landsat-collection-2\".format(location_name.lower())\n", - "collection_id" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Collection `title`\n", - "\n", - "Here we set a simple title for our collection." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'2022 Landsat images over philly'" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection_title = \"2022 Landsat images over {}\".format(location_name.lower())\n", - "collection_title" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Collection `description`\n", - "\n", - "Here we give a brief description of the Collection. If this were a real Collection that were being published, I'd recommend putting a much more detailed description to ensure anyone using your STAC knows what they are working with!\n", - "\n", - "Notice we are using [Markdown](https://www.markdownguide.org/) to write the description. The `description` field can be Markdown to help tools that render information about STAC to display the information in a more readable way." - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "### Philly Landsat Collection 2\n", - "\n", - "A collection of Landsat scenes around Philly in 2022.\n", - "\n" - ] - } - ], - "source": [ - "collection_description = \"\"\"### {} Landsat Collection 2\n", - "\n", - "A collection of Landsat scenes around {} in 2022.\n", - "\"\"\".format(location_name, location_name)\n", - "print(collection_description)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Collection `extent`\n", - "\n", - "A Collection specifies the spatial and temporal extent of all the item it contains. Since Landsat spans the globe, we'll simply put a global extent here. We'll also specify an open-ended time interval.\n", - "\n", - "Towards the end of the notebook, we'll use a method to easily scope this down to cover the times and space the Items occupy once we've added all the items." - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [], - "source": [ - "spatial_extent = pystac.SpatialExtent([[-180.0, -90.0, 180.0, 90.0]])\n", - "temporal_extent = pystac.TemporalExtent([[datetime(2013, 6, 1), None]])\n", - "collection_extent = pystac.Extent(spatial_extent, temporal_extent)" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [], - "source": [ - "collection = pystac.Collection(\n", - " id=collection_id,\n", - " title=collection_title,\n", - " description=collection_description,\n", - " extent=collection_extent,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now look at our Collection as a `dict` to check our values." - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'type': 'Collection',\n", - " 'id': 'philly-landsat-collection-2',\n", - " 'stac_version': '1.0.0',\n", - " 'description': '### Philly Landsat Collection 2\\n\\nA collection of Landsat scenes around Philly in 2022.\\n',\n", - " 'links': [],\n", - " 'title': '2022 Landsat images over philly',\n", - " 'extent': {'spatial': {'bbox': [[-180.0, -90.0, 180.0, 90.0]]},\n", - " 'temporal': {'interval': [['2013-06-01T00:00:00Z', None]]}},\n", - " 'license': 'proprietary'}" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.to_dict()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Set the license\n", - "\n", - "Notice the `license` above is `proprietary`. This is the default in PySTAC if no license is specified; however Landsat is certainly not proprietary (thankfully!), so let's change the license to the correct [SPDX](https://spdx.org/licenses/) string for public domain data:" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [], - "source": [ - "collection_license = \"PDDL-1.0\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Set the providers\n", - "\n", - "A collection will specify the providers of the data, including what role they have played. We can set our provider information by instantiating `pystac.Provider` objects:" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": {}, - "outputs": [], - "source": [ - "collection.providers = [\n", - " pystac.Provider(\n", - " name=\"NASA\",\n", - " roles=[pystac.ProviderRole.PRODUCER, pystac.ProviderRole.LICENSOR],\n", - " url=\"https://landsat.gsfc.nasa.gov/\",\n", - " ),\n", - " pystac.Provider(\n", - " name=\"USGS\",\n", - " roles=[\n", - " pystac.ProviderRole.PROCESSOR,\n", - " pystac.ProviderRole.PRODUCER,\n", - " pystac.ProviderRole.LICENSOR,\n", - " ],\n", - " url=\"https://www.usgs.gov/landsat-missions/landsat-collection-2-level-2-science-products\",\n", - " ),\n", - " pystac.Provider(\n", - " name=\"Microsoft\",\n", - " roles=[pystac.ProviderRole.HOST],\n", - " url=\"https://planetarycomputer.microsoft.com\",\n", - " ),\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create items for each scene\n", - "\n", - "We created an Item for a single scene above. This method consolidates that logic into a single method that can construct an Item from a scene, so we can create an Item for every scene in our subset:" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [], - "source": [ - "def item_from_metadata(mtl_xml_url: str) -> pystac.Item:\n", - " metadata = get_metadata(mtl_xml_url)\n", - " download_url = partial(download_sidecar, metadata)\n", - "\n", - " bbox = get_bbox(metadata)\n", - " item = pystac.Item(\n", - " id=get_item_id(metadata),\n", - " datetime=get_datetime(metadata),\n", - " geometry=get_geometry(metadata, bbox),\n", - " bbox=bbox,\n", - " properties={},\n", - " )\n", - "\n", - " item.common_metadata.gsd = 30.0\n", - "\n", - " item_eo_ext = EOExtension.ext(item, add_if_missing=True)\n", - " item_eo_ext.cloud_cover = get_cloud_cover(metadata)\n", - "\n", - " add_assets(item, landsat_band_info(metadata, download_url))\n", - "\n", - " item_proj_ext = ProjectionExtension.ext(item, add_if_missing=True)\n", - " assert item.bbox is not None\n", - " item_proj_ext.epsg = get_epsg(metadata, item.bbox[1], item.bbox[3])\n", - "\n", - " item_view_ext = ViewExtension.ext(item, add_if_missing=True)\n", - " view_info = get_view_info(metadata)\n", - " item_view_ext.sun_azimuth = view_info[\"sun_azimuth\"]\n", - " item_view_ext.sun_elevation = view_info[\"sun_elevation\"]\n", - "\n", - " item.validate()\n", - "\n", - " return item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we create an item per scene and add it to our collection. Since this is reading multiple metadata files per scene from the internet, it may take a little bit to run:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ANG file for url https://landsateuwest.blob.core.windows.net/landsat-c2/level-2/standard/oli-tirs/2022/014/032/LC09_L2SP_014032_20221024_20221026_02_T2/LC09_L2SP_014032_20221024_20221026_02_T2_ANG.txt is incorrectly formatted\n" - ] - } - ], - "source": [ - "for url in scene_mtls:\n", - " try:\n", - " item = item_from_metadata(url)\n", - " collection.add_item(item)\n", - " except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reset collection extent based on items\n", - "\n", - "Now that we've added all the item we can use the `update_extent_from_items` method on the Collection to set the extent based on the contained items:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'spatial': {'bbox': [[-76.29048, 39.25502, -73.48833, 41.38441]]},\n", - " 'temporal': {'interval': [['2022-10-08T15:40:14.577173Z',\n", - " '2022-12-19T15:40:17.729916Z']]}}" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.update_extent_from_items()\n", - "collection.extent.to_dict()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set the HREFs for everything in the catalog\n", - "\n", - "We've been building up our Collection and Items in memory. This has been convenient as it allows us not to think about file paths as we construct our Catalog. However, a STAC is not valid without any HREFs! \n", - "\n", - "We can use the `normalize_hrefs` method to set all the HREFs in the entire STAC based on a root directory. This will use the [STAC Best Practices](https://github.com/radiantearth/stac-spec/blob/master/best-practices.md#catalog-layout) recommendations for STAC file layout for each Catalog, Collection and Item in the STAC.\n", - "\n", - "Here we use that method and set the root directory to a subdirectory of our user's `home` directory:" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "\n", - "root_path = str(Path.home() / \"{}-landsat-stac\".format(location_name))\n", - "\n", - "collection.normalize_hrefs(root_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we have all the Collection's data set and HREFs in place we can validate the entire STAC using `validate_all`, which recursively crawls through a catalog and validates every STAC object in the catalog:" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "8" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.validate_all()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Write the catalog locally\n", - "\n", - "Now that we have our complete, validated STAC in memory, let's write it out. This is as simple as calling `save` on the Collection. We need to specify the type of catalog in order to property write out links - these types are described again in the STAC [Best Practices](https://github.com/radiantearth/stac-spec/blob/master/best-practices.md#use-of-links) documentation.\n", - "\n", - "We'll use the \"self contained\" type, which uses relative paths and does not specify absolute \"self\" links to any object. This makes the catalog more portable, as it remains valid even if you copy the STAC to new locations." - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": {}, - "outputs": [], - "source": [ - "collection.save(pystac.CatalogType.SELF_CONTAINED)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we've written our STAC out we probably want to view it. We can use the `describe` method to print out a simple representation of the catalog:" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n" - ] - } - ], - "source": [ - "collection.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also use the `to_dict` method on individual STAC objects in order to see the data, as we've been doing in the tutorial:" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'type': 'Collection',\n", - " 'id': 'philly-landsat-collection-2',\n", - " 'stac_version': '1.0.0',\n", - " 'description': '### Philly Landsat Collection 2\\n\\nA collection of Landsat scenes around Philly in 2022.\\n',\n", - " 'links': [{'rel': 'root',\n", - " 'href': './collection.json',\n", - " 'type': 'application/json',\n", - " 'title': '2022 Landsat images over philly'},\n", - " {'rel': 'item',\n", - " 'href': './LC80140322022353/LC80140322022353.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC90140322022345/LC90140322022345.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC80140322022337/LC80140322022337.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC90140322022329/LC90140322022329.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC80140322022321/LC80140322022321.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC90140322022313/LC90140322022313.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC80140322022305/LC80140322022305.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'item',\n", - " 'href': './LC90140322022281/LC90140322022281.json',\n", - " 'type': 'application/json'},\n", - " {'rel': 'self',\n", - " 'href': '/Users/pjh/Philly-landsat-stac/collection.json',\n", - " 'type': 'application/json'}],\n", - " 'title': '2022 Landsat images over philly',\n", - " 'extent': {'spatial': {'bbox': [[-76.29048, 39.25502, -73.48833, 41.38441]]},\n", - " 'temporal': {'interval': [['2022-10-08T15:40:14.577173Z',\n", - " '2022-12-19T15:40:17.729916Z']]}},\n", - " 'license': 'proprietary',\n", - " 'providers': [{'name': 'NASA',\n", - " 'roles': [,\n", - " ],\n", - " 'url': 'https://landsat.gsfc.nasa.gov/'},\n", - " {'name': 'USGS',\n", - " 'roles': [,\n", - " ,\n", - " ],\n", - " 'url': 'https://www.usgs.gov/landsat-missions/landsat-collection-2-level-2-science-products'},\n", - " {'name': 'Microsoft',\n", - " 'roles': [],\n", - " 'url': 'https://planetarycomputer.microsoft.com'}]}" - ] - }, - "execution_count": 54, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.to_dict()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, if we want to browse our STAC more interactively, we can serve the Collection from a local webserver and then browse the Collection with [stac-browser](https://github.com/radiantearth/stac-browser).\n", - "\n", - "We can use this simple Python server (copied from [this gist](https://gist.github.com/acdha/925e9ffc3d74ad59c3ea)) to serve our our directory at port 5555:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from http.server import HTTPServer, SimpleHTTPRequestHandler\n", - "\n", - "os.chdir(root_path)\n", - "\n", - "\n", - "class CORSRequestHandler(SimpleHTTPRequestHandler):\n", - " def end_headers(self) -> None:\n", - " self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n", - " self.send_header(\"Access-Control-Allow-Methods\", \"GET\")\n", - " self.send_header(\"Cache-Control\", \"no-store, no-cache, must-revalidate\")\n", - " return super(CORSRequestHandler, self).end_headers()\n", - "\n", - "\n", - "with HTTPServer((\"localhost\", 5555), CORSRequestHandler) as httpd:\n", - " httpd.serve_forever()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can browse our STAC Collection with stac-browser in a few different ways:\n", - "1. Follow the [instructions](https://github.com/radiantearth/stac-browser/blob/main/local_files.md) for starting a stac-browser instance and point it at `http://localhost:5555/collection.json` to serve out the STAC.\n", - "2. If you want to avoid setting up your own stac-browser instance, you can use the [STAC Browser Demo](https://radiantearth.github.io/stac-browser/) hosted by Radiant Earth: [https://radiantearth.github.io/stac-browser/#/http://localhost:5555/collection.json](https://radiantearth.github.io/stac-browser/#/http://localhost:5555/collection.json)\n", - "\n", - "To quit the server, use the `Kernel` -> `Interrupt` menu option." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Acknowledgements\n", - "\n", - "Credit to [sat-stac-landsat](https://github.com/sat-utils/sat-stac-landsat) from which a lot of this code was based." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "pystac", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/tutorials/how-to-create-stac-catalogs.ipynb b/docs/tutorials/how-to-create-stac-catalogs.ipynb deleted file mode 100644 index 97523f4aa..000000000 --- a/docs/tutorials/how-to-create-stac-catalogs.ipynb +++ /dev/null @@ -1,4341 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# How to create STAC Catalogs \n", - "## STAC Community Sprint, Arlington, November 7th 2019" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook runs through some of the basics of using PySTAC to create a static STAC. It was part of a 30 minute presentation at the [community STAC sprint](https://github.com/radiantearth/community-sprints/tree/master/11052019-arlignton-va) in Arlington, VA in November 2019, updated to work with current PySTAC." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial will require the `boto3`, `rasterio`, and `shapely` libraries:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:34.023332Z", - "iopub.status.busy": "2025-09-11T17:34:34.021839Z", - "iopub.status.idle": "2025-09-11T17:34:34.786566Z", - "shell.execute_reply": "2025-09-11T17:34:34.781537Z", - "shell.execute_reply.started": "2025-09-11T17:34:34.023279Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/home/jsignell/pystac/.venv/bin/python: No module named pip\n", - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], - "source": [ - "%pip install boto3 rasterio shapely pystac --quiet" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can import pystac and access most of the functionality we need with the single import:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:34.789047Z", - "iopub.status.busy": "2025-09-11T17:34:34.788742Z", - "iopub.status.idle": "2025-09-11T17:34:37.682573Z", - "shell.execute_reply": "2025-09-11T17:34:37.681902Z", - "shell.execute_reply.started": "2025-09-11T17:34:34.789020Z" - } - }, - "outputs": [], - "source": [ - "import pystac" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating a catalog from a local file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To give us some material to work with, lets download a single image from the [Spacenet 5 challenge](https://www.topcoder.com/challenges/30099956). We'll use a temporary directory to save off our single-item STAC." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:37.683455Z", - "iopub.status.busy": "2025-09-11T17:34:37.683245Z", - "iopub.status.idle": "2025-09-11T17:34:37.692742Z", - "shell.execute_reply": "2025-09-11T17:34:37.689106Z", - "shell.execute_reply.started": "2025-09-11T17:34:37.683430Z" - } - }, - "outputs": [], - "source": [ - "import os\n", - "import urllib.request\n", - "from tempfile import TemporaryDirectory\n", - "\n", - "tmp_dir = TemporaryDirectory()\n", - "img_path = os.path.join(tmp_dir.name, \"image.tif\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:37.695556Z", - "iopub.status.busy": "2025-09-11T17:34:37.695277Z", - "iopub.status.idle": "2025-09-11T17:34:38.289508Z", - "shell.execute_reply": "2025-09-11T17:34:38.288573Z", - "shell.execute_reply.started": "2025-09-11T17:34:37.695525Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "('/tmp/tmpjzy2xbol/image.tif', )" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "url = (\n", - " \"https://spacenet-dataset.s3.amazonaws.com/\"\n", - " \"spacenet/SN5_roads/train/AOI_7_Moscow/MS/\"\n", - " \"SN5_roads_train_AOI_7_Moscow_MS_chip996.tif\"\n", - ")\n", - "urllib.request.urlretrieve(url, img_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We want to create a Catalog. Let's check the docs for `Catalog` to see what information we'll need." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:38.291556Z", - "iopub.status.busy": "2025-09-11T17:34:38.291249Z", - "iopub.status.idle": "2025-09-11T17:34:38.347577Z", - "shell.execute_reply": "2025-09-11T17:34:38.345637Z", - "shell.execute_reply.started": "2025-09-11T17:34:38.291519Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCatalog\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[str] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcatalog_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'CatalogType'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'ABSOLUTE_PUBLISHED'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstrategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'HrefLayoutStrategy | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "A PySTAC Catalog represents a STAC catalog in memory.\n", - "\n", - "A Catalog is a :class:`~pystac.STACObject` that may contain children,\n", - "which are instances of :class:`~pystac.Catalog` or :class:`~pystac.Collection`,\n", - "as well as :class:`~pystac.Item` s.\n", - "\n", - "Args:\n", - " id : Identifier for the catalog. Must be unique within the STAC.\n", - " description : Detailed multi-line description to fully explain the catalog.\n", - " `CommonMark 0.29 syntax `_ MAY be used for rich\n", - " text representation.\n", - " title : Optional short descriptive one-line title for the catalog.\n", - " stac_extensions : Optional list of extensions the Catalog implements.\n", - " href : Optional HREF for this catalog, which be set as the\n", - " catalog's self link's HREF.\n", - " catalog_type : Optional catalog type for this catalog. Must\n", - " be one of the values in :class:`~pystac.CatalogType`.\n", - " strategy : The layout strategy to use for setting the\n", - " HREFs of the catalog child objects and items.\n", - " If not provided, it will default to the strategy of the root and fallback to\n", - " :class:`~pystac.layout.BestPracticesLayoutStrategy`.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/catalog.py\n", - "\u001b[0;31mType:\u001b[0m ABCMeta\n", - "\u001b[0;31mSubclasses:\u001b[0m Collection" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "?pystac.Catalog" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's just give an ID and a description. We don't have to worry about the HREF right now; that will be set later." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:38.348654Z", - "iopub.status.busy": "2025-09-11T17:34:38.348454Z", - "iopub.status.idle": "2025-09-11T17:34:38.356583Z", - "shell.execute_reply": "2025-09-11T17:34:38.355631Z", - "shell.execute_reply.started": "2025-09-11T17:34:38.348634Z" - } - }, - "outputs": [], - "source": [ - "catalog = pystac.Catalog(id=\"test-catalog\", description=\"Tutorial catalog.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are no children or items in the catalog, since we haven't added anything yet." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:38.360126Z", - "iopub.status.busy": "2025-09-11T17:34:38.358977Z", - "iopub.status.idle": "2025-09-11T17:34:38.370844Z", - "shell.execute_reply": "2025-09-11T17:34:38.366059Z", - "shell.execute_reply.started": "2025-09-11T17:34:38.360096Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n", - "[]\n" - ] - } - ], - "source": [ - "print(list(catalog.get_children()))\n", - "print(list(catalog.get_items()))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We'll now create an Item to represent the image. Check the pydocs to see what you need to supply:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:38.372631Z", - "iopub.status.busy": "2025-09-11T17:34:38.371828Z", - "iopub.status.idle": "2025-09-11T17:34:38.383667Z", - "shell.execute_reply": "2025-09-11T17:34:38.380181Z", - "shell.execute_reply.started": "2025-09-11T17:34:38.372599Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mItem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mgeometry\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any] | None'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mbbox\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[float] | None'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Datetime | None'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mproperties\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstart_datetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Datetime | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mend_datetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Datetime | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[str] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcollection\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | Collection | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0massets\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Asset] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "An Item is the core granular entity in a STAC, containing the core metadata\n", - "that enables any client to search or crawl online catalogs of spatial 'assets' -\n", - "satellite imagery, derived data, DEM's, etc.\n", - "\n", - "Args:\n", - " id : Provider identifier. Must be unique within the STAC.\n", - " geometry : Defines the full footprint of the asset represented by this\n", - " item, formatted according to\n", - " `RFC 7946, section 3.1 (GeoJSON) `_.\n", - " bbox : Bounding Box of the asset represented by this item\n", - " using either 2D or 3D geometries. The length of the array must be 2*n\n", - " where n is the number of dimensions. Could also be None in the case of a\n", - " null geometry.\n", - " datetime : datetime associated with this item. If None,\n", - " a start_datetime and end_datetime must be supplied.\n", - " properties : A dictionary of additional metadata for the item.\n", - " start_datetime : Optional inclusive start datetime, part of common metadata.\n", - " This value will override any `start_datetime` key in properties.\n", - " end_datetime : Optional inclusive end datetime, part of common metadata.\n", - " This value will override any `end_datetime` key in properties.\n", - " stac_extensions : Optional list of extensions the Item implements.\n", - " href : Optional HREF for this item, which be set as the item's\n", - " self link's HREF.\n", - " collection : The Collection or Collection ID that this item\n", - " belongs to.\n", - " extra_fields : Extra fields that are part of the top-level JSON\n", - " properties of the Item.\n", - " assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All\n", - " :class:`~pystac.Asset` values in the dictionary will have their\n", - " :attr:`~pystac.Asset.owner` attribute set to the created Item.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/item.py\n", - "\u001b[0;31mType:\u001b[0m _ProtocolMeta\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "?pystac.Item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using [rasterio](https://rasterio.readthedocs.io/en/stable/), we can pull out the bounding box of the image to use for the image metadata. If the image contained a NoData border, we would ideally pull out the footprint and save it as the geometry; in this case, we're working with a small chip that most likely has no NoData values." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:38.388240Z", - "iopub.status.busy": "2025-09-11T17:34:38.386541Z", - "iopub.status.idle": "2025-09-11T17:34:39.087296Z", - "shell.execute_reply": "2025-09-11T17:34:39.083512Z", - "shell.execute_reply.started": "2025-09-11T17:34:38.388077Z" - } - }, - "outputs": [], - "source": [ - "import rasterio\n", - "from shapely.geometry import Polygon, mapping\n", - "\n", - "\n", - "def get_bbox_and_footprint(raster_uri):\n", - " with rasterio.open(raster_uri) as ds:\n", - " bounds = ds.bounds\n", - " bbox = [bounds.left, bounds.bottom, bounds.right, bounds.top]\n", - " footprint = Polygon(\n", - " [\n", - " [bounds.left, bounds.bottom],\n", - " [bounds.left, bounds.top],\n", - " [bounds.right, bounds.top],\n", - " [bounds.right, bounds.bottom],\n", - " ]\n", - " )\n", - "\n", - " return (bbox, mapping(footprint))" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.091178Z", - "iopub.status.busy": "2025-09-11T17:34:39.090715Z", - "iopub.status.idle": "2025-09-11T17:34:39.223785Z", - "shell.execute_reply": "2025-09-11T17:34:39.218544Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.091145Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[37.6616853489879, 55.73478197572927, 37.66573047610874, 55.73882710285011]\n", - "{'type': 'Polygon', 'coordinates': (((37.6616853489879, 55.73478197572927), (37.6616853489879, 55.73882710285011), (37.66573047610874, 55.73882710285011), (37.66573047610874, 55.73478197572927), (37.6616853489879, 55.73478197572927)),)}\n" - ] - } - ], - "source": [ - "bbox, footprint = get_bbox_and_footprint(img_path)\n", - "print(bbox)\n", - "print(footprint)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We're also using `datetime.utcnow()` to supply the required datetime property for our Item. Since this is a required property, you might often find yourself making up a time to fill in if you don't know the exact capture time." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.231210Z", - "iopub.status.busy": "2025-09-11T17:34:39.229462Z", - "iopub.status.idle": "2025-09-11T17:34:39.250212Z", - "shell.execute_reply": "2025-09-11T17:34:39.245522Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.231016Z" - } - }, - "outputs": [], - "source": [ - "from datetime import datetime\n", - "\n", - "item = pystac.Item(\n", - " id=\"local-image\",\n", - " geometry=footprint,\n", - " bbox=bbox,\n", - " datetime=datetime.utcnow(),\n", - " properties={},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We haven't added it to a catalog yet, so it's parent isn't set. Once we add it to the catalog, we can see it correctly links to it's parent." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.254710Z", - "iopub.status.busy": "2025-09-11T17:34:39.254243Z", - "iopub.status.idle": "2025-09-11T17:34:39.264605Z", - "shell.execute_reply": "2025-09-11T17:34:39.260741Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.254672Z" - } - }, - "outputs": [], - "source": [ - "assert item.get_parent() is None" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.269432Z", - "iopub.status.busy": "2025-09-11T17:34:39.267610Z", - "iopub.status.idle": "2025-09-11T17:34:39.337330Z", - "shell.execute_reply": "2025-09-11T17:34:39.336596Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.269272Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=item target=<Item id=local-image>>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "catalog.add_item(item)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.340406Z", - "iopub.status.busy": "2025-09-11T17:34:39.340122Z", - "iopub.status.idle": "2025-09-11T17:34:39.345974Z", - "shell.execute_reply": "2025-09-11T17:34:39.345064Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.340384Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Catalog id=test-catalog>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.get_parent()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`describe()` is a useful method on `Catalog` - but be careful when using it on large catalogs, as it will walk the entire tree of the STAC." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.346688Z", - "iopub.status.busy": "2025-09-11T17:34:39.346487Z", - "iopub.status.idle": "2025-09-11T17:34:39.351538Z", - "shell.execute_reply": "2025-09-11T17:34:39.350797Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.346669Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n" - ] - } - ], - "source": [ - "catalog.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding Assets\n", - "\n", - "We've created an Item, but there aren't any assets associated with it. Let's create one:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.352797Z", - "iopub.status.busy": "2025-09-11T17:34:39.352545Z", - "iopub.status.idle": "2025-09-11T17:34:39.359463Z", - "shell.execute_reply": "2025-09-11T17:34:39.358442Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.352773Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAsset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mmedia_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mroles\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[str] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'None'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "An object that contains a link to data associated with an Item or Collection that\n", - "can be downloaded or streamed.\n", - "\n", - "Args:\n", - " href : Link to the asset object. Relative and absolute links are both\n", - " allowed.\n", - " title : Optional displayed title for clients and users.\n", - " description : A description of the Asset providing additional details,\n", - " such as how it was processed or created. CommonMark 0.29 syntax MAY be used\n", - " for rich text representation.\n", - " media_type : Optional description of the media type. Registered Media Types\n", - " are preferred. See :class:`~pystac.MediaType` for common media types.\n", - " roles : Optional, Semantic roles (i.e. thumbnail, overview,\n", - " data, metadata) of the asset.\n", - " extra_fields : Optional, additional fields for this asset. This is used\n", - " by extensions as a way to serialize and deserialize properties on asset\n", - " object JSON.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/asset.py\n", - "\u001b[0;31mType:\u001b[0m type\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "?pystac.Asset" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.360281Z", - "iopub.status.busy": "2025-09-11T17:34:39.360057Z", - "iopub.status.idle": "2025-09-11T17:34:39.363197Z", - "shell.execute_reply": "2025-09-11T17:34:39.362512Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.360259Z" - } - }, - "outputs": [], - "source": [ - "item.add_asset(\n", - " key=\"image\", asset=pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At any time we can call `to_dict()` on STAC objects to see how the STAC JSON is shaping up. Notice the asset is now set:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.364270Z", - "iopub.status.busy": "2025-09-11T17:34:39.364047Z", - "iopub.status.idle": "2025-09-11T17:34:39.377746Z", - "shell.execute_reply": "2025-09-11T17:34:39.374789Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.364244Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"type\": \"Feature\",\n", - " \"stac_version\": \"1.1.0\",\n", - " \"stac_extensions\": [],\n", - " \"id\": \"local-image\",\n", - " \"geometry\": {\n", - " \"type\": \"Polygon\",\n", - " \"coordinates\": [\n", - " [\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ]\n", - " ]\n", - " ]\n", - " },\n", - " \"bbox\": [\n", - " 37.6616853489879,\n", - " 55.73478197572927,\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " \"properties\": {\n", - " \"datetime\": \"2025-09-11T17:34:39.240259Z\"\n", - " },\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"root\",\n", - " \"href\": null,\n", - " \"type\": \"application/json\"\n", - " },\n", - " {\n", - " \"rel\": \"parent\",\n", - " \"href\": null,\n", - " \"type\": \"application/json\"\n", - " }\n", - " ],\n", - " \"assets\": {\n", - " \"image\": {\n", - " \"href\": \"/tmp/tmpjzy2xbol/image.tif\",\n", - " \"type\": \"image/tiff; application=geotiff\"\n", - " }\n", - " }\n", - "}\n" - ] - } - ], - "source": [ - "import json\n", - "\n", - "print(json.dumps(item.to_dict(), indent=4))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the link `href` properties are `null`. This is OK, as we're working with the STAC in memory. Next, we'll talk about writing the catalog out, and how to set those HREFs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Saving the catalog" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As the JSON above indicates, there's no HREFs set on these in-memory items. PySTAC uses the `self` link on STAC objects to track where the file lives. Because we haven't set them, they evaluate to `None`:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.385246Z", - "iopub.status.busy": "2025-09-11T17:34:39.383246Z", - "iopub.status.idle": "2025-09-11T17:34:39.391640Z", - "shell.execute_reply": "2025-09-11T17:34:39.388897Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.385205Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "True\n" - ] - } - ], - "source": [ - "print(catalog.get_self_href() is None)\n", - "print(item.get_self_href() is None)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In order to set them, we can use `normalize_hrefs`. This method will create a normalized set of HREFs for each STAC object in the catalog, according to the [best practices document](https://github.com/radiantearth/stac-spec/blob/v0.8.1/best-practices.md#catalog-layout)'s recommendations on how to lay out a catalog." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.395029Z", - "iopub.status.busy": "2025-09-11T17:34:39.394764Z", - "iopub.status.idle": "2025-09-11T17:34:39.400859Z", - "shell.execute_reply": "2025-09-11T17:34:39.400153Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.395006Z" - } - }, - "outputs": [], - "source": [ - "catalog.normalize_hrefs(os.path.join(tmp_dir.name, \"stac\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we've normalized to a root directory (the temporary directory), we see that the `self` links are set:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.402183Z", - "iopub.status.busy": "2025-09-11T17:34:39.401919Z", - "iopub.status.idle": "2025-09-11T17:34:39.408402Z", - "shell.execute_reply": "2025-09-11T17:34:39.407484Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.402159Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/tmp/tmpjzy2xbol/stac/catalog.json\n", - "/tmp/tmpjzy2xbol/stac/local-image/local-image.json\n" - ] - } - ], - "source": [ - "print(catalog.get_self_href())\n", - "print(item.get_self_href())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now call `save` on the catalog, which will recursively save all the STAC objects to their respective self HREFs.\n", - "\n", - "Save requires a `CatalogType` to be set. You can review the [API docs](https://pystac.readthedocs.io/en/stable/api.html#catalogtype) on `CatalogType` to see what each type means (unfortunately `help` doesn't show docstrings for attributes)." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.409703Z", - "iopub.status.busy": "2025-09-11T17:34:39.409361Z", - "iopub.status.idle": "2025-09-11T17:34:39.418646Z", - "shell.execute_reply": "2025-09-11T17:34:39.415257Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.409670Z" - } - }, - "outputs": [], - "source": [ - "catalog.save(catalog_type=pystac.CatalogType.SELF_CONTAINED)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.423656Z", - "iopub.status.busy": "2025-09-11T17:34:39.422133Z", - "iopub.status.idle": "2025-09-11T17:34:39.903665Z", - "shell.execute_reply": "2025-09-11T17:34:39.899167Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.423520Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/tmp/tmpjzy2xbol/stac/catalog.json\n", - "\n", - "/tmp/tmpjzy2xbol/stac/local-image:\n", - "local-image.json\n" - ] - } - ], - "source": [ - "!ls {tmp_dir.name}/stac/*" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.912197Z", - "iopub.status.busy": "2025-09-11T17:34:39.909380Z", - "iopub.status.idle": "2025-09-11T17:34:39.944091Z", - "shell.execute_reply": "2025-09-11T17:34:39.939296Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.911968Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"type\": \"Catalog\",\n", - " \"id\": \"test-catalog\",\n", - " \"stac_version\": \"1.1.0\",\n", - " \"description\": \"Tutorial catalog.\",\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"root\",\n", - " \"href\": \"./catalog.json\",\n", - " \"type\": \"application/json\"\n", - " },\n", - " {\n", - " \"rel\": \"item\",\n", - " \"href\": \"./local-image/local-image.json\",\n", - " \"type\": \"application/geo+json\"\n", - " }\n", - " ]\n", - "}\n" - ] - } - ], - "source": [ - "with open(catalog.self_href) as f:\n", - " print(f.read())" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.951241Z", - "iopub.status.busy": "2025-09-11T17:34:39.949644Z", - "iopub.status.idle": "2025-09-11T17:34:39.977350Z", - "shell.execute_reply": "2025-09-11T17:34:39.974102Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.951022Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"type\": \"Feature\",\n", - " \"stac_version\": \"1.1.0\",\n", - " \"stac_extensions\": [],\n", - " \"id\": \"local-image\",\n", - " \"geometry\": {\n", - " \"type\": \"Polygon\",\n", - " \"coordinates\": [\n", - " [\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ]\n", - " ]\n", - " ]\n", - " },\n", - " \"bbox\": [\n", - " 37.6616853489879,\n", - " 55.73478197572927,\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " \"properties\": {\n", - " \"datetime\": \"2025-09-11T17:34:39.240259Z\"\n", - " },\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"root\",\n", - " \"href\": \"../catalog.json\",\n", - " \"type\": \"application/json\"\n", - " },\n", - " {\n", - " \"rel\": \"parent\",\n", - " \"href\": \"../catalog.json\",\n", - " \"type\": \"application/json\"\n", - " }\n", - " ],\n", - " \"assets\": {\n", - " \"image\": {\n", - " \"href\": \"/tmp/tmpjzy2xbol/image.tif\",\n", - " \"type\": \"image/tiff; application=geotiff\"\n", - " }\n", - " }\n", - "}\n" - ] - } - ], - "source": [ - "with open(item.self_href) as f:\n", - " print(f.read())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see, all links are saved with relative paths. That's because we used `catalog_type=CatalogType.SELF_CONTAINED`. If we save an Absolute Published catalog, we'll see absolute paths:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:39.982814Z", - "iopub.status.busy": "2025-09-11T17:34:39.980791Z", - "iopub.status.idle": "2025-09-11T17:34:40.024036Z", - "shell.execute_reply": "2025-09-11T17:34:40.014479Z", - "shell.execute_reply.started": "2025-09-11T17:34:39.982669Z" - } - }, - "outputs": [], - "source": [ - "catalog.save(catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the links included in the STAC item are all absolute:" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.026179Z", - "iopub.status.busy": "2025-09-11T17:34:40.025917Z", - "iopub.status.idle": "2025-09-11T17:34:40.038197Z", - "shell.execute_reply": "2025-09-11T17:34:40.036140Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.026156Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"type\": \"Feature\",\n", - " \"stac_version\": \"1.1.0\",\n", - " \"stac_extensions\": [],\n", - " \"id\": \"local-image\",\n", - " \"geometry\": {\n", - " \"type\": \"Polygon\",\n", - " \"coordinates\": [\n", - " [\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ]\n", - " ]\n", - " ]\n", - " },\n", - " \"bbox\": [\n", - " 37.6616853489879,\n", - " 55.73478197572927,\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " \"properties\": {\n", - " \"datetime\": \"2025-09-11T17:34:39.240259Z\"\n", - " },\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"root\",\n", - " \"href\": \"/tmp/tmpjzy2xbol/stac/catalog.json\",\n", - " \"type\": \"application/json\"\n", - " },\n", - " {\n", - " \"rel\": \"parent\",\n", - " \"href\": \"/tmp/tmpjzy2xbol/stac/catalog.json\",\n", - " \"type\": \"application/json\"\n", - " },\n", - " {\n", - " \"rel\": \"self\",\n", - " \"href\": \"/tmp/tmpjzy2xbol/stac/local-image/local-image.json\",\n", - " \"type\": \"application/json\"\n", - " }\n", - " ],\n", - " \"assets\": {\n", - " \"image\": {\n", - " \"href\": \"/tmp/tmpjzy2xbol/image.tif\",\n", - " \"type\": \"image/tiff; application=geotiff\"\n", - " }\n", - " }\n", - "}\n" - ] - } - ], - "source": [ - "with open(item.get_self_href()) as f:\n", - " print(f.read())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that the Asset HREF is absolute in both cases. We can make the Asset HREF relative to the STAC Item by using `.make_all_asset_hrefs_relative()`:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.039260Z", - "iopub.status.busy": "2025-09-11T17:34:40.038939Z", - "iopub.status.idle": "2025-09-11T17:34:40.051146Z", - "shell.execute_reply": "2025-09-11T17:34:40.049037Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.039226Z" - } - }, - "outputs": [], - "source": [ - "catalog.make_all_asset_hrefs_relative()\n", - "catalog.save(catalog_type=pystac.CatalogType.SELF_CONTAINED)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.056354Z", - "iopub.status.busy": "2025-09-11T17:34:40.054822Z", - "iopub.status.idle": "2025-09-11T17:34:40.067961Z", - "shell.execute_reply": "2025-09-11T17:34:40.064026Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.056213Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"type\": \"Feature\",\n", - " \"stac_version\": \"1.1.0\",\n", - " \"stac_extensions\": [],\n", - " \"id\": \"local-image\",\n", - " \"geometry\": {\n", - " \"type\": \"Polygon\",\n", - " \"coordinates\": [\n", - " [\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " [\n", - " 37.66573047610874,\n", - " 55.73478197572927\n", - " ],\n", - " [\n", - " 37.6616853489879,\n", - " 55.73478197572927\n", - " ]\n", - " ]\n", - " ]\n", - " },\n", - " \"bbox\": [\n", - " 37.6616853489879,\n", - " 55.73478197572927,\n", - " 37.66573047610874,\n", - " 55.73882710285011\n", - " ],\n", - " \"properties\": {\n", - " \"datetime\": \"2025-09-11T17:34:39.240259Z\"\n", - " },\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"root\",\n", - " \"href\": \"../catalog.json\",\n", - " \"type\": \"application/json\"\n", - " },\n", - " {\n", - " \"rel\": \"parent\",\n", - " \"href\": \"../catalog.json\",\n", - " \"type\": \"application/json\"\n", - " }\n", - " ],\n", - " \"assets\": {\n", - " \"image\": {\n", - " \"href\": \"../../image.tif\",\n", - " \"type\": \"image/tiff; application=geotiff\"\n", - " }\n", - " }\n", - "}\n" - ] - } - ], - "source": [ - "with open(item.get_self_href()) as f:\n", - " print(f.read())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating an Item that implements the EO extension\n", - "\n", - "In the code above our item only implemented the core STAC Item specification. With [extensions](https://github.com/radiantearth/stac-spec/tree/v0.9.0/extensions) we can record more information and add additional functionality to the Item. Given that we know this is a World View 3 image that has earth observation data, we can enable the [eo extension](https://github.com/radiantearth/stac-spec/tree/v0.8.1/extensions/eo) to add band information." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To add eo information to an item we'll need to specify some more data. First, let's define the bands of World View 3:" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.074454Z", - "iopub.status.busy": "2025-09-11T17:34:40.072859Z", - "iopub.status.idle": "2025-09-11T17:34:40.089077Z", - "shell.execute_reply": "2025-09-11T17:34:40.084569Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.074311Z" - } - }, - "outputs": [], - "source": [ - "from pystac.extensions.eo import Band\n", - "\n", - "# From: https://www.spaceimagingme.com/downloads/sensors/datasheets/DG_WorldView3_DS_2014.pdf\n", - "\n", - "wv3_bands = [\n", - " Band.create(\n", - " name=\"Coastal\", description=\"Coastal: 400 - 450 nm\", common_name=\"coastal\"\n", - " ),\n", - " Band.create(name=\"Blue\", description=\"Blue: 450 - 510 nm\", common_name=\"blue\"),\n", - " Band.create(name=\"Green\", description=\"Green: 510 - 580 nm\", common_name=\"green\"),\n", - " Band.create(\n", - " name=\"Yellow\", description=\"Yellow: 585 - 625 nm\", common_name=\"yellow\"\n", - " ),\n", - " Band.create(name=\"Red\", description=\"Red: 630 - 690 nm\", common_name=\"red\"),\n", - " Band.create(\n", - " name=\"Red Edge\", description=\"Red Edge: 705 - 745 nm\", common_name=\"rededge\"\n", - " ),\n", - " Band.create(\n", - " name=\"Near-IR1\", description=\"Near-IR1: 770 - 895 nm\", common_name=\"nir08\"\n", - " ),\n", - " Band.create(\n", - " name=\"Near-IR2\", description=\"Near-IR2: 860 - 1040 nm\", common_name=\"nir09\"\n", - " ),\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that we used the `.create` method create new band information." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now create an Item, enable the eo extension, add the band information and add it to our catalog:" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.091982Z", - "iopub.status.busy": "2025-09-11T17:34:40.090896Z", - "iopub.status.idle": "2025-09-11T17:34:40.109370Z", - "shell.execute_reply": "2025-09-11T17:34:40.108494Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.091797Z" - } - }, - "outputs": [], - "source": [ - "eo_item = pystac.Item(\n", - " id=\"local-image-eo\",\n", - " geometry=footprint,\n", - " bbox=bbox,\n", - " datetime=datetime.utcnow(),\n", - " properties={},\n", - ")\n", - "eo_item.ext.add(\"eo\")\n", - "eo_item.ext.eo.bands = wv3_bands" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are also [common metadata](https://github.com/radiantearth/stac-spec/blob/v0.9.0/item-spec/common-metadata.md) fields that we can use to capture additional information about the WorldView 3 imagery:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.110474Z", - "iopub.status.busy": "2025-09-11T17:34:40.110247Z", - "iopub.status.idle": "2025-09-11T17:34:40.118516Z", - "shell.execute_reply": "2025-09-11T17:34:40.113515Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.110452Z" - } - }, - "outputs": [], - "source": [ - "eo_item.common_metadata.platform = \"Maxar\"\n", - "eo_item.common_metadata.instruments = [\"WorldView3\"]\n", - "eo_item.common_metadata.gsd = 0.3" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.120144Z", - "iopub.status.busy": "2025-09-11T17:34:40.119873Z", - "iopub.status.idle": "2025-09-11T17:34:40.146749Z", - "shell.execute_reply": "2025-09-11T17:34:40.141220Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.120119Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Item id=local-image-eo>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "eo_item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use the eo extension to add bands to the assets we add to the item:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.151104Z", - "iopub.status.busy": "2025-09-11T17:34:40.150774Z", - "iopub.status.idle": "2025-09-11T17:34:40.159942Z", - "shell.execute_reply": "2025-09-11T17:34:40.156499Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.151079Z" - } - }, - "outputs": [], - "source": [ - "asset = pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", - "eo_item.add_asset(\"image\", asset)\n", - "asset.ext.eo.bands = wv3_bands" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we look at the asset, we can see the appropriate band indexes are set:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.161890Z", - "iopub.status.busy": "2025-09-11T17:34:40.161460Z", - "iopub.status.idle": "2025-09-11T17:34:40.168427Z", - "shell.execute_reply": "2025-09-11T17:34:40.167804Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.161856Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Asset href=/tmp/tmpjzy2xbol/image.tif>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "asset" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's clear the in-memory catalog, add the EO item, and save to a new STAC:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.169341Z", - "iopub.status.busy": "2025-09-11T17:34:40.169117Z", - "iopub.status.idle": "2025-09-11T17:34:40.175972Z", - "shell.execute_reply": "2025-09-11T17:34:40.175157Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.169320Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "catalog.clear_items()\n", - "list(catalog.get_items())" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.176949Z", - "iopub.status.busy": "2025-09-11T17:34:40.176713Z", - "iopub.status.idle": "2025-09-11T17:34:40.182432Z", - "shell.execute_reply": "2025-09-11T17:34:40.181574Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.176928Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "catalog.add_item(eo_item)\n", - "list(catalog.get_items())" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.183488Z", - "iopub.status.busy": "2025-09-11T17:34:40.183163Z", - "iopub.status.idle": "2025-09-11T17:34:40.191846Z", - "shell.execute_reply": "2025-09-11T17:34:40.189964Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.183452Z" - } - }, - "outputs": [], - "source": [ - "catalog.normalize_and_save(\n", - " root_href=os.path.join(tmp_dir.name, \"stac-eo\"),\n", - " catalog_type=pystac.CatalogType.SELF_CONTAINED,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, if we read the catalog from the filesystem, PySTAC recognizes that the item implements eo and so use it's functionality, e.g. getting the bands off the asset:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.193027Z", - "iopub.status.busy": "2025-09-11T17:34:40.192739Z", - "iopub.status.idle": "2025-09-11T17:34:40.200721Z", - "shell.execute_reply": "2025-09-11T17:34:40.200122Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.192999Z" - } - }, - "outputs": [], - "source": [ - "catalog2 = pystac.read_file(os.path.join(tmp_dir.name, \"stac-eo\", \"catalog.json\"))" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.204907Z", - "iopub.status.busy": "2025-09-11T17:34:40.203318Z", - "iopub.status.idle": "2025-09-11T17:34:40.222371Z", - "shell.execute_reply": "2025-09-11T17:34:40.219982Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.204765Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert isinstance(catalog2, pystac.Catalog)\n", - "list(catalog2.get_items())" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.239082Z", - "iopub.status.busy": "2025-09-11T17:34:40.238693Z", - "iopub.status.idle": "2025-09-11T17:34:40.246510Z", - "shell.execute_reply": "2025-09-11T17:34:40.243414Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.239036Z" - } - }, - "outputs": [], - "source": [ - "item: pystac.Item = next(catalog2.get_items(recursive=True))" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.249481Z", - "iopub.status.busy": "2025-09-11T17:34:40.249043Z", - "iopub.status.idle": "2025-09-11T17:34:40.253396Z", - "shell.execute_reply": "2025-09-11T17:34:40.252592Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.249431Z" - } - }, - "outputs": [], - "source": [ - "assert item.ext.has(\"eo\")" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.254625Z", - "iopub.status.busy": "2025-09-11T17:34:40.254329Z", - "iopub.status.idle": "2025-09-11T17:34:40.265308Z", - "shell.execute_reply": "2025-09-11T17:34:40.262693Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.254595Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.assets[\"image\"].ext.eo.bands" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Collections\n", - "\n", - "Collections are a subtype of Catalog that have some additional properties to make them more searchable. They also can define common properties so that items in the collection don't have to duplicate common data for each item. Let's create a collection to hold common properties between two images from the Spacenet 5 challenge.\n", - "\n", - "First we'll get another image, and it's bbox and footprint:" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.270138Z", - "iopub.status.busy": "2025-09-11T17:34:40.268568Z", - "iopub.status.idle": "2025-09-11T17:34:40.674409Z", - "shell.execute_reply": "2025-09-11T17:34:40.673622Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.269944Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "('/tmp/tmpjzy2xbol/image.tif', )" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "url2 = (\n", - " \"https://spacenet-dataset.s3.amazonaws.com/\"\n", - " \"spacenet/SN5_roads/train/AOI_7_Moscow/MS/\"\n", - " \"SN5_roads_train_AOI_7_Moscow_MS_chip997.tif\"\n", - ")\n", - "img_path2 = os.path.join(tmp_dir.name, \"image.tif\")\n", - "urllib.request.urlretrieve(url2, img_path2)" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.675340Z", - "iopub.status.busy": "2025-09-11T17:34:40.675037Z", - "iopub.status.idle": "2025-09-11T17:34:40.684175Z", - "shell.execute_reply": "2025-09-11T17:34:40.682564Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.675308Z" - } - }, - "outputs": [], - "source": [ - "bbox2, footprint2 = get_bbox_and_footprint(img_path2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can take a look at the pydocs for Collection to see what information we need to supply in order to satisfy the spec." - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.687122Z", - "iopub.status.busy": "2025-09-11T17:34:40.686818Z", - "iopub.status.idle": "2025-09-11T17:34:40.699666Z", - "shell.execute_reply": "2025-09-11T17:34:40.695288Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.687088Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCollection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextent\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Extent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[str] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcatalog_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'CatalogType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlicense\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'other'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mkeywords\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[str] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mproviders\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'list[Provider] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0msummaries\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Summaries | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0massets\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Asset] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstrategy\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'HrefLayoutStrategy | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "A Collection extends the Catalog spec with additional metadata that helps\n", - "enable discovery.\n", - "\n", - "Args:\n", - " id : Identifier for the collection. Must be unique within the STAC.\n", - " description : Detailed multi-line description to fully explain the\n", - " collection. `CommonMark 0.29 syntax `_ MAY\n", - " be used for rich text representation.\n", - " extent : Spatial and temporal extents that describe the bounds of\n", - " all items contained within this Collection.\n", - " title : Optional short descriptive one-line title for the\n", - " collection.\n", - " stac_extensions : Optional list of extensions the Collection\n", - " implements.\n", - " href : Optional HREF for this collection, which be set as the\n", - " collection's self link's HREF.\n", - " catalog_type : Optional catalog type for this catalog. Must\n", - " be one of the values in :class`~pystac.CatalogType`.\n", - " license : Collection's license(s) as a\n", - " `SPDX License identifier `_,\n", - " or `other`. If collection includes data with multiple\n", - " different licenses, use `other` and add a link for\n", - " each. The licenses `various` and `proprietary` are deprecated.\n", - " Defaults to 'other'.\n", - " keywords : Optional list of keywords describing the collection.\n", - " providers : Optional list of providers of this Collection.\n", - " summaries : An optional map of property summaries,\n", - " either a set of values or statistics such as a range.\n", - " extra_fields : Extra fields that are part of the top-level\n", - " JSON properties of the Collection.\n", - " assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All\n", - " :class:`~pystac.Asset` values in the dictionary will have their\n", - " :attr:`~pystac.Asset.owner` attribute set to the created Collection.\n", - " strategy : The layout strategy to use for setting the\n", - " HREFs of the catalog child objects and items.\n", - " If not provided, it will default to strategy of the parent and fallback to\n", - " :class:`~pystac.layout.BestPracticesLayoutStrategy`.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/collection.py\n", - "\u001b[0;31mType:\u001b[0m _ProtocolMeta\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "?pystac.Collection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Beyond what a Catalog requires, a Collection requires a license, and an `Extent` that describes the range of space and time that the items it hold occupy." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.700661Z", - "iopub.status.busy": "2025-09-11T17:34:40.700439Z", - "iopub.status.idle": "2025-09-11T17:34:40.706589Z", - "shell.execute_reply": "2025-09-11T17:34:40.705603Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.700640Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mExtent\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mspatial\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'SpatialExtent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtemporal\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'TemporalExtent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dict[str, Any] | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "Describes the spatiotemporal extents of a Collection.\n", - "\n", - "Args:\n", - " spatial : Potential spatial extent covered by the collection.\n", - " temporal : Potential temporal extent covered by the collection.\n", - " extra_fields : Dictionary containing additional top-level fields defined on the\n", - " Extent object.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/collection.py\n", - "\u001b[0;31mType:\u001b[0m type\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "?pystac.Extent" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "An Extent is comprised of a SpatialExtent and a TemporalExtent. These hold one or more bounding boxes and time intervals, respectively, that completely cover the items contained in the collections.\n", - "\n", - "Let's start with creating two new items - these will be core Items. We can set these items to implement the `eo` extension by specifying them in the `stac_extensions`." - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.707924Z", - "iopub.status.busy": "2025-09-11T17:34:40.707538Z", - "iopub.status.idle": "2025-09-11T17:34:40.719902Z", - "shell.execute_reply": "2025-09-11T17:34:40.716919Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.707895Z" - } - }, - "outputs": [], - "source": [ - "collection_item = pystac.Item(\n", - " id=\"local-image-col-1\",\n", - " geometry=footprint,\n", - " bbox=bbox,\n", - " datetime=datetime.utcnow(),\n", - " properties={},\n", - ")\n", - "\n", - "collection_item.common_metadata.gsd = 0.3\n", - "collection_item.common_metadata.platform = \"Maxar\"\n", - "collection_item.common_metadata.instruments = [\"WorldView3\"]\n", - "\n", - "asset = pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", - "collection_item.add_asset(\"image\", asset)\n", - "asset.ext.add(\"eo\")\n", - "asset.ext.eo.bands = wv3_bands\n", - "\n", - "collection_item2 = pystac.Item(\n", - " id=\"local-image-col-2\",\n", - " geometry=footprint2,\n", - " bbox=bbox2,\n", - " datetime=datetime.utcnow(),\n", - " properties={},\n", - ")\n", - "\n", - "collection_item2.common_metadata.gsd = 0.3\n", - "collection_item2.common_metadata.platform = \"Maxar\"\n", - "collection_item2.common_metadata.instruments = [\"WorldView3\"]\n", - "\n", - "asset2 = pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", - "collection_item2.add_asset(\"image\", asset2)\n", - "asset2.ext.add(\"eo\")\n", - "asset2.ext.eo.bands = [\n", - " band for band in wv3_bands if band.name in [\"Red\", \"Green\", \"Blue\"]\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use our two items' metadata to find out what the proper bounds are:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.720929Z", - "iopub.status.busy": "2025-09-11T17:34:40.720675Z", - "iopub.status.idle": "2025-09-11T17:34:40.725156Z", - "shell.execute_reply": "2025-09-11T17:34:40.724438Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.720904Z" - } - }, - "outputs": [], - "source": [ - "from shapely.geometry import shape\n", - "\n", - "unioned_footprint = shape(footprint).union(shape(footprint2))\n", - "collection_bbox = list(unioned_footprint.bounds)\n", - "spatial_extent = pystac.SpatialExtent(bboxes=[collection_bbox])" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.726167Z", - "iopub.status.busy": "2025-09-11T17:34:40.725796Z", - "iopub.status.idle": "2025-09-11T17:34:40.730562Z", - "shell.execute_reply": "2025-09-11T17:34:40.728577Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.726140Z" - } - }, - "outputs": [], - "source": [ - "collection_interval = sorted([collection_item.datetime, collection_item2.datetime])\n", - "temporal_extent = pystac.TemporalExtent(intervals=[collection_interval])" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.732100Z", - "iopub.status.busy": "2025-09-11T17:34:40.731809Z", - "iopub.status.idle": "2025-09-11T17:34:40.736376Z", - "shell.execute_reply": "2025-09-11T17:34:40.734729Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.732073Z" - } - }, - "outputs": [], - "source": [ - "collection_extent = pystac.Extent(spatial=spatial_extent, temporal=temporal_extent)" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.737721Z", - "iopub.status.busy": "2025-09-11T17:34:40.737479Z", - "iopub.status.idle": "2025-09-11T17:34:40.741105Z", - "shell.execute_reply": "2025-09-11T17:34:40.740424Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.737699Z" - } - }, - "outputs": [], - "source": [ - "collection = pystac.Collection(\n", - " id=\"wv3-images\",\n", - " description=\"Spacenet 5 images over Moscow\",\n", - " extent=collection_extent,\n", - " license=\"CC-BY-SA-4.0\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now if we add our items to our Collection, and our Collection to our Catalog, we get the following STAC that can be saved:" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.744836Z", - "iopub.status.busy": "2025-09-11T17:34:40.742919Z", - "iopub.status.idle": "2025-09-11T17:34:40.760500Z", - "shell.execute_reply": "2025-09-11T17:34:40.759918Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.744674Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>,\n", - " >]" - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.add_items([collection_item, collection_item2])" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.761397Z", - "iopub.status.busy": "2025-09-11T17:34:40.761176Z", - "iopub.status.idle": "2025-09-11T17:34:40.768145Z", - "shell.execute_reply": "2025-09-11T17:34:40.765795Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.761377Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=child target=<Collection id=wv3-images>>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 54, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "catalog.clear_items()\n", - "catalog.clear_children()\n", - "catalog.add_child(collection)" - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.769946Z", - "iopub.status.busy": "2025-09-11T17:34:40.769691Z", - "iopub.status.idle": "2025-09-11T17:34:40.774752Z", - "shell.execute_reply": "2025-09-11T17:34:40.773922Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.769922Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n", - " * \n", - " * \n" - ] - } - ], - "source": [ - "catalog.describe()" - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.775801Z", - "iopub.status.busy": "2025-09-11T17:34:40.775544Z", - "iopub.status.idle": "2025-09-11T17:34:40.784824Z", - "shell.execute_reply": "2025-09-11T17:34:40.784015Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.775777Z" - } - }, - "outputs": [], - "source": [ - "catalog.normalize_and_save(\n", - " root_href=os.path.join(tmp_dir.name, \"stac-collection\"),\n", - " catalog_type=pystac.CatalogType.SELF_CONTAINED,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Cleanup\n", - "\n", - "Don't forget to clean up the temporary directory!" - ] - }, - { - "cell_type": "code", - "execution_count": 57, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.785964Z", - "iopub.status.busy": "2025-09-11T17:34:40.785696Z", - "iopub.status.idle": "2025-09-11T17:34:40.790373Z", - "shell.execute_reply": "2025-09-11T17:34:40.789749Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.785939Z" - } - }, - "outputs": [], - "source": [ - "tmp_dir.cleanup()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating a STAC of imagery from Spacenet 5 data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's take what we've learned and create a Catalog with more data in it.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Allowing PySTAC to read from AWS S3\n", - "\n", - "PySTAC aims to be virtually zero-dependency (notwithstanding the why-isn't-this-in-stdlib datetime-util), so it doesn't have the ability to read from or write to anything but the local file system. However, we can hook into PySTAC's IO in the following way. Learn more about how to customize I/O in STAC from the [documentation](https://pystac.readthedocs.io/en/stable/concepts.html#i-o-in-pystac):" - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.791263Z", - "iopub.status.busy": "2025-09-11T17:34:40.791013Z", - "iopub.status.idle": "2025-09-11T17:34:40.800283Z", - "shell.execute_reply": "2025-09-11T17:34:40.798485Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.791240Z" - } - }, - "outputs": [], - "source": [ - "from typing import Any, Union\n", - "from urllib.parse import urlparse\n", - "\n", - "import boto3\n", - "\n", - "from pystac import Link\n", - "from pystac.stac_io import DefaultStacIO\n", - "\n", - "\n", - "class CustomStacIO(DefaultStacIO):\n", - " def __init__(self):\n", - " self.s3 = boto3.resource(\"s3\")\n", - " super().__init__()\n", - "\n", - " def read_text(self, source: Union[str, Link], *args: Any, **kwargs: Any) -> str:\n", - " parsed = urlparse(source)\n", - " if parsed.scheme == \"s3\":\n", - " bucket = parsed.netloc\n", - " key = parsed.path[1:]\n", - "\n", - " obj = self.s3.Object(bucket, key)\n", - " return obj.get()[\"Body\"].read().decode(\"utf-8\")\n", - " else:\n", - " return super().read_text(source, *args, **kwargs)\n", - "\n", - " def write_text(\n", - " self, dest: Union[str, Link], txt: str, *args: Any, **kwargs: Any\n", - " ) -> None:\n", - " parsed = urlparse(dest)\n", - " if parsed.scheme == \"s3\":\n", - " bucket = parsed.netloc\n", - " key = parsed.path[1:]\n", - " self.s3.Object(bucket, key).put(Body=txt, ContentEncoding=\"utf-8\")\n", - " else:\n", - " super().write_text(dest, txt, *args, **kwargs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We'll need a utility to list keys for reading the lists of files from S3:" - ] - }, - { - "cell_type": "code", - "execution_count": 59, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.805001Z", - "iopub.status.busy": "2025-09-11T17:34:40.803327Z", - "iopub.status.idle": "2025-09-11T17:34:40.812484Z", - "shell.execute_reply": "2025-09-11T17:34:40.810463Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.804855Z" - } - }, - "outputs": [], - "source": [ - "# From https://alexwlchan.net/2017/07/listing-s3-keys/\n", - "from botocore import UNSIGNED\n", - "from botocore.config import Config\n", - "\n", - "\n", - "def get_s3_keys(bucket, prefix):\n", - " \"\"\"Generate all the keys in an S3 bucket.\"\"\"\n", - " s3 = boto3.client(\"s3\", config=Config(signature_version=UNSIGNED))\n", - " kwargs = {\"Bucket\": bucket, \"Prefix\": prefix}\n", - " while True:\n", - " resp = s3.list_objects_v2(**kwargs)\n", - " for obj in resp[\"Contents\"]:\n", - " yield obj[\"Key\"]\n", - "\n", - " try:\n", - " kwargs[\"ContinuationToken\"] = resp[\"NextContinuationToken\"]\n", - " except KeyError:\n", - " break" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's make a STAC of imagery over Moscow as part of the Spacenet 5 challenge. As a first step, we can list out the imagery and extract IDs from each of the chips." - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:40.813459Z", - "iopub.status.busy": "2025-09-11T17:34:40.813221Z", - "iopub.status.idle": "2025-09-11T17:34:46.867759Z", - "shell.execute_reply": "2025-09-11T17:34:46.867159Z", - "shell.execute_reply.started": "2025-09-11T17:34:40.813436Z" - } - }, - "outputs": [], - "source": [ - "moscow_training_chip_uris = list(\n", - " get_s3_keys(\n", - " bucket=\"spacenet-dataset\", prefix=\"spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/\"\n", - " )\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 61, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:46.868640Z", - "iopub.status.busy": "2025-09-11T17:34:46.868433Z", - "iopub.status.idle": "2025-09-11T17:34:46.879549Z", - "shell.execute_reply": "2025-09-11T17:34:46.878830Z", - "shell.execute_reply.started": "2025-09-11T17:34:46.868620Z" - } - }, - "outputs": [], - "source": [ - "import re\n", - "\n", - "chip_id_to_data = {}\n", - "\n", - "\n", - "def get_chip_id(uri):\n", - " return re.search(r\".*\\_chip(\\d+)\\.\", uri).group(1)\n", - "\n", - "\n", - "for uri in moscow_training_chip_uris:\n", - " chip_id = get_chip_id(uri)\n", - " chip_id_to_data[chip_id] = {\"img\": \"s3://spacenet-dataset/{}\".format(uri)}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this tutorial, we'll only take a subset of the data." - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:46.880764Z", - "iopub.status.busy": "2025-09-11T17:34:46.880497Z", - "iopub.status.idle": "2025-09-11T17:34:46.896891Z", - "shell.execute_reply": "2025-09-11T17:34:46.895894Z", - "shell.execute_reply.started": "2025-09-11T17:34:46.880741Z" - } - }, - "outputs": [], - "source": [ - "chip_id_to_data = dict(list(chip_id_to_data.items())[:10])" - ] - }, - { - "cell_type": "code", - "execution_count": 63, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:46.901454Z", - "iopub.status.busy": "2025-09-11T17:34:46.899631Z", - "iopub.status.idle": "2025-09-11T17:34:46.919356Z", - "shell.execute_reply": "2025-09-11T17:34:46.918698Z", - "shell.execute_reply.started": "2025-09-11T17:34:46.901291Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'0': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip0.tif'},\n", - " '1': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1.tif'},\n", - " '10': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip10.tif'},\n", - " '100': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip100.tif'},\n", - " '1000': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1000.tif'},\n", - " '1001': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1001.tif'},\n", - " '1002': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1002.tif'},\n", - " '1003': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1003.tif'},\n", - " '1004': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1004.tif'},\n", - " '1005': {'img': 's3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1005.tif'}}" - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "chip_id_to_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's turn each of those chips into a STAC Item that represents the image." - ] - }, - { - "cell_type": "code", - "execution_count": 64, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:46.920294Z", - "iopub.status.busy": "2025-09-11T17:34:46.920044Z", - "iopub.status.idle": "2025-09-11T17:34:46.925925Z", - "shell.execute_reply": "2025-09-11T17:34:46.923655Z", - "shell.execute_reply.started": "2025-09-11T17:34:46.920272Z" - } - }, - "outputs": [], - "source": [ - "chip_id_to_items = {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We'll create core `Item`s for our imagery, but mark them with the `eo` extension as we did above, and store the `eo` data in a `Collection`.\n", - "\n", - "Note that the image CRS is in WGS:84 (Lat/Lng). If it wasn't, we'd have to reproject the footprint to WGS:84 in order to be compliant with the spec (which can easily be done with [pyproj](https://github.com/pyproj4/pyproj)).\n", - "\n", - "Here we're taking advantage of `rasterio`'s ability to read S3 URIs, which only grabs the GeoTIFF metadata and does not pull the whole file down." - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:46.927740Z", - "iopub.status.busy": "2025-09-11T17:34:46.927417Z", - "iopub.status.idle": "2025-09-11T17:34:51.498898Z", - "shell.execute_reply": "2025-09-11T17:34:51.495995Z", - "shell.execute_reply.started": "2025-09-11T17:34:46.927713Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip0.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip10.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip100.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1000.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1001.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1002.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1003.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1004.tif\n", - "Processing s3://spacenet-dataset/spacenet/SN5_roads/train/AOI_7_Moscow/PS-MS/SN5_roads_train_AOI_7_Moscow_PS-MS_chip1005.tif\n" - ] - } - ], - "source": [ - "import os\n", - "\n", - "os.environ[\"AWS_NO_SIGN_REQUEST\"] = \"true\"\n", - "\n", - "for chip_id in chip_id_to_data:\n", - " img_uri = chip_id_to_data[chip_id][\"img\"]\n", - " print(\"Processing {}\".format(img_uri))\n", - " bbox, footprint = get_bbox_and_footprint(img_uri)\n", - "\n", - " item = pystac.Item(\n", - " id=\"img_{}\".format(chip_id),\n", - " geometry=footprint,\n", - " bbox=bbox,\n", - " datetime=datetime.utcnow(),\n", - " properties={},\n", - " )\n", - "\n", - " item.common_metadata.gsd = 0.3\n", - " item.common_metadata.platform = \"Maxar\"\n", - " item.common_metadata.instruments = [\"WorldView3\"]\n", - "\n", - " item.ext.add(\"eo\")\n", - " item.ext.eo.bands = wv3_bands\n", - " asset = pystac.Asset(href=img_uri, media_type=pystac.MediaType.COG)\n", - " item.add_asset(key=\"ps-ms\", asset=asset)\n", - " asset.ext.eo.bands = wv3_bands\n", - " chip_id_to_items[chip_id] = item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating the Collection\n", - "\n", - "All of these images are over Moscow. In Spacenet 5, we have a couple cities that have imagery; a good way to separate these collections of imagery. We can store all of the common `eo` metadata in the collection." - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.504759Z", - "iopub.status.busy": "2025-09-11T17:34:51.502523Z", - "iopub.status.idle": "2025-09-11T17:34:51.512282Z", - "shell.execute_reply": "2025-09-11T17:34:51.511530Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.504250Z" - } - }, - "outputs": [], - "source": [ - "from shapely.geometry import MultiPolygon, shape\n", - "\n", - "footprints = list(map(lambda i: shape(i.geometry).envelope, chip_id_to_items.values()))\n", - "collection_bbox = MultiPolygon(footprints).bounds\n", - "spatial_extent = pystac.SpatialExtent(bboxes=[collection_bbox])" - ] - }, - { - "cell_type": "code", - "execution_count": 67, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.513268Z", - "iopub.status.busy": "2025-09-11T17:34:51.513029Z", - "iopub.status.idle": "2025-09-11T17:34:51.537453Z", - "shell.execute_reply": "2025-09-11T17:34:51.533824Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.513247Z" - } - }, - "outputs": [], - "source": [ - "datetimes = sorted(list(map(lambda i: i.datetime, chip_id_to_items.values())))\n", - "temporal_extent = pystac.TemporalExtent(intervals=[[datetimes[0], datetimes[-1]]])" - ] - }, - { - "cell_type": "code", - "execution_count": 68, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.538921Z", - "iopub.status.busy": "2025-09-11T17:34:51.538656Z", - "iopub.status.idle": "2025-09-11T17:34:51.544139Z", - "shell.execute_reply": "2025-09-11T17:34:51.542713Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.538896Z" - } - }, - "outputs": [], - "source": [ - "collection_extent = pystac.Extent(spatial=spatial_extent, temporal=temporal_extent)" - ] - }, - { - "cell_type": "code", - "execution_count": 69, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.546614Z", - "iopub.status.busy": "2025-09-11T17:34:51.546338Z", - "iopub.status.idle": "2025-09-11T17:34:51.550776Z", - "shell.execute_reply": "2025-09-11T17:34:51.549729Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.546590Z" - } - }, - "outputs": [], - "source": [ - "collection = pystac.Collection(\n", - " id=\"wv3-images\",\n", - " description=\"Spacenet 5 images over Moscow\",\n", - " extent=collection_extent,\n", - " license=\"CC-BY-SA-4.0\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 70, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.552351Z", - "iopub.status.busy": "2025-09-11T17:34:51.551710Z", - "iopub.status.idle": "2025-09-11T17:34:51.559532Z", - "shell.execute_reply": "2025-09-11T17:34:51.558758Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.552121Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>,\n", - " >,\n", - " >,\n", - " >,\n", - " >,\n", - " >,\n", - " >,\n", - " >,\n", - " >,\n", - " >]" - ] - }, - "execution_count": 70, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.add_items(chip_id_to_items.values())" - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.563283Z", - "iopub.status.busy": "2025-09-11T17:34:51.562799Z", - "iopub.status.idle": "2025-09-11T17:34:51.568210Z", - "shell.execute_reply": "2025-09-11T17:34:51.566752Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.563252Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n" - ] - } - ], - "source": [ - "collection.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can create a Catalog and add the collection." - ] - }, - { - "cell_type": "code", - "execution_count": 72, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.570857Z", - "iopub.status.busy": "2025-09-11T17:34:51.570555Z", - "iopub.status.idle": "2025-09-11T17:34:51.579217Z", - "shell.execute_reply": "2025-09-11T17:34:51.576538Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.570830Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=child target=<Collection id=wv3-images>>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 72, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "catalog = pystac.Catalog(id=\"spacenet5\", description=\"Spacenet 5 Data (Test)\")\n", - "catalog.add_child(collection)" - ] - }, - { - "cell_type": "code", - "execution_count": 73, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:51.582829Z", - "iopub.status.busy": "2025-09-11T17:34:51.582174Z", - "iopub.status.idle": "2025-09-11T17:34:51.588996Z", - "shell.execute_reply": "2025-09-11T17:34:51.588228Z", - "shell.execute_reply.started": "2025-09-11T17:34:51.582628Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n" - ] - } - ], - "source": [ - "catalog.describe()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorials/how-to-read-data-from-stac.ipynb b/docs/tutorials/how-to-read-data-from-stac.ipynb deleted file mode 100644 index ec262c1f8..000000000 --- a/docs/tutorials/how-to-read-data-from-stac.ipynb +++ /dev/null @@ -1,7466 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "be2c57c1-798a-4eaf-b2b8-41c261b657d1", - "metadata": {}, - "source": [ - "# How to read data from STAC\n", - "\n", - "This notebook shows how to read the data in from a STAC asset using [xarray](https://docs.xarray.dev/en/stable/) and a little hidden helper library called [xpystac](https://pypi.org/project/xpystac/).\n", - "\n", - "## tl;dr\n", - "\n", - "For any PySTAC asset that can be represented as an xarray dataset you can read the data using the following command:\n", - "\n", - "```python\n", - "xr.open_dataset(asset)\n", - "```\n", - "\n", - "If you want to load multiple assets from the same item and/or many items at once use:\n", - "\n", - "\n", - "```python\n", - "odc.stac.load([items])\n", - "```\n", - "\n", - "## Dependencies\n", - "\n", - "There are lots of optional dependencies depending on where and how the data you are interested in are stored. Here are some of the libraries that you will probably need:\n", - "\n", - "- dask - to delay data loading until access\n", - "- pystac - STAC object structures\n", - "- xarray, rioxarray - data structures\n", - "- xpystac, odc-stac - helper for loading pystac into xarray objects" - ] - }, - { - "cell_type": "markdown", - "id": "ad3fb6dc-3529-47bd-a5b3-f5260f23db88", - "metadata": {}, - "source": [ - "Despite all these install instructions, the import block is very straightforward" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "2a8afebd-b397-4e7a-b448-0f59cc030e66", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:55:58.808745Z", - "iopub.status.busy": "2025-08-21T19:55:58.808345Z", - "iopub.status.idle": "2025-08-21T19:56:01.983744Z", - "shell.execute_reply": "2025-08-21T19:56:01.983228Z", - "shell.execute_reply.started": "2025-08-21T19:55:58.808709Z" - } - }, - "outputs": [], - "source": [ - "import odc.stac\n", - "import planetary_computer\n", - "import rioxarray\n", - "import xarray as xr\n", - "\n", - "import pystac" - ] - }, - { - "cell_type": "markdown", - "id": "6b24745c-b2d5-43d6-9c7e-66458b3a88e3", - "metadata": {}, - "source": [ - "## Examples\n", - "\n", - "Here are a few examples of the different types of objects that you can open in xarray." - ] - }, - { - "cell_type": "markdown", - "id": "30da7cfd-2861-4095-b15b-9952a7d824d9", - "metadata": {}, - "source": [ - "### COGs\n", - "\n", - "Read all the data from the COGs referenced by the assets on an item." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c77432e6-8b0d-44d2-a947-ec74a529b8cb", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:56:03.945808Z", - "iopub.status.busy": "2025-08-21T19:56:03.945372Z", - "iopub.status.idle": "2025-08-21T19:56:05.448439Z", - "shell.execute_reply": "2025-08-21T19:56:05.448060Z", - "shell.execute_reply.started": "2025-08-21T19:56:03.945773Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.Dataset> Size: 2GB\n",
-       "Dimensions:      (y: 7801, x: 7761, time: 1)\n",
-       "Coordinates:\n",
-       "  * y            (y) float64 62kB -3.713e+06 -3.713e+06 ... -3.947e+06\n",
-       "  * x            (x) float64 62kB 3.774e+05 3.774e+05 ... 6.102e+05 6.102e+05\n",
-       "    spatial_ref  int32 4B 32656\n",
-       "  * time         (time) datetime64[ns] 8B 2023-04-08T23:37:51.630731\n",
-       "Data variables: (12/19)\n",
-       "    qa           (time, y, x) int16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    red          (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    blue         (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    drad         (time, y, x) int16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    emis         (time, y, x) int16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    emsd         (time, y, x) int16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    ...           ...\n",
-       "    swir16       (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    swir22       (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    coastal      (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    qa_pixel     (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    qa_radsat    (time, y, x) uint16 121MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>\n",
-       "    qa_aerosol   (time, y, x) uint8 61MB dask.array<chunksize=(1, 1024, 1024), meta=np.ndarray>
" - ], - "text/plain": [ - " Size: 2GB\n", - "Dimensions: (y: 7801, x: 7761, time: 1)\n", - "Coordinates:\n", - " * y (y) float64 62kB -3.713e+06 -3.713e+06 ... -3.947e+06\n", - " * x (x) float64 62kB 3.774e+05 3.774e+05 ... 6.102e+05 6.102e+05\n", - " spatial_ref int32 4B 32656\n", - " * time (time) datetime64[ns] 8B 2023-04-08T23:37:51.630731\n", - "Data variables: (12/19)\n", - " qa (time, y, x) int16 121MB dask.array\n", - " red (time, y, x) uint16 121MB dask.array\n", - " blue (time, y, x) uint16 121MB dask.array\n", - " drad (time, y, x) int16 121MB dask.array\n", - " emis (time, y, x) int16 121MB dask.array\n", - " emsd (time, y, x) int16 121MB dask.array\n", - " ... ...\n", - " swir16 (time, y, x) uint16 121MB dask.array\n", - " swir22 (time, y, x) uint16 121MB dask.array\n", - " coastal (time, y, x) uint16 121MB dask.array\n", - " qa_pixel (time, y, x) uint16 121MB dask.array\n", - " qa_radsat (time, y, x) uint16 121MB dask.array\n", - " qa_aerosol (time, y, x) uint8 61MB dask.array" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "landsat_item = pystac.Item.from_file(\n", - " \"https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-c2-l2/items/LC09_L2SP_088084_20230408_02_T2\",\n", - ")\n", - "\n", - "ds = odc.stac.load(\n", - " [landsat_item], chunks={\"x\": 1024, \"y\": 1024}, patch_url=planetary_computer.sign\n", - ")\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "id": "db92558d-4d8c-4afd-873e-76de202048b7", - "metadata": {}, - "source": [ - "Let's prove that we really can access the data within the COGs." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "add6347a-df37-495e-9794-7d86795d18f8", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:56:05.449425Z", - "iopub.status.busy": "2025-08-21T19:56:05.449167Z", - "iopub.status.idle": "2025-08-21T19:56:10.768178Z", - "shell.execute_reply": "2025-08-21T19:56:10.767351Z", - "shell.execute_reply.started": "2025-08-21T19:56:05.449403Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 2.68 s, sys: 459 ms, total: 3.14 s\n", - "Wall time: 5.31 s\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.DataArray 'blue' ()> Size: 8B\n",
-       "array(13940.01841915)\n",
-       "Coordinates:\n",
-       "    spatial_ref  int32 4B 32656
" - ], - "text/plain": [ - " Size: 8B\n", - "array(13940.01841915)\n", - "Coordinates:\n", - " spatial_ref int32 4B 32656" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%time\n", - "ds.blue.mean().compute()" - ] - }, - { - "cell_type": "markdown", - "id": "65ed7b6d-2e24-46cf-a61f-bc17df20588c", - "metadata": {}, - "source": [ - "For more control over the individual COGs referenced by the assets, you can grab the href off the asset, sign it and then use it directly." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "5873b326-820b-4eec-8ba3-c3207bf8f9ce", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:56:10.769208Z", - "iopub.status.busy": "2025-08-21T19:56:10.768974Z", - "iopub.status.idle": "2025-08-21T19:56:11.425713Z", - "shell.execute_reply": "2025-08-21T19:56:11.424874Z", - "shell.execute_reply.started": "2025-08-21T19:56:10.769187Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.DataArray (band: 1, y: 122, x: 122)> Size: 30kB\n",
-       "[14884 values with dtype=uint16]\n",
-       "Coordinates:\n",
-       "  * band         (band) int64 8B 1\n",
-       "  * x            (x) float64 976B 3.783e+05 3.802e+05 ... 6.074e+05 6.093e+05\n",
-       "  * y            (y) float64 976B -3.714e+06 -3.716e+06 ... -3.946e+06\n",
-       "    spatial_ref  int64 8B 0\n",
-       "Attributes:\n",
-       "    AREA_OR_POINT:  Point\n",
-       "    _FillValue:     0\n",
-       "    scale_factor:   1.0\n",
-       "    add_offset:     0.0
" - ], - "text/plain": [ - " Size: 30kB\n", - "[14884 values with dtype=uint16]\n", - "Coordinates:\n", - " * band (band) int64 8B 1\n", - " * x (x) float64 976B 3.783e+05 3.802e+05 ... 6.074e+05 6.093e+05\n", - " * y (y) float64 976B -3.714e+06 -3.716e+06 ... -3.946e+06\n", - " spatial_ref int64 8B 0\n", - "Attributes:\n", - " AREA_OR_POINT: Point\n", - " _FillValue: 0\n", - " scale_factor: 1.0\n", - " add_offset: 0.0" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "landsat_asset = landsat_item.assets[\"blue\"]\n", - "landsat_asset_href = planetary_computer.sign(landsat_asset).href\n", - "\n", - "da = rioxarray.open_rasterio(landsat_asset_href, overview_level=5)\n", - "da" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "156a1c68-2d3f-48f1-8833-92d014b148c1", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:56:11.426154Z", - "iopub.status.busy": "2025-08-21T19:56:11.426030Z", - "iopub.status.idle": "2025-08-21T19:56:11.622313Z", - "shell.execute_reply": "2025-08-21T19:56:11.621036Z", - "shell.execute_reply.started": "2025-08-21T19:56:11.426142Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 3.44 ms, sys: 0 ns, total: 3.44 ms\n", - "Wall time: 186 ms\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.DataArray ()> Size: 8B\n",
-       "array(14472.21996775)\n",
-       "Coordinates:\n",
-       "    spatial_ref  int64 8B 0
" - ], - "text/plain": [ - " Size: 8B\n", - "array(14472.21996775)\n", - "Coordinates:\n", - " spatial_ref int64 8B 0" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%time\n", - "da.mean()" - ] - }, - { - "cell_type": "markdown", - "id": "b2eceda5-7dc7-482c-a95a-7f24f7698933", - "metadata": {}, - "source": [ - "### Zarr\n", - "\n", - "Read from an asset that references data stored in zarr" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "5176680b-3d58-4c72-bf58-d80051f6de75", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:56:11.624714Z", - "iopub.status.busy": "2025-08-21T19:56:11.624335Z", - "iopub.status.idle": "2025-08-21T19:56:22.617465Z", - "shell.execute_reply": "2025-08-21T19:56:22.616757Z", - "shell.execute_reply.started": "2025-08-21T19:56:11.624682Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.Dataset> Size: 69GB\n",
-       "Dimensions:                  (time: 14965, y: 584, x: 284, nv: 2)\n",
-       "Coordinates:\n",
-       "    lat                      (y, x) float32 663kB dask.array<chunksize=(584, 284), meta=np.ndarray>\n",
-       "    lon                      (y, x) float32 663kB dask.array<chunksize=(584, 284), meta=np.ndarray>\n",
-       "  * time                     (time) datetime64[ns] 120kB 1980-01-01T12:00:00 ...\n",
-       "  * x                        (x) float32 1kB -5.802e+06 ... -5.519e+06\n",
-       "  * y                        (y) float32 2kB -3.9e+04 -4e+04 ... -6.22e+05\n",
-       "Dimensions without coordinates: nv\n",
-       "Data variables:\n",
-       "    dayl                     (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    lambert_conformal_conic  int16 2B ...\n",
-       "    prcp                     (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    srad                     (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    swe                      (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    time_bnds                (time, nv) datetime64[ns] 239kB dask.array<chunksize=(365, 2), meta=np.ndarray>\n",
-       "    tmax                     (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    tmin                     (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    vp                       (time, y, x) float32 10GB dask.array<chunksize=(365, 584, 284), meta=np.ndarray>\n",
-       "    yearday                  (time) int16 30kB dask.array<chunksize=(365,), meta=np.ndarray>\n",
-       "Attributes:\n",
-       "    Conventions:       CF-1.6\n",
-       "    Version_data:      Daymet Data Version 4.0\n",
-       "    Version_software:  Daymet Software Version 4.0\n",
-       "    citation:          Please see http://daymet.ornl.gov/ for current Daymet ...\n",
-       "    references:        Please see http://daymet.ornl.gov/ for current informa...\n",
-       "    source:            Daymet Software Version 4.0\n",
-       "    start_year:        1980
" - ], - "text/plain": [ - " Size: 69GB\n", - "Dimensions: (time: 14965, y: 584, x: 284, nv: 2)\n", - "Coordinates:\n", - " lat (y, x) float32 663kB dask.array\n", - " lon (y, x) float32 663kB dask.array\n", - " * time (time) datetime64[ns] 120kB 1980-01-01T12:00:00 ...\n", - " * x (x) float32 1kB -5.802e+06 ... -5.519e+06\n", - " * y (y) float32 2kB -3.9e+04 -4e+04 ... -6.22e+05\n", - "Dimensions without coordinates: nv\n", - "Data variables:\n", - " dayl (time, y, x) float32 10GB dask.array\n", - " lambert_conformal_conic int16 2B ...\n", - " prcp (time, y, x) float32 10GB dask.array\n", - " srad (time, y, x) float32 10GB dask.array\n", - " swe (time, y, x) float32 10GB dask.array\n", - " time_bnds (time, nv) datetime64[ns] 239kB dask.array\n", - " tmax (time, y, x) float32 10GB dask.array\n", - " tmin (time, y, x) float32 10GB dask.array\n", - " vp (time, y, x) float32 10GB dask.array\n", - " yearday (time) int16 30kB dask.array\n", - "Attributes:\n", - " Conventions: CF-1.6\n", - " Version_data: Daymet Data Version 4.0\n", - " Version_software: Daymet Software Version 4.0\n", - " citation: Please see http://daymet.ornl.gov/ for current Daymet ...\n", - " references: Please see http://daymet.ornl.gov/ for current informa...\n", - " source: Daymet Software Version 4.0\n", - " start_year: 1980" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "daymet_collection = pystac.Collection.from_file(\n", - " \"https://planetarycomputer.microsoft.com/api/stac/v1/collections/daymet-daily-hi\"\n", - ")\n", - "daymet_asset = daymet_collection.assets[\"zarr-abfs\"]\n", - "daymet_asset_signed = planetary_computer.sign(daymet_asset)\n", - "\n", - "ds = xr.open_dataset(daymet_asset_signed, chunks={})\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "id": "fd4e0c53-90b0-4276-9caf-9014aa0a31f9", - "metadata": {}, - "source": [ - "### Reference file\n", - "\n", - "If the collection has a reference file we can use that.\n", - "\n", - "
\n", - " \n", - "This will not work for kerchunk>=0.2.8 and xpystac<=0.2.0. Track the xpystac ticket here: [xpystac #48](https://github.com/stac-utils/xpystac/issues/48)\n", - " \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "e69db448-c493-4972-ab1d-a8fb3f8ab71f", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-21T19:56:22.618362Z", - "iopub.status.busy": "2025-08-21T19:56:22.618022Z", - "iopub.status.idle": "2025-08-21T19:56:33.446768Z", - "shell.execute_reply": "2025-08-21T19:56:33.446285Z", - "shell.execute_reply.started": "2025-08-21T19:56:22.618347Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.Dataset> Size: 738GB\n",
-       "Dimensions:  (time: 23741, lat: 600, lon: 1440)\n",
-       "Coordinates:\n",
-       "  * lat      (lat) float64 5kB -59.88 -59.62 -59.38 -59.12 ... 89.38 89.62 89.88\n",
-       "  * lon      (lon) float64 12kB 0.125 0.375 0.625 0.875 ... 359.4 359.6 359.9\n",
-       "  * time     (time) datetime64[us] 190kB 1950-01-01T12:00:00 ... 2014-12-31T1...\n",
-       "Data variables:\n",
-       "    hurs     (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    huss     (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    pr       (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    rlds     (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    rsds     (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    sfcWind  (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    tas      (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    tasmax   (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "    tasmin   (time, lat, lon) float32 82GB dask.array<chunksize=(1, 600, 1440), meta=np.ndarray>\n",
-       "Attributes: (12/22)\n",
-       "    Conventions:           CF-1.7\n",
-       "    activity:              NEX-GDDP-CMIP6\n",
-       "    cmip6_institution_id:  CSIRO-ARCCSS\n",
-       "    cmip6_license:         CC-BY-SA 4.0\n",
-       "    cmip6_source_id:       ACCESS-CM2\n",
-       "    contact:               Dr. Rama Nemani: rama.nemani@nasa.gov, Dr. Bridget...\n",
-       "    ...                    ...\n",
-       "    scenario:              historical\n",
-       "    source:                BCSD\n",
-       "    title:                 ACCESS-CM2, r1i1p1f1, historical, global downscale...\n",
-       "    tracking_id:           16d27564-470f-41ea-8077-f4cc3efa5bfe\n",
-       "    variant_label:         r1i1p1f1\n",
-       "    version:               1.0
" - ], - "text/plain": [ - " Size: 738GB\n", - "Dimensions: (time: 23741, lat: 600, lon: 1440)\n", - "Coordinates:\n", - " * lat (lat) float64 5kB -59.88 -59.62 -59.38 -59.12 ... 89.38 89.62 89.88\n", - " * lon (lon) float64 12kB 0.125 0.375 0.625 0.875 ... 359.4 359.6 359.9\n", - " * time (time) datetime64[us] 190kB 1950-01-01T12:00:00 ... 2014-12-31T1...\n", - "Data variables:\n", - " hurs (time, lat, lon) float32 82GB dask.array\n", - " huss (time, lat, lon) float32 82GB dask.array\n", - " pr (time, lat, lon) float32 82GB dask.array\n", - " rlds (time, lat, lon) float32 82GB dask.array\n", - " rsds (time, lat, lon) float32 82GB dask.array\n", - " sfcWind (time, lat, lon) float32 82GB dask.array\n", - " tas (time, lat, lon) float32 82GB dask.array\n", - " tasmax (time, lat, lon) float32 82GB dask.array\n", - " tasmin (time, lat, lon) float32 82GB dask.array\n", - "Attributes: (12/22)\n", - " Conventions: CF-1.7\n", - " activity: NEX-GDDP-CMIP6\n", - " cmip6_institution_id: CSIRO-ARCCSS\n", - " cmip6_license: CC-BY-SA 4.0\n", - " cmip6_source_id: ACCESS-CM2\n", - " contact: Dr. Rama Nemani: rama.nemani@nasa.gov, Dr. Bridget...\n", - " ... ...\n", - " scenario: historical\n", - " source: BCSD\n", - " title: ACCESS-CM2, r1i1p1f1, historical, global downscale...\n", - " tracking_id: 16d27564-470f-41ea-8077-f4cc3efa5bfe\n", - " variant_label: r1i1p1f1\n", - " version: 1.0" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cmip6_collection = pystac.Collection.from_file(\n", - " \"https://planetarycomputer.microsoft.com/api/stac/v1/collections/nasa-nex-gddp-cmip6\"\n", - ")\n", - "cmip6_asset = cmip6_collection.assets[\"ACCESS-CM2.historical\"]\n", - "cmip6_asset_signed = planetary_computer.sign(cmip6_asset)\n", - "\n", - "ds = xr.open_dataset(cmip6_asset_signed, chunks={}, patch_url=planetary_computer.sign)\n", - "ds" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/tutorials/pystac-introduction.ipynb b/docs/tutorials/pystac-introduction.ipynb deleted file mode 100644 index f6f656725..000000000 --- a/docs/tutorials/pystac-introduction.ipynb +++ /dev/null @@ -1,6660 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# PySTAC Introduction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial includes a basic introduction on reading, writing, and creating STAC objects using Pystac.\n", - "\n", - "It is adapted from the tutorials within the [sat-stac repo](https://github.com/sat-utils/sat-stac/blob/master/tutorial-1.ipynb).\n", - "\n", - "It uses an example stac stored in the `../example-catalog` directory along-side this notebook. The example stac has the following format:\n", - "\n", - "```\n", - "../example-catalog\n", - "├── catalog.json\n", - "└── landsat-8-l1\n", - " ├── 2018-05\n", - " │ └── LC80150322018141LGN00.json\n", - " ├── 2018-06\n", - " │ ├── LC80140332018166LGN00.json\n", - " │ └── LC80300332018166LGN00.json\n", - " ├── 2018-07\n", - " │ └── LC80150332018189LGN00.json\n", - " └── collection.json\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:53.916701Z", - "iopub.status.busy": "2025-09-11T17:34:53.916377Z", - "iopub.status.idle": "2025-09-11T17:34:55.929815Z", - "shell.execute_reply": "2025-09-11T17:34:55.926407Z", - "shell.execute_reply.started": "2025-09-11T17:34:53.916674Z" - } - }, - "outputs": [], - "source": [ - "import pystac" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Working with existing catalogs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Open a root catalog from it's json file" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:55.931977Z", - "iopub.status.busy": "2025-09-11T17:34:55.931669Z", - "iopub.status.idle": "2025-09-11T17:34:55.939757Z", - "shell.execute_reply": "2025-09-11T17:34:55.939055Z", - "shell.execute_reply.started": "2025-09-11T17:34:55.931942Z" - } - }, - "outputs": [], - "source": [ - "cat = pystac.Catalog.from_file(\"../example-catalog/catalog.json\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see all elements of the STAC using the `describe` method" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:55.940651Z", - "iopub.status.busy": "2025-09-11T17:34:55.940428Z", - "iopub.status.idle": "2025-09-11T17:34:55.963983Z", - "shell.execute_reply": "2025-09-11T17:34:55.960476Z", - "shell.execute_reply.started": "2025-09-11T17:34:55.940630Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n" - ] - } - ], - "source": [ - "cat.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each STAC object has links that you can use to traverse the STAC tree" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:55.969764Z", - "iopub.status.busy": "2025-09-11T17:34:55.967863Z", - "iopub.status.idle": "2025-09-11T17:34:55.976737Z", - "shell.execute_reply": "2025-09-11T17:34:55.976171Z", - "shell.execute_reply.started": "2025-09-11T17:34:55.969721Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[,\n", - " >,\n", - " >]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cat.links" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Pystac has several methods that allow you to access links:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:55.978253Z", - "iopub.status.busy": "2025-09-11T17:34:55.978017Z", - "iopub.status.idle": "2025-09-11T17:34:55.987054Z", - "shell.execute_reply": "2025-09-11T17:34:55.986426Z", - "shell.execute_reply.started": "2025-09-11T17:34:55.978232Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Get all child links\n", - "cat.get_child_links()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or the children directly:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:55.987841Z", - "iopub.status.busy": "2025-09-11T17:34:55.987620Z", - "iopub.status.idle": "2025-09-11T17:34:55.992811Z", - "shell.execute_reply": "2025-09-11T17:34:55.992213Z", - "shell.execute_reply.started": "2025-09-11T17:34:55.987821Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list(cat.get_children())" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:55.993642Z", - "iopub.status.busy": "2025-09-11T17:34:55.993427Z", - "iopub.status.idle": "2025-09-11T17:34:56.049234Z", - "shell.execute_reply": "2025-09-11T17:34:56.047339Z", - "shell.execute_reply.started": "2025-09-11T17:34:55.993621Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Collection id=landsat-8-l1>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# or a single child by id\n", - "cat.get_child(\"landsat-8-l1\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.050223Z", - "iopub.status.busy": "2025-09-11T17:34:56.049995Z", - "iopub.status.idle": "2025-09-11T17:34:56.054626Z", - "shell.execute_reply": "2025-09-11T17:34:56.054045Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.050201Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=self target=/home/jsignell/pystac/docs/example-catalog/catalog.json>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Get a single link by 'rel'\n", - "cat.get_single_link(\"self\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.055429Z", - "iopub.status.busy": "2025-09-11T17:34:56.055221Z", - "iopub.status.idle": "2025-09-11T17:34:56.063587Z", - "shell.execute_reply": "2025-09-11T17:34:56.061222Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.055409Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Get item links directly within this catalog (there are none for this catalog)\n", - "cat.get_item_links()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or the items directly:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.064607Z", - "iopub.status.busy": "2025-09-11T17:34:56.064322Z", - "iopub.status.idle": "2025-09-11T17:34:56.070842Z", - "shell.execute_reply": "2025-09-11T17:34:56.069267Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.064578Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# get item objects\n", - "list(cat.get_items())" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.071984Z", - "iopub.status.busy": "2025-09-11T17:34:56.071747Z", - "iopub.status.idle": "2025-09-11T17:34:56.077495Z", - "shell.execute_reply": "2025-09-11T17:34:56.076770Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.071962Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# get all items anywhere below this catalog on the STAC tree\n", - "list(cat.get_items(recursive=True))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can access the stac item from a link using the `target` property" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.078735Z", - "iopub.status.busy": "2025-09-11T17:34:56.078490Z", - "iopub.status.idle": "2025-09-11T17:34:56.086510Z", - "shell.execute_reply": "2025-09-11T17:34:56.084230Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.078712Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - ">\n" - ] - } - ], - "source": [ - "link = cat.get_single_link(\"child\")\n", - "print(link)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.087287Z", - "iopub.status.busy": "2025-09-11T17:34:56.087076Z", - "iopub.status.idle": "2025-09-11T17:34:56.093043Z", - "shell.execute_reply": "2025-09-11T17:34:56.092303Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.087267Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "print(link.target)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can convert any stac item to a python dict using the `to_dict` method." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.096152Z", - "iopub.status.busy": "2025-09-11T17:34:56.095881Z", - "iopub.status.idle": "2025-09-11T17:34:56.103324Z", - "shell.execute_reply": "2025-09-11T17:34:56.101132Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.096127Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'type': 'Catalog',\n", - " 'id': 'landsat-stac-collection-catalog',\n", - " 'stac_version': '1.1.0',\n", - " 'description': 'STAC for Landsat data',\n", - " 'links': [{'rel': 'root',\n", - " 'href': './catalog.json',\n", - " 'type': 'application/json',\n", - " 'title': 'STAC for Landsat data'},\n", - " {'rel': 'child',\n", - " 'href': './landsat-8-l1/collection.json',\n", - " 'title': 'Landsat 8 L1'}],\n", - " 'title': 'STAC for Landsat data'}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cat.to_dict(include_self_link=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.104964Z", - "iopub.status.busy": "2025-09-11T17:34:56.104626Z", - "iopub.status.idle": "2025-09-11T17:34:56.113491Z", - "shell.execute_reply": "2025-09-11T17:34:56.111517Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.104927Z" - } - }, - "outputs": [], - "source": [ - "# get first (and only in this case) sub-catalog\n", - "subcat = next(cat.get_children())" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.114990Z", - "iopub.status.busy": "2025-09-11T17:34:56.114699Z", - "iopub.status.idle": "2025-09-11T17:34:56.129738Z", - "shell.execute_reply": "2025-09-11T17:34:56.127763Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.114961Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Root Catalog: landsat-stac-collection-catalog\n", - "Sub Catalog: landsat-8-l1\n", - "Sub Catalog parent: landsat-stac-collection-catalog\n", - "Sub Catalog children:\n" - ] - } - ], - "source": [ - "# print some IDs\n", - "print(\"Root Catalog: \", cat.id)\n", - "print(\"Sub Catalog: \", subcat.id)\n", - "print(\"Sub Catalog parent: \", subcat.get_parent().id)\n", - "\n", - "# iterate through child catalogs of the sub-catalog\n", - "print(\"Sub Catalog children:\")\n", - "for child in subcat.get_children():\n", - " print(\" \", child.id)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.130807Z", - "iopub.status.busy": "2025-09-11T17:34:56.130481Z", - "iopub.status.idle": "2025-09-11T17:34:56.138253Z", - "shell.execute_reply": "2025-09-11T17:34:56.136570Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.130771Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "**Items**\n", - "LC80140332018166LGN00\n", - "LC80150322018141LGN00\n", - "LC80150332018189LGN00\n", - "LC80300332018166LGN00\n" - ] - } - ], - "source": [ - "print(\"\\n**Items**\")\n", - "for i in cat.get_items(recursive=True):\n", - " print(i.id)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating new catalogs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can initialize a new Catalog with an id and a description. Note that by default it sets a new catalog as root." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.139204Z", - "iopub.status.busy": "2025-09-11T17:34:56.138960Z", - "iopub.status.idle": "2025-09-11T17:34:56.145229Z", - "shell.execute_reply": "2025-09-11T17:34:56.142675Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.139181Z" - }, - "scrolled": true - }, - "outputs": [], - "source": [ - "# create a Catalog object with JSON\n", - "mycat = pystac.Catalog(id=\"mycat\", description=\"My shiny new STAC catalog\")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.146216Z", - "iopub.status.busy": "2025-09-11T17:34:56.145970Z", - "iopub.status.idle": "2025-09-11T17:34:56.152561Z", - "shell.execute_reply": "2025-09-11T17:34:56.151741Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.146194Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mycat.links" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding catalogs to catalogs" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.153760Z", - "iopub.status.busy": "2025-09-11T17:34:56.153409Z", - "iopub.status.idle": "2025-09-11T17:34:56.158861Z", - "shell.execute_reply": "2025-09-11T17:34:56.158231Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.153724Z" - }, - "scrolled": true - }, - "outputs": [], - "source": [ - "# add a new catalog to a root catalog\n", - "kitten = pystac.Catalog(\n", - " id=\"mykitten\", description=\"A child catalog of my shiny new STAC catalog\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When you add a child catalog to a parent catalog, the child catalog assumes the root catalog of it's parent. 'Child' and 'parent' links are also added to the parent and child catalogs, respectively." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.159902Z", - "iopub.status.busy": "2025-09-11T17:34:56.159578Z", - "iopub.status.idle": "2025-09-11T17:34:56.171683Z", - "shell.execute_reply": "2025-09-11T17:34:56.168429Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.159872Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>]" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kitten.links" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.173473Z", - "iopub.status.busy": "2025-09-11T17:34:56.173193Z", - "iopub.status.idle": "2025-09-11T17:34:56.178246Z", - "shell.execute_reply": "2025-09-11T17:34:56.177623Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.173448Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=child target=<Catalog id=mykitten>>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mycat.add_child(kitten)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.179097Z", - "iopub.status.busy": "2025-09-11T17:34:56.178865Z", - "iopub.status.idle": "2025-09-11T17:34:56.183594Z", - "shell.execute_reply": "2025-09-11T17:34:56.182503Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.179071Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>,\n", - " >]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kitten.links" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.184382Z", - "iopub.status.busy": "2025-09-11T17:34:56.184174Z", - "iopub.status.idle": "2025-09-11T17:34:56.188808Z", - "shell.execute_reply": "2025-09-11T17:34:56.188241Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.184363Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[>,\n", - " >]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mycat.links" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.189795Z", - "iopub.status.busy": "2025-09-11T17:34:56.189531Z", - "iopub.status.idle": "2025-09-11T17:34:56.195712Z", - "shell.execute_reply": "2025-09-11T17:34:56.194797Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.189767Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n" - ] - } - ], - "source": [ - "mycat.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding collections to catalogs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the next two steps we will work with Pystac Collections and Items. We will pull them out of our example catalog and add them to the new STAC that we have created." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Collections are Catalogs but also include spatial and temporal extents as well as additional properties. " - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.197285Z", - "iopub.status.busy": "2025-09-11T17:34:56.197016Z", - "iopub.status.idle": "2025-09-11T17:34:56.205927Z", - "shell.execute_reply": "2025-09-11T17:34:56.204972Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.197263Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Collection id=landsat-8-l1>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# open the Landsat collection\n", - "collection = pystac.Collection.from_file(\n", - " \"../example-catalog/landsat-8-l1/collection.json\"\n", - ")\n", - "collection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "See the spatial and temporal extent of this collection" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.208716Z", - "iopub.status.busy": "2025-09-11T17:34:56.206890Z", - "iopub.status.idle": "2025-09-11T17:34:56.221383Z", - "shell.execute_reply": "2025-09-11T17:34:56.220708Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.208548Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'spatial': {'bbox': [[-180.0, -90.0, 180.0, 90.0]]},\n", - " 'temporal': {'interval': [['2018-05-21T15:44:59Z', '2018-07-08T15:45:34Z']]}}" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.extent.to_dict()" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.223704Z", - "iopub.status.busy": "2025-09-11T17:34:56.223445Z", - "iopub.status.idle": "2025-09-11T17:34:56.239443Z", - "shell.execute_reply": "2025-09-11T17:34:56.234219Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.223681Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.links" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.241560Z", - "iopub.status.busy": "2025-09-11T17:34:56.241256Z", - "iopub.status.idle": "2025-09-11T17:34:56.252570Z", - "shell.execute_reply": "2025-09-11T17:34:56.251608Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.241528Z" - }, - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=child target=<Collection id=landsat-8-l1>>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# add it to the child catalog created above\n", - "kitten.add_child(collection)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.253485Z", - "iopub.status.busy": "2025-09-11T17:34:56.253183Z", - "iopub.status.idle": "2025-09-11T17:34:56.266789Z", - "shell.execute_reply": "2025-09-11T17:34:56.264749Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.253441Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[,\n", - " >,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " >]" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "collection.links" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding items to collection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Items are stac objects whose parents can be either Catalogs or Collections. They also have spatio-temporal information and assets. Assets point directly to the data included in the STAC." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.268153Z", - "iopub.status.busy": "2025-09-11T17:34:56.267790Z", - "iopub.status.idle": "2025-09-11T17:34:56.293988Z", - "shell.execute_reply": "2025-09-11T17:34:56.290445Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.268120Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Item id=LC80150322018141LGN00>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# open a Landsat item\n", - "item = pystac.read_file(\n", - " \"../example-catalog/landsat-8-l1/2018-05/LC80150322018141LGN00.json\"\n", - ")\n", - "item" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.295175Z", - "iopub.status.busy": "2025-09-11T17:34:56.294664Z", - "iopub.status.idle": "2025-09-11T17:34:56.311450Z", - "shell.execute_reply": "2025-09-11T17:34:56.310721Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.294899Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ]" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.links" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.314221Z", - "iopub.status.busy": "2025-09-11T17:34:56.312434Z", - "iopub.status.idle": "2025-09-11T17:34:56.331974Z", - "shell.execute_reply": "2025-09-11T17:34:56.331263Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.314000Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'index': ,\n", - " 'thumbnail': ,\n", - " 'B1': ,\n", - " 'B2': ,\n", - " 'B3': ,\n", - " 'B4': ,\n", - " 'B5': ,\n", - " 'B6': ,\n", - " 'B7': ,\n", - " 'B8': ,\n", - " 'B9': ,\n", - " 'B10': ,\n", - " 'B11': ,\n", - " 'ANG': ,\n", - " 'MTL': ,\n", - " 'BQA': }" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item.assets" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.332797Z", - "iopub.status.busy": "2025-09-11T17:34:56.332548Z", - "iopub.status.idle": "2025-09-11T17:34:56.341914Z", - "shell.execute_reply": "2025-09-11T17:34:56.341088Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.332773Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - " <Link rel=item target=<Item id=LC80150322018141LGN00>>\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# add it to the collection created above\n", - "collection.add_item(item)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.342876Z", - "iopub.status.busy": "2025-09-11T17:34:56.342655Z", - "iopub.status.idle": "2025-09-11T17:34:56.364261Z", - "shell.execute_reply": "2025-09-11T17:34:56.361623Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.342856Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n", - " * \n" - ] - } - ], - "source": [ - "# now look at the catalog we've created\n", - "mycat.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Currently, this STAC only exists in memory. We can use `normalize_and_save` to save off the STAC with the canonical \"absolute published\" form:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.369342Z", - "iopub.status.busy": "2025-09-11T17:34:56.367815Z", - "iopub.status.idle": "2025-09-11T17:34:56.380914Z", - "shell.execute_reply": "2025-09-11T17:34:56.380260Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.369195Z" - } - }, - "outputs": [], - "source": [ - "mycat.normalize_and_save(\n", - " \"pystac-example-absolute\", catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice now that the 'parent' link of an item is a absolute HREF:" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.381821Z", - "iopub.status.busy": "2025-09-11T17:34:56.381578Z", - "iopub.status.idle": "2025-09-11T17:34:56.396723Z", - "shell.execute_reply": "2025-09-11T17:34:56.395724Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.381800Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'/home/jsignell/pystac/docs/tutorials/pystac-example-absolute/mykitten/landsat-8-l1/collection.json'" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item = next(mycat.get_items(recursive=True))\n", - "item.get_single_link(\"parent\").get_href()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also normalize and save the catalog to the other types described in the best practices documentation: \"relative published\" and \"self contained\". A self contained catalog contains all relative links, and no self links. Notice how saving a self contained catalog will produce relative links:" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.402199Z", - "iopub.status.busy": "2025-09-11T17:34:56.400504Z", - "iopub.status.idle": "2025-09-11T17:34:56.419288Z", - "shell.execute_reply": "2025-09-11T17:34:56.418402Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.401994Z" - } - }, - "outputs": [], - "source": [ - "mycat.normalize_and_save(\n", - " \"pystac-example-relative\", catalog_type=pystac.CatalogType.SELF_CONTAINED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": { - "execution": { - "iopub.execute_input": "2025-09-11T17:34:56.420192Z", - "iopub.status.busy": "2025-09-11T17:34:56.419955Z", - "iopub.status.idle": "2025-09-11T17:34:56.425475Z", - "shell.execute_reply": "2025-09-11T17:34:56.424373Z", - "shell.execute_reply.started": "2025-09-11T17:34:56.420171Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'../collection.json'" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item = next(mycat.get_items(recursive=True))\n", - "item.get_single_link(\"parent\").get_href()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorials/pystac-spacenet-tutorial.ipynb b/docs/tutorials/pystac-spacenet-tutorial.ipynb deleted file mode 100644 index b79a2773d..000000000 --- a/docs/tutorials/pystac-spacenet-tutorial.ipynb +++ /dev/null @@ -1,377 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create and manipulate SpaceNet Vegas STAC" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial shows how to create and manipulate STACs using pystac.\n", - "\n", - "- Create (in memory) a pystac catalog of [SpaceNet 2 imagery from the Las Vegas AOI](https://spacenetchallenge.github.io/AOI_Lists/AOI_2_Vegas.html) using data hosted in a public s3 bucket\n", - "- Set relative paths for all STAC object\n", - "- Normalize links from a root directory and save the STAC there" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "\n", - "sys.path.append(\"..\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You may need install the following packages that are not included in the Python 3 standard library. If you do not have any of these installed, you can do do with pip:\n", - "\n", - "[boto3](https://pypi.org/project/boto3/): `pip install boto3` \n", - "[botocore](https://pypi.org/project/botocore/): `pip install botocore` \n", - "[rasterio](https://pypi.org/project/rasterio/): `pip install rasterio` \n", - "[shapely](https://pypi.org/project/Shapely/): `pip install Shapely` \n", - "[rio-cogeo](https://github.com/cogeotiff/rio-cogeo): `pip install rio-cogeo`" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime\n", - "from os.path import basename, join\n", - "\n", - "import boto3\n", - "import rasterio\n", - "from shapely.geometry import GeometryCollection, box, mapping, shape\n", - "\n", - "import pystac" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create SpaceNet Vegas STAC" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Initialize a STAC for the SpaceNet 2 dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "spacenet = pystac.Catalog(id=\"spacenet\", description=\"SpaceNet 2 STAC\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We do not yet know the spatial extent of the Vegas AOI. We will need to determine it when we download all of the images. As a placeholder we will create a spatial extent of null values." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "sp_extent = pystac.SpatialExtent([None, None, None, None])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The capture date for SpaceNet 2 Vegas imagery is October 22, 2015. Create a python datetime object using that date" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "capture_date = datetime.strptime(\"2015-10-22\", \"%Y-%m-%d\")\n", - "tmp_extent = pystac.TemporalExtent([(capture_date, None)])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create an Extent object that will define both the spatial and temporal extents of the Vegas collection" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "extent = pystac.Extent(sp_extent, tmp_extent)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a collection that will encompass the Vegas data and add to the spacenet catalog" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - "
    \n", - " \n", - " \n", - " \n", - "
  • \n", - " rel\n", - " \"child\"\n", - "
  • \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
  • \n", - " href\n", - " None\n", - "
  • \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
  • \n", - " type\n", - " \"application/json\"\n", - "
  • \n", - " \n", - " \n", - " \n", - "
\n", - "
\n", - "
" - ], - "text/plain": [ - ">" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "vegas = pystac.Collection(\n", - " id=\"vegas\", description=\"Vegas SpaceNet 2 dataset\", extent=extent\n", - ")\n", - "spacenet.add_child(vegas)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "* \n", - " * \n" - ] - } - ], - "source": [ - "spacenet.describe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Find the locations of SpaceNet images. In order to make this example quicker, we will limit the number of scenes that we use to 10." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "client = boto3.client(\"s3\")\n", - "scenes = client.list_objects(\n", - " Bucket=\"spacenet-dataset\",\n", - " Prefix=\"spacenet/SN2_buildings/train/AOI_2_Vegas/PS-RGB/\",\n", - " MaxKeys=20,\n", - ")\n", - "scenes = [s[\"Key\"] for s in scenes[\"Contents\"] if s[\"Key\"].endswith(\".tif\")][0:10]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For each scene, create and item with a defined bounding box. Each item will include the geotiff as an asset. We will add labels in the next section." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "for scene in scenes:\n", - " uri = join(\"s3://spacenet-dataset/\", scene)\n", - " params = {}\n", - " params[\"id\"] = basename(uri).split(\".\")[0]\n", - " with rasterio.open(uri) as src:\n", - " params[\"bbox\"] = list(src.bounds)\n", - " params[\"geometry\"] = mapping(box(*params[\"bbox\"]))\n", - " params[\"datetime\"] = capture_date\n", - " params[\"properties\"] = {}\n", - " i = pystac.Item(**params)\n", - " i.add_asset(\n", - " key=\"image\",\n", - " asset=pystac.Asset(\n", - " href=uri, title=\"Geotiff\", media_type=pystac.MediaType.GEOTIFF\n", - " ),\n", - " )\n", - " vegas.add_item(i)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now reset the spatial extent of the Vegas collection using the geometry objects from from the items we just added." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "bounds = [\n", - " list(\n", - " GeometryCollection(\n", - " [shape(s.geometry) for s in spacenet.get_items(recursive=True)]\n", - " ).bounds\n", - " )\n", - "]\n", - "vegas.extent.spatial = pystac.SpatialExtent(bounds)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Currently, this STAC only exists in memory. We need to set all of the paths based on the root directory we want to save off that catalog too, and then save a \"self contained\" catalog, which will have all links be relative and contain no 'self' links. We can do this by using the `normalize` method to set the HREFs of all of our STAC objects. We'll then validate the catalog, and then save:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "spacenet.normalize_hrefs(\"spacenet-stac\")" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "10" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "spacenet.validate_all()" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "spacenet.save(catalog_type=pystac.CatalogType.SELF_CONTAINED)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/pyproject.toml b/pyproject.toml index 32dab63d8..94cba19a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ readme = "README.md" authors = [ { name = "Rob Emanuele", email = "rdemanuele@gmail.com" }, { name = "Jon Duckworth", email = "duckontheweb@gmail.com" }, + { name = "Pete Gadomski", email = "pete.gadomski@gmail.com" }, ] maintainers = [{ name = "Pete Gadomski", email = "pete.gadomski@gmail.com" }] keywords = ["pystac", "imagery", "raster", "catalog", "STAC"] @@ -15,70 +16,46 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +requires-python = ">=3.12" +dependencies = [ + "python-dateutil>=2.9.0.post0", ] -requires-python = ">=3.10" -dependencies = ["python-dateutil>=2.7.0"] dynamic = ["version"] [project.optional-dependencies] -jinja2 = ["jinja2<4.0"] -orjson = ["orjson>=3.5"] -urllib3 = ["urllib3>=2.6.0"] -validation = ["jsonschema~=4.18"] +obstore = [ + "obspec>=0.1.0", + "obstore>=0.8.2", +] +validation = [ + "jsonschema>=4.25.1", + "referencing>=0.37.0", +] +jinja2 = [ + "jinja2>=3.1.6", +] [dependency-groups] +bench = [ + "asv>=0.6.5", +] dev = [ - "asv>=0.6.4", - "codespell<2.5", - "coverage>=7.6.2", - "doc8>=1.1.2", - "filelock>=3.20.1", # No direct dependency, avoid CVE-2025-68146. + "basedpyright>=1.36.1", "html5lib>=1.1", - "jinja2>=3.1.4", - "jsonschema>=4.23.0", - "mypy>=1.11.2", - "orjson>=3.10.7", - "packaging>=24.1", - "pre-commit>=4.0.1", - "pytest>=8.3.3", - "pytest-cov>=5.0.0", - "pytest-mock>=3.14.0", - "pytest-recording>=0.13.2", + "jsonschema>=4.25.1", + "pytest>=9.0.2", + "pytest-mock>=3.15.1", + "pytest-recording>=0.13.4", "requests-mock>=1.12.1", - "ruff>=0.6.9", - "types-html5lib>=1.1.11.20240806", - "types-jsonschema>=4.23.0.20240813", - "types-orjson>=3.6.2", - "types-python-dateutil>=2.9.0.20241003", - "types-urllib3>=1.26.25.14", - "urllib3>=1.26.19", - "virtualenv>=20.26.6", + "ruff>=0.14.9", ] docs = [ - "adlfs>=2024.12.0", - "boto3>=1.35.39", - "ipython>=8.28.0", - "jinja2>=3.1.4", - "jupyter>=1.1.1", - "jupyterlab>=4.4.8", # No direct dependency, avoid CVE-2025-59842. - "nbsphinx>=0.9.5", - "odc-stac>=0.4.0", - "planetary-computer>=1.0.0", - "pydata-sphinx-theme>=0.15.4", - "rasterio>=1.4.1", - "rioxarray>=0.19.0", - "shapely>=2.0.6", - "sphinx>=8.1.1", - "sphinx-autobuild>=2024.10.3", - "sphinx-design>=0.6.1", - "sphinxcontrib-fulltoc>=1.2.0", - "starlette>=0.49.1", # No direct dependency, avoid CVE-2025-62727. - "xpystac>=0.2.0", - "zarr>=2.18.3", + "mkdocs-material>=9.7.0", + "mkdocstrings-python>=2.0.1", ] [project.urls] @@ -88,42 +65,25 @@ Issues = "https://github.com/stac-utils/pystac/issues" Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md" Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software" -[tool.coverage.run] -branch = true -source = ["pystac"] -omit = ["pystac/extensions/label.py"] - -[tool.coverage.report] -fail_under = 90 -exclude_lines = ["if TYPE_CHECKING:"] - -[tool.doc8] -ignore-path = ["docs/_build", "docs/tutorials"] -max-line-length = 88 - -[tool.mypy] -show_error_codes = true -strict = true - -[[tool.mypy.overrides]] -module = ["jinja2"] -ignore_missing_imports = true +[tool.basedpyright] +reportExplicitAny = false +reportAny = false +reportImportCycles = false +reportImplicitStringConcatenation = false [tool.ruff] -line-length = 88 -lint.select = ["E", "F", "I"] +lint.select = ["E", "F", "I", "UP"] +extend-exclude = ["tests/v1", "src/pystac/validation"] [tool.pytest.ini_options] -filterwarnings = ["error"] addopts = "--block-network --record-mode=none" -[tool.setuptools.packages.find] -include = ["pystac*"] -exclude = ["tests*", "benchmarks*"] - -[tool.setuptools.dynamic] -version = { attr = "pystac.version.__version__" } +[tool.uv] +default-groups = ["dev", "docs"] [build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "src/pystac/version.py" diff --git a/pystac/asset.py b/pystac/asset.py deleted file mode 100644 index cfffc1337..000000000 --- a/pystac/asset.py +++ /dev/null @@ -1,395 +0,0 @@ -from __future__ import annotations - -import shutil -from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, Protocol, TypeVar - -from pystac import MediaType, STACError, common_metadata, utils -from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href - -if TYPE_CHECKING: - from pystac.common_metadata import CommonMetadata - from pystac.extensions.ext import AssetExt - -#: Generalized version of :class:`Asset` -A = TypeVar("A", bound="Asset") - - -class Asset: - """An object that contains a link to data associated with an Item or Collection that - can be downloaded or streamed. - - Args: - href : Link to the asset object. Relative and absolute links are both - allowed. - title : Optional displayed title for clients and users. - description : A description of the Asset providing additional details, - such as how it was processed or created. CommonMark 0.29 syntax MAY be used - for rich text representation. - media_type : Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - roles : Optional, Semantic roles (i.e. thumbnail, overview, - data, metadata) of the asset. - extra_fields : Optional, additional fields for this asset. This is used - by extensions as a way to serialize and deserialize properties on asset - object JSON. - """ - - href: str - """Link to the asset object. Relative and absolute links are both allowed.""" - - title: str | None - """Optional displayed title for clients and users.""" - - description: str | None - """A description of the Asset providing additional details, such as how it was - processed or created. CommonMark 0.29 syntax MAY be used for rich text - representation.""" - - media_type: str | None - """Optional description of the media type. Registered Media Types are preferred. - See :class:`~pystac.MediaType` for common media types.""" - - roles: list[str] | None - """Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) of the - asset.""" - - owner: Assets | None - """The :class:`~pystac.Item` or :class:`~pystac.Collection` that this asset belongs - to, or ``None`` if it has no owner.""" - - extra_fields: dict[str, Any] - """Optional, additional fields for this asset. This is used by extensions as a - way to serialize and deserialize properties on asset object JSON.""" - - def __init__( - self, - href: str, - title: str | None = None, - description: str | None = None, - media_type: str | None = None, - roles: list[str] | None = None, - extra_fields: dict[str, Any] | None = None, - ) -> None: - self.href = utils.make_posix_style(href) - self.title = title - self.description = description - self.media_type = media_type - self.roles = roles - self.extra_fields = extra_fields or {} - - # The Item which owns this Asset. - self.owner = None - - def set_owner(self, obj: Assets) -> None: - """Sets the owning item of this Asset. - - The owning item will be used to resolve relative HREFs of this asset. - - Args: - obj: The Collection or Item that owns this asset. - """ - self.owner = obj - - def get_absolute_href(self) -> str | None: - """Gets the absolute href for this asset, if possible. - - If this Asset has no associated Item, and the asset HREF is a relative path, - this method will return ``None``. If the Item that owns the Asset has no - self HREF, this will also return ``None``. - - Returns: - str: The absolute HREF of this asset, or None if an absolute HREF could not - be determined. - """ - item_self = self.owner.get_self_href() if self.owner is not None else None - if utils.is_absolute_href(self.href, item_self): - return self.href - else: - if item_self is not None: - return utils.make_absolute_href(self.href, item_self) - return None - - def to_dict(self) -> dict[str, Any]: - """Returns this Asset as a dictionary. - - Returns: - dict: A serialization of the Asset. - """ - - d: dict[str, Any] = {"href": self.href} - - if self.media_type is not None: - d["type"] = self.media_type - - if self.title is not None: - d["title"] = self.title - - if self.description is not None: - d["description"] = self.description - - if self.extra_fields is not None and len(self.extra_fields) > 0: - for k, v in self.extra_fields.items(): - d[k] = v - - if self.roles is not None: - d["roles"] = self.roles - - return d - - def clone(self) -> Asset: - """Clones this asset. Makes a ``deepcopy`` of the - :attr:`~pystac.Asset.extra_fields`. - - Returns: - Asset: The clone of this asset. - """ - cls = self.__class__ - return cls( - href=self.href, - title=self.title, - description=self.description, - media_type=self.media_type, - roles=self.roles, - extra_fields=deepcopy(self.extra_fields), - ) - - def has_role(self, role: str) -> bool: - """Check if a role exists in the Asset role list. - - Args: - role: Role to check for existence. - - Returns: - bool: True if role exists, else False. - """ - if self.roles is None: - return False - else: - return role in self.roles - - @property - def common_metadata(self) -> CommonMetadata: - """Access the asset's common metadata fields as a - :class:`~pystac.CommonMetadata` object.""" - return common_metadata.CommonMetadata(self) - - def __repr__(self) -> str: - return f"" - - def _repr_html_(self) -> str: - from html import escape - - from pystac.html.jinja_env import get_jinja_env - - jinja_env = get_jinja_env() - if jinja_env: - template = jinja_env.get_template("JSON.jinja2") - return str(template.render(dict=self.to_dict(), plain=escape(repr(self)))) - else: - return escape(repr(self)) - - @classmethod - def from_dict(cls: type[A], d: dict[str, Any]) -> A: - """Constructs an Asset from a dict. - - Returns: - Asset: The Asset deserialized from the JSON dict. - """ - d = copy(d) - href = d.pop("href") - media_type = d.pop("type", None) - title = d.pop("title", None) - description = d.pop("description", None) - roles = d.pop("roles", None) - properties = None - if any(d): - properties = d - - return cls( - href=href, - media_type=media_type, - title=title, - description=description, - roles=roles, - extra_fields=properties, - ) - - def move(self, href: str) -> Asset: - """Moves this asset's file to a new location on the local filesystem, - setting the asset href accordingly. - - Modifies the asset in place, and returns the same asset. - - Args: - href: The new asset location. Must be a local path. If relative - it must be relative to the owner object. - - Returns: - Asset: The asset with the updated href. - """ - src = _absolute_href(self.href, self.owner, "move") - dst = _absolute_href(href, self.owner, "move") - shutil.move(src, dst) - self.href = href - return self - - def copy(self, href: str) -> Asset: - """Copies this asset's file to a new location on the local filesystem, - setting the asset href accordingly. - - Modifies the asset in place, and returns the same asset. - - Args: - href: The new asset location. Must be a local path. If relative - it must be relative to the owner object. - - Returns: - Asset: The asset with the updated href. - """ - src = _absolute_href(self.href, self.owner, "copy") - dst = _absolute_href(href, self.owner, "copy") - shutil.copy2(src, dst) - self.href = href - return self - - def delete(self) -> None: - """Delete this asset's file. Does not delete the asset from the item - that owns it. See :meth:`~pystac.Item.delete_asset` for that. - - Does not modify the asset. - """ - import os - - href = _absolute_href(self.href, self.owner, "delete") - os.remove(href) - - @property - def ext(self) -> AssetExt: - """Accessor for extension classes on this asset - - Example:: - - asset.ext.proj.code = "EPSG:4326" - """ - from pystac.extensions.ext import AssetExt - - return AssetExt(stac_object=self) - - -class Assets(Protocol): - """Protocol, with functionality, for STAC objects that have assets.""" - - assets: dict[str, Asset] - """The asset dictionary.""" - - def get_assets( - self, - media_type: str | MediaType | None = None, - role: str | None = None, - ) -> dict[str, Asset]: - """Get this object's assets. - - Args: - media_type: If set, filter the assets such that only those with a - matching ``media_type`` are returned. - role: If set, filter the assets such that only those with a matching - ``role`` are returned. - - Returns: - Dict[str, Asset]: A dictionary of assets that match ``media_type`` - and/or ``role`` if set or else all of this object's assets. - """ - return { - k: deepcopy(v) - for k, v in self.assets.items() - if (media_type is None or v.media_type == media_type) - and (role is None or v.has_role(role)) - } - - def add_asset(self, key: str, asset: Asset) -> None: - """Adds an Asset to this object. - - Args: - key : The unique key of this asset. - asset : The Asset to add. - """ - asset.set_owner(self) - self.assets[key] = asset - - def delete_asset(self, key: str) -> None: - """Deletes the asset at the given key, and removes the asset's data - file from the local filesystem. - - It is an error to attempt to delete an asset's file if it is on a - remote filesystem. - - To delete the asset without removing the file, use `del item.assets["key"]`. - - Args: - key: The unique key of this asset. - """ - asset = self.assets[key] - asset.set_owner(self) - asset.delete() - - del self.assets[key] - - def make_asset_hrefs_relative(self) -> Assets: - """Modify each asset's HREF to be relative to this object's self HREF. - - Returns: - Item: self - """ - self_href = self.get_self_href() - for asset in self.assets.values(): - if is_absolute_href(asset.href, self_href): - if self_href is None: - raise STACError( - "Cannot make asset HREFs relative if no self_href is set." - ) - asset.href = make_relative_href(asset.href, self_href) - return self - - def make_asset_hrefs_absolute(self) -> Assets: - """Modify each asset's HREF to be absolute. - - Any asset HREFs that are relative will be modified to absolute based on this - item's self HREF. - - Returns: - Assets: self - """ - self_href = self.get_self_href() - for asset in self.assets.values(): - if not is_absolute_href(asset.href, self_href): - if self_href is None: - raise STACError( - "Cannot make relative asset HREFs absolute " - "if no self_href is set." - ) - asset.href = make_absolute_href(asset.href, self_href) - return self - - def get_self_href(self) -> str | None: - """Abstract definition of STACObject.get_self_href. - - Needed to make the `make_asset_hrefs_{absolute|relative}` methods pass - type checking. Refactoring out all the link behavior in STACObject to - its own protocol would be too heavy, so we just use this stub instead. - """ - ... - - -def _absolute_href(href: str, owner: Assets | None, action: str = "access") -> str: - item_self = owner.get_self_href() if owner else None - if utils.is_absolute_href(href, item_self): - return href - else: - if item_self is None: - raise ValueError( - f"Cannot {action} file if asset href ('{href}') is relative " - "and owner item is not set. Hint: try using " - ":func:`~pystac.Item.make_asset_hrefs_absolute`" - ) - return utils.make_absolute_href(href, item_self) diff --git a/pystac/catalog.py b/pystac/catalog.py deleted file mode 100644 index 3a81ad2f2..000000000 --- a/pystac/catalog.py +++ /dev/null @@ -1,1307 +0,0 @@ -from __future__ import annotations - -import os -from collections.abc import Callable, Iterable, Iterator -from copy import deepcopy -from typing import ( - TYPE_CHECKING, - Any, - TypeVar, - cast, -) - -import pystac -import pystac.media_type -from pystac.cache import ResolvedObjectCache -from pystac.errors import STACError, STACTypeError -from pystac.layout import ( - APILayoutStrategy, - BestPracticesLayoutStrategy, - HrefLayoutStrategy, - LayoutTemplate, -) -from pystac.link import Link -from pystac.serialization import ( - identify_stac_object, - identify_stac_object_type, - migrate_to_latest, -) -from pystac.stac_object import STACObject, STACObjectType -from pystac.utils import ( - HREF, - StringEnum, - _is_url, - is_absolute_href, - make_absolute_href, - make_relative_href, -) - -if TYPE_CHECKING: - from pystac.asset import Asset - from pystac.collection import Collection - from pystac.extensions.ext import CatalogExt - from pystac.item import Item - -#: Generalized version of :class:`Catalog` -C = TypeVar("C", bound="Catalog") - - -class CatalogType(StringEnum): - SELF_CONTAINED = "SELF_CONTAINED" - """A 'self-contained catalog' is one that is designed for portability. - Users may want to download an online catalog from and be able to use it on their - local computer, so all links need to be relative. - - See: - :stac-spec:`The best practices documentation on self-contained catalogs - ` - """ - - ABSOLUTE_PUBLISHED = "ABSOLUTE_PUBLISHED" - """ - Absolute Published Catalog is a catalog that uses absolute links for everything, - both in the links objects and in the asset hrefs. - - See: - :stac-spec:`The best practices documentation on published catalogs - ` - """ - - RELATIVE_PUBLISHED = "RELATIVE_PUBLISHED" - """ - Relative Published Catalog is a catalog that uses relative links for everything, but - includes an absolute self link at the root catalog, to identify its online location. - - See: - :stac-spec:`The best practices documentation on published catalogs - ` - """ - - @classmethod - def determine_type(cls, stac_json: dict[str, Any]) -> CatalogType | None: - """Determines the catalog type based on a STAC JSON dict. - - Only applies to Catalogs or Collections - - Args: - stac_json : The STAC JSON dict to determine the catalog type - - Returns: - Optional[CatalogType]: The catalog type of the catalog or collection. - Will return None if it cannot be determined. - """ - self_link = None - relative = False - for link in stac_json["links"]: - if link["rel"] == pystac.RelType.SELF: - self_link = link - else: - relative |= not is_absolute_href(link["href"]) - - if self_link: - if relative: - return cls.RELATIVE_PUBLISHED - else: - return cls.ABSOLUTE_PUBLISHED - else: - if relative: - return cls.SELF_CONTAINED - else: - return None - - -class Catalog(STACObject): - """A PySTAC Catalog represents a STAC catalog in memory. - - A Catalog is a :class:`~pystac.STACObject` that may contain children, - which are instances of :class:`~pystac.Catalog` or :class:`~pystac.Collection`, - as well as :class:`~pystac.Item` s. - - Args: - id : Identifier for the catalog. Must be unique within the STAC. - description : Detailed multi-line description to fully explain the catalog. - `CommonMark 0.29 syntax `_ MAY be used for rich - text representation. - title : Optional short descriptive one-line title for the catalog. - stac_extensions : Optional list of extensions the Catalog implements. - href : Optional HREF for this catalog, which be set as the - catalog's self link's HREF. - catalog_type : Optional catalog type for this catalog. Must - be one of the values in :class:`~pystac.CatalogType`. - strategy : The layout strategy to use for setting the - HREFs of the catalog child objects and items. - If not provided, it will default to the strategy of the root and fallback to - :class:`~pystac.layout.BestPracticesLayoutStrategy`. - """ - - catalog_type: CatalogType - """The catalog type. Defaults to :attr:`CatalogType.ABSOLUTE_PUBLISHED`.""" - - description: str - """Detailed multi-line description to fully explain the catalog.""" - - extra_fields: dict[str, Any] - """Extra fields that are part of the top-level JSON properties of the Catalog.""" - - id: str - """Identifier for the catalog.""" - - links: list[Link] - """A list of :class:`~pystac.Link` objects representing all links associated with - this Catalog.""" - - title: str | None - """Optional short descriptive one-line title for the catalog.""" - - stac_extensions: list[str] - """List of extensions the Catalog implements.""" - - _resolved_objects: ResolvedObjectCache - - STAC_OBJECT_TYPE = pystac.STACObjectType.CATALOG - - _stac_io: pystac.StacIO | None = None - """Optional instance of StacIO that will be used by default - for any IO operations on objects contained by this catalog. - Set while reading in a catalog. This is set when a catalog - is read by a StacIO instance.""" - - DEFAULT_FILE_NAME = "catalog.json" - """Default file name that will be given to this STAC object in - a canonical format. - """ - - _fallback_strategy: HrefLayoutStrategy = BestPracticesLayoutStrategy() - """Fallback layout strategy""" - - def __init__( - self, - id: str, - description: str, - title: str | None = None, - stac_extensions: list[str] | None = None, - extra_fields: dict[str, Any] | None = None, - href: str | None = None, - catalog_type: CatalogType = CatalogType.ABSOLUTE_PUBLISHED, - strategy: HrefLayoutStrategy | None = None, - ): - super().__init__(stac_extensions or []) - - self.id = id - self.description = description - self.title = title - if extra_fields is None: - self.extra_fields = {} - else: - self.extra_fields = extra_fields - - self._resolved_objects = ResolvedObjectCache() - - self.add_link(Link.root(self)) - - if href is not None: - self.set_self_href(href) - - self.catalog_type: CatalogType = catalog_type - - self.strategy: HrefLayoutStrategy | None = strategy - - self._resolved_objects.cache(self) - - def __repr__(self) -> str: - return f"" - - def set_root(self, root: Catalog | None) -> None: - STACObject.set_root(self, root) - if root is not None: - root._resolved_objects = ResolvedObjectCache.merge( - root._resolved_objects, self._resolved_objects - ) - - # Walk through resolved object links and update the root - for link in self.links: - if link.rel == pystac.RelType.CHILD or link.rel == pystac.RelType.ITEM: - target = link.target - if isinstance(target, STACObject): - target.set_root(root) - - def is_relative(self) -> bool: - return self.catalog_type in [ - CatalogType.RELATIVE_PUBLISHED, - CatalogType.SELF_CONTAINED, - ] - - def _get_strategy(self, strategy: HrefLayoutStrategy | None) -> HrefLayoutStrategy: - if strategy is not None: - return strategy - elif self.strategy is not None: - return self.strategy - elif root := self.get_root(): - if root.strategy is not None: - return root.strategy - else: - return root._fallback_strategy - else: - return self._fallback_strategy - - def add_child( - self, - child: Catalog | Collection, - title: str | None = None, - strategy: HrefLayoutStrategy | None = None, - set_parent: bool = True, - ) -> Link: - """Adds a link to a child :class:`~pystac.Catalog` or - :class:`~pystac.Collection`. - - This method will set the child's parent to this object and potentially - override its self_link (unless ``set_parent`` is False). - - It will always set its root to this Catalog's root. - - Args: - child : The child to add. - title : Optional title to give to the :class:`~pystac.Link` - strategy : The layout strategy to use for setting the - self href of the child. If not provided, defaults to - the layout strategy of the parent or root and falls back to - :class:`~pystac.layout.BestPracticesLayoutStrategy`. - set_parent : Whether to set the parent on the child as well. - Defaults to True. - - Returns: - Link: The link created for the child - """ - - # Prevent typo confusion - if isinstance(child, pystac.Item): - raise pystac.STACError("Cannot add item as child. Use add_item instead.") - - strategy = self._get_strategy(strategy) - - child.set_root(self.get_root()) - if set_parent: - child.set_parent(self) - else: - child._allow_parent_to_override_href = False - - # set self link - self_href = self.get_self_href() - if self_href and set_parent: - child_href = strategy.get_href(child, self_href) - child.set_self_href(child_href) - - child_link = Link.child(child, title=title) - self.add_link(child_link) - return child_link - - def add_children( - self, - children: Iterable[Catalog | Collection], - strategy: HrefLayoutStrategy | None = None, - ) -> list[Link]: - """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection` - objects. This method will set each child's parent to this object, and their - root to this Catalog's root. - - Args: - children : The children to add. - strategy : The layout strategy to use for setting the - self href of the children. If not provided, defaults to - the layout strategy of the parent or root and falls back to - :class:`~pystac.layout.BestPracticesLayoutStrategy`. - - Returns: - List[Link]: An array of links created for the children - """ - return [self.add_child(child, strategy=strategy) for child in children] - - def add_item( - self, - item: Item, - title: str | None = None, - strategy: HrefLayoutStrategy | None = None, - set_parent: bool = True, - ) -> Link: - """Adds a link to an :class:`~pystac.Item`. - - This method will set the item's parent to this object and potentially - override its self_link (unless ``set_parent`` is False) - - It will always set its root to this Catalog's root. - - Args: - item : The item to add. - title : Optional title to give to the :class:`~pystac.Link` - strategy : The layout strategy to use for setting the - self href of the item. If not provided, defaults to - the layout strategy of the parent or root and falls back to - :class:`~pystac.layout.BestPracticesLayoutStrategy`. - set_parent : Whether to set the parent on the item as well. - Defaults to True. - - Returns: - Link: The link created for the item - """ - - # Prevent typo confusion - if isinstance(item, pystac.Catalog): - raise pystac.STACError("Cannot add catalog as item. Use add_child instead.") - - strategy = self._get_strategy(strategy) - - item.set_root(self.get_root()) - if set_parent: - item.set_parent(self) - else: - item._allow_parent_to_override_href = False - - # set self link - self_href = self.get_self_href() - if self_href and set_parent: - item_href = strategy.get_href(item, self_href) - item.set_self_href(item_href) - - item_link = Link.item(item, title=title) - self.add_link(item_link) - return item_link - - def add_items( - self, - items: Iterable[Item], - strategy: HrefLayoutStrategy | None = None, - ) -> list[Link]: - """Adds links to multiple :class:`Items `. - - This method will set each item's parent to this object, and their root to - this Catalog's root. - - Args: - items : The items to add. - strategy : The layout strategy to use for setting the - self href of the items. If not provided, defaults to - the layout strategy of the parent or root and falls back to - :class:`~pystac.layout.BestPracticesLayoutStrategy`. - - Returns: - List[Link]: A list of links created for the item - """ - return [self.add_item(item, strategy=strategy) for item in items] - - def get_child( - self, id: str, recursive: bool = False, sort_links_by_id: bool = True - ) -> Catalog | Collection | None: - """Gets the child of this catalog with the given ID, if it exists. - - Args: - id : The ID of the child to find. - recursive : If True, search this catalog and all children for the - item; otherwise, only search the children of this catalog. Defaults - to False. - sort_links_by_id : If True, links containing the ID will be checked - first. If links do not contain the ID then setting this to False - will improve performance. Defaults to True. - - Return: - Catalog or Collection or None: The child with the given ID, - or None if not found. - """ - if not recursive: - children: Iterable[pystac.Catalog | pystac.Collection] - if not sort_links_by_id: - children = self.get_children() - else: - - def sort_function(links: list[Link]) -> list[Link]: - return sorted( - links, - key=lambda x: (href := x.get_href()) is None or id not in href, - ) - - children = map( - lambda x: cast(pystac.Catalog | pystac.Collection, x), - self.get_stac_objects( - pystac.RelType.CHILD, modify_links=sort_function - ), - ) - return next((c for c in children if c.id == id), None) - else: - for root, _, _ in self.walk(): - child = root.get_child(id, recursive=False) - if child is not None: - return child - return None - - def get_children(self) -> Iterable[Catalog | Collection]: - """Return all children of this catalog. - - Return: - Iterable[Catalog or Collection]: Iterable of children whose parent - is this catalog. - """ - return map( - lambda x: cast(pystac.Catalog | pystac.Collection, x), - self.get_stac_objects(pystac.RelType.CHILD), - ) - - def get_collections(self) -> Iterable[Collection]: - """Return all children of this catalog that are :class:`~pystac.Collection` - instances.""" - return map( - lambda x: cast(pystac.Collection, x), - self.get_stac_objects(pystac.RelType.CHILD, pystac.Collection), - ) - - def get_all_collections(self) -> Iterable[Collection]: - """Get all collections from this catalog and all subcatalogs. Will traverse - any subcatalogs recursively.""" - yield from self.get_collections() - for child in self.get_children(): - yield from child.get_all_collections() - - def get_child_links(self) -> list[Link]: - """Return all child links of this catalog. - - Return: - List[Link]: List of links of this catalog with ``rel == 'child'`` - """ - return self.get_links( - rel=pystac.RelType.CHILD, - media_type=pystac.media_type.STAC_JSON, - ) - - def clear_children(self) -> None: - """Removes all children from this catalog. - - Return: - Catalog: Returns ``self`` - """ - child_ids = [child.id for child in self.get_children()] - for child_id in child_ids: - self.remove_child(child_id) - - def remove_child(self, child_id: str) -> None: - """Removes a child from this catalog. - - Args: - child_id : The ID of the child to remove. - """ - new_links: list[pystac.Link] = [] - root = self.get_root() - for link in self.links: - if link.rel != pystac.RelType.CHILD: - new_links.append(link) - else: - link.resolve_stac_object(root=root) - child = cast("Catalog", link.target) - if child.id != child_id: - new_links.append(link) - else: - child.set_parent(None) - child.set_root(None) - self.links = new_links - - def get_item(self, id: str, recursive: bool = False) -> Item | None: - """ - DEPRECATED. - - .. deprecated:: 1.8 - Use ``next(pystac.Catalog.get_items(id), None)`` instead. - - Returns an item with a given ID. - - Args: - id : The ID of the item to find. - recursive : If True, search this catalog and all children for the - item; otherwise, only search the items of this catalog. Defaults - to False. - - Return: - Item or None: The item with the given ID, or None if not found. - """ - import warnings - - warnings.warn( - "get_item is deprecated and will be removed in v2. " - "Use next(self.get_items(id), None) instead", - DeprecationWarning, - ) - if not recursive: - return next((i for i in self.get_items() if i.id == id), None) - else: - for root, _, _ in self.walk(): - item = root.get_item(id, recursive=False) - if item is not None: - return item - return None - - def get_items(self, *ids: str, recursive: bool = False) -> Iterator[Item]: - """Return all items or specific items of this catalog. - - Args: - *ids : The IDs of the items to include. - recursive : If True, search this catalog and all children for the - item; otherwise, only search the items of this catalog. Defaults - to False. - - Return: - Iterator[Item]: Generator of items whose parent is this catalog, and - (if recursive) all catalogs or collections connected to this catalog - through child links. - """ - from itertools import chain - - items: Iterator[Item] - if not recursive: - items = map( - lambda x: cast(pystac.Item, x), - self.get_stac_objects(pystac.RelType.ITEM), - ) - else: - items = chain( - self.get_items(recursive=False), - *(child.get_items(recursive=True) for child in self.get_children()), - ) - if ids: - yield from (i for i in items if i.id in ids) - else: - yield from items - - def clear_items(self) -> None: - """Removes all items from this catalog. - - Return: - Catalog: Returns ``self`` - """ - for link in self.get_item_links(): - if link.is_resolved(): - item = cast(pystac.Item, link.target) - item.set_parent(None) - item.set_root(None) - - self.links = [link for link in self.links if link.rel != pystac.RelType.ITEM] - - def remove_item(self, item_id: str) -> None: - """Removes an item from this catalog. - - Args: - item_id : The ID of the item to remove. - """ - new_links: list[pystac.Link] = [] - root = self.get_root() - for link in self.links: - if link.rel != pystac.RelType.ITEM: - new_links.append(link) - else: - link.resolve_stac_object(root=root) - item = cast(pystac.Item, link.target) - if item.id != item_id: - new_links.append(link) - else: - item.set_parent(None) - item.set_root(None) - self.links = new_links - - def get_all_items(self) -> Iterator[Item]: - """ - DEPRECATED. - - .. deprecated:: 1.8 - Use ``pystac.Catalog.get_items(recursive=True)`` instead. - - Get all items from this catalog and all subcatalogs. Will traverse - any subcatalogs recursively. - - Returns: - Generator[Item]: All items that belong to this catalog, and all - catalogs or collections connected to this catalog through - child links. - """ - import warnings - from itertools import chain - - warnings.warn( - "get_all_items is deprecated and will be removed in v2", - DeprecationWarning, - ) - return chain( - self.get_items(), - *(child.get_items(recursive=True) for child in self.get_children()), - ) - - def get_item_links(self) -> list[Link]: - """Return all item links of this catalog. - - Return: - List[Link]: List of links of this catalog with ``rel == 'item'`` - """ - return self.get_links( - rel=pystac.RelType.ITEM, media_type=pystac.media_type.STAC_JSON - ) - - def to_dict( - self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> dict[str, Any]: - links = [ - x - for x in self.links - if x.rel != pystac.RelType.ROOT or x.get_href(transform_hrefs) is not None - ] - if not include_self_link: - links = [x for x in links if x.rel != pystac.RelType.SELF] - - d: dict[str, Any] = { - "type": self.STAC_OBJECT_TYPE.value.title(), - "id": self.id, - "stac_version": pystac.get_stac_version(), - "description": self.description, - "links": [link.to_dict(transform_href=transform_hrefs) for link in links], - } - - if self.stac_extensions: - d["stac_extensions"] = self.stac_extensions - - for key in self.extra_fields: - d[key] = self.extra_fields[key] - - if self.title is not None: - d["title"] = self.title - - return d - - def clone(self) -> Catalog: - cls = self.__class__ - clone = cls( - id=self.id, - description=self.description, - title=self.title, - stac_extensions=self.stac_extensions.copy(), - extra_fields=deepcopy(self.extra_fields), - catalog_type=self.catalog_type, - ) - clone._resolved_objects.cache(clone) - - for link in self.links: - if link.rel == pystac.RelType.ROOT: - # Catalog __init__ sets correct root to clone; don't reset - # if the root link points to self - root_is_self = link.is_resolved() and link.target is self - if not root_is_self: - clone.set_root(None) - clone.add_link(link.clone()) - else: - clone.add_link(link.clone()) - - return clone - - def make_all_asset_hrefs_relative(self) -> None: - """Recursively makes all the HREFs of assets in this catalog relative""" - for item in self.get_items(recursive=True): - item.make_asset_hrefs_relative() - for collection in self.get_all_collections(): - collection.make_asset_hrefs_relative() - - def make_all_asset_hrefs_absolute(self) -> None: - """Recursively makes all the HREFs of assets in this catalog absolute""" - for item in self.get_items(recursive=True): - item.make_asset_hrefs_absolute() - for collection in self.get_all_collections(): - collection.make_asset_hrefs_absolute() - - def normalize_and_save( - self, - root_href: str, - catalog_type: CatalogType | None = None, - strategy: HrefLayoutStrategy | None = None, - stac_io: pystac.StacIO | None = None, - skip_unresolved: bool = False, - ) -> None: - """Normalizes link HREFs to the given root_href, and saves the catalog. - - This is a convenience method that simply calls :func:`Catalog.normalize_hrefs - ` and :func:`Catalog.save ` - in sequence. - - Args: - root_href : The absolute HREF that all links will be normalized - against. - catalog_type : The catalog type that dictates the structure of - the catalog to save. Use a member of :class:`~pystac.CatalogType`. - Defaults to the root catalog.catalog_type or the current catalog - catalog_type if there is no root catalog. - strategy : The layout strategy to use in setting the - HREFS for this catalog. If not provided, defaults to - the layout strategy of the parent or root and falls back to - :class:`~pystac.layout.BestPracticesLayoutStrategy` - stac_io : Optional instance of :class:`~pystac.StacIO` to use. If not - provided, will use the instance set while reading in the catalog, - or the default instance if this is not available. - skip_unresolved : Skip unresolved links when normalizing the tree. - Defaults to False. Because unresolved links are not saved, this - argument can be used to normalize and save only newly-added - objects. - """ - self.normalize_hrefs( - root_href, strategy=strategy, skip_unresolved=skip_unresolved - ) - self.save(catalog_type, stac_io=stac_io) - - def normalize_hrefs( - self, - root_href: str, - strategy: HrefLayoutStrategy | None = None, - skip_unresolved: bool = False, - ) -> None: - """Normalize HREFs will regenerate all link HREFs based on - an absolute root_href and the canonical catalog layout as specified - in the STAC specification's best practices. - - This method mutates the entire catalog tree, unless ``skip_unresolved`` - is True, in which case only resolved links are modified. This is useful - in the case when you have loaded a large catalog and you've added a few - items/children, and you only want to update those newly-added objects, - not the whole tree. - - Args: - root_href : The absolute HREF that all links will be normalized against. - strategy : The layout strategy to use in setting the HREFS - for this catalog. If not provided, defaults to - the layout strategy of the parent or root and falls back to - :class:`~pystac.layout.BestPracticesLayoutStrategy` - skip_unresolved : Skip unresolved links when normalizing the tree. - Defaults to False. - - See: - :stac-spec:`STAC best practices document ` - for the canonical layout of a STAC. - """ - - _strategy = self._get_strategy(strategy) - - # Normalizing requires an absolute path - if not is_absolute_href(root_href): - root_href = make_absolute_href(root_href, os.getcwd(), start_is_dir=True) - - if isinstance(_strategy, APILayoutStrategy) and not _is_url(root_href): - raise STACError("When using APILayoutStrategy the root_href must be a URL") - - def process_item( - item: Item, _root_href: str, is_root: bool, parent: Catalog | None - ) -> Callable[[], None] | None: - if not skip_unresolved: - item.resolve_links() - - # Abort as the intended parent is not the actual parent - # https://github.com/stac-utils/pystac/issues/1116 - if parent is not None and item.get_parent() != parent: - return None - - new_self_href = _strategy.get_href(item, _root_href, is_root) - - def fn() -> None: - item.set_self_href(new_self_href) - - return fn - - def process_catalog( - cat: Catalog, - _root_href: str, - is_root: bool, - parent: Catalog | None = None, - ) -> list[Callable[[], None]]: - setter_funcs: list[Callable[[], None]] = [] - - if not skip_unresolved: - cat.resolve_links() - - # Abort as the intended parent is not the actual parent - # https://github.com/stac-utils/pystac/issues/1116 - if parent is not None and cat.get_parent() != parent: - return setter_funcs - - new_self_href = _strategy.get_href(cat, _root_href, is_root) - new_root = new_self_href - - for link in cat.get_links(): - if skip_unresolved and not link.is_resolved(): - continue - elif link.rel == pystac.RelType.ITEM: - link.resolve_stac_object(root=self.get_root()) - item_fn = process_item( - cast(pystac.Item, link.target), new_root, is_root, cat - ) - if item_fn is not None: - setter_funcs.append(item_fn) - elif link.rel == pystac.RelType.CHILD: - link.resolve_stac_object(root=self.get_root()) - setter_funcs.extend( - process_catalog( - cast(pystac.Catalog | pystac.Collection, link.target), - new_root, - is_root=False, - parent=cat, - ) - ) - - def fn() -> None: - cat.set_self_href(new_self_href) - - setter_funcs.append(fn) - - return setter_funcs - - # Collect functions that will actually mutate the objects. - # Delay mutation as setting hrefs while walking the catalog - # can result in bad links. - setter_funcs = process_catalog(self, root_href, is_root=True) - - for fn in setter_funcs: - fn() - - def generate_subcatalogs( - self, - template: str, - defaults: dict[str, Any] | None = None, - parent_ids: list[str] | None = None, - ) -> list[Catalog]: - """Walks through the catalog and generates subcatalogs - for items based on the template string. - - See :class:`~pystac.layout.LayoutTemplate` - for details on the construction of template strings. This template string - will be applied to the items, and subcatalogs will be created that separate - and organize the items based on template values. - - Args: - template : A template string that - can be consumed by a :class:`~pystac.layout.LayoutTemplate` - defaults : Default values for the template variables - that will be used if the property cannot be found on - the item. - parent_ids : Optional list of the parent catalogs' - identifiers. If the bottom-most subcatalogs already match the - template, no subcatalog is added. - - Returns: - list[Catalog]: List of new catalogs created - """ - result: list[Catalog] = [] - parent_ids = parent_ids or list() - parent_ids.append(self.id) - for child in self.get_children(): - result.extend( - child.generate_subcatalogs( - template, defaults=defaults, parent_ids=parent_ids.copy() - ) - ) - - layout_template = LayoutTemplate(template, defaults=defaults) - - keep_item_links: list[Link] = [] - item_links = [lk for lk in self.links if lk.rel == pystac.RelType.ITEM] - for link in item_links: - link.resolve_stac_object(root=self.get_root()) - item = cast(pystac.Item, link.target) - subcat_ids = layout_template.substitute(item).split("/") - id_iter = reversed(parent_ids) - if all([f"{id}" == next(id_iter, None) for id in reversed(subcat_ids)]): - # Skip items for which the sub-catalog structure already - # matches the template. The list of parent IDs can include more - # elements on the root side, so compare the reversed sequences. - keep_item_links.append(link) - continue - curr_parent = self - for subcat_id in subcat_ids: - subcat = curr_parent.get_child(subcat_id) - if subcat is None: - subcat_desc = "Catalog of items from {} with id {}".format( - curr_parent.id, subcat_id - ) - subcat = pystac.Catalog(id=subcat_id, description=subcat_desc) - curr_parent.add_child(subcat) - result.append(subcat) - curr_parent = subcat - - # resolve collection link so when added back points to correct location - col_link = item.get_single_link(pystac.RelType.COLLECTION) - if col_link is not None: - col_link.resolve_stac_object() - - curr_parent.add_item(item) - - # keep only non-item links and item links that have not been moved elsewhere - self.links = [ - lk for lk in self.links if lk.rel != pystac.RelType.ITEM - ] + keep_item_links - - return result - - def save( - self, - catalog_type: CatalogType | None = None, - dest_href: str | None = None, - stac_io: pystac.StacIO | None = None, - ) -> None: - """Save this catalog and all it's children/item to files determined by the - object's self link HREF or a specified path. - - Args: - catalog_type : The catalog type that dictates the structure of - the catalog to save. Use a member of :class:`~pystac.CatalogType`. - If not supplied, the catalog_type of this catalog will be used. - If that attribute is not set, an exception will be raised. - dest_href : The location where the catalog is to be saved. - If not supplied, the catalog's self link HREF is used to determine - the location of the catalog file and children's files. - stac_io : Optional instance of :class:`~pystac.StacIO` to use. If not - provided, will use the instance set while reading in the catalog, - or the default instance if this is not available. - Note: - If the catalog type is ``CatalogType.ABSOLUTE_PUBLISHED``, - all self links will be included, and hierarchical links be absolute URLs. - If the catalog type is ``CatalogType.RELATIVE_PUBLISHED``, this catalog's - self link will be included, but no child catalog will have self links, and - hierarchical links will be relative URLs - If the catalog type is ``CatalogType.SELF_CONTAINED``, no self links will - be included and hierarchical links will be relative URLs. - """ - - root = self.get_root() - if root is None: - raise Exception("There is no root catalog") - - if catalog_type is not None: - root.catalog_type = catalog_type - - items_include_self_link = root.catalog_type in [CatalogType.ABSOLUTE_PUBLISHED] - - for child_link in self.get_child_links(): - if child_link.is_resolved(): - child = cast(Catalog, child_link.target) - if dest_href is not None: - rel_href = make_relative_href(child.self_href, self.self_href) - child_dest_href = make_absolute_href( - rel_href, dest_href, start_is_dir=True - ) - child.save( - dest_href=os.path.dirname(child_dest_href), - stac_io=stac_io, - ) - else: - child.save(stac_io=stac_io) - - for item_link in self.get_item_links(): - if item_link.is_resolved(): - item = cast(pystac.Item, item_link.target) - if dest_href is not None: - rel_href = make_relative_href(item.self_href, self.self_href) - item_dest_href = make_absolute_href( - rel_href, dest_href, start_is_dir=True - ) - item.save_object( - include_self_link=items_include_self_link, - dest_href=item_dest_href, - stac_io=stac_io, - ) - else: - item.save_object( - include_self_link=items_include_self_link, stac_io=stac_io - ) - - include_self_link = False - # include a self link if this is the root catalog - # or if ABSOLUTE_PUBLISHED catalog - if root.catalog_type == CatalogType.ABSOLUTE_PUBLISHED: - include_self_link = True - elif root.catalog_type != CatalogType.SELF_CONTAINED: - root_link = self.get_root_link() - if root_link and root_link.get_absolute_href() == self.get_self_href(): - include_self_link = True - - catalog_dest_href = None - if dest_href is not None: - rel_href = make_relative_href(self.self_href, self.self_href) - catalog_dest_href = make_absolute_href( - rel_href, dest_href, start_is_dir=True - ) - self.save_object( - include_self_link=include_self_link, - dest_href=catalog_dest_href, - stac_io=stac_io, - ) - if catalog_type is not None: - self.catalog_type = catalog_type - - def walk( - self, - ) -> Iterable[tuple[Catalog, Iterable[Catalog], Iterable[Item]]]: - """Walks through children and items of catalogs. - - For each catalog in the STAC's tree rooted at this catalog (including this - catalog itself), it yields a 3-tuple (root, subcatalogs, items). The root in - that 3-tuple refers to the current catalog being walked, the subcatalogs are any - catalogs or collections for which the root is a parent, and items represents - any items that have the root as a parent. - - This has similar functionality to Python's :func:`os.walk`. - - Returns: - Generator[(Catalog, Generator[Catalog], Generator[Item])]: A generator that - yields a 3-tuple (parent_catalog, children, items). - """ - children = self.get_children() - items = self.get_items() - - yield self, children, items - for child in self.get_children(): - yield from child.walk() - - def fully_resolve(self) -> None: - """Resolves every link in this catalog. - - Useful if, e.g., you'd like to read a catalog from a filesystem, upgrade - every object in the catalog to the latest STAC version, and save it back - to the filesystem. By default, :py:meth:`~pystac.Catalog.save` skips - unresolved links. - """ - for _, _, items in self.walk(): - # items is a generator, so we need to consume it to resolve the - # items - for item in items: - pass - - def validate_all(self, max_items: int | None = None, recursive: bool = True) -> int: - """Validates each catalog, collection, item contained within this catalog. - - Walks through the children and items of the catalog and validates each - stac object. - - Args: - max_items : The maximum number of STAC items to validate. Default - is None which means, validate them all. - recursive : Whether to validate catalog, collections, and items contained - within child objects. - - Returns: - int : Number of STAC items validated. - - Raises: - STACValidationError: Raises this error on any item that is invalid. - Will raise on the first invalid stac object encountered. - """ - n = 0 - self.validate() - for child in self.get_children(): - if recursive: - inner_max_items = None if max_items is None else max_items - n - n += child.validate_all(max_items=inner_max_items, recursive=True) - else: - child.validate() - for item in self.get_items(): - if max_items is not None and n >= max_items: - break - item.validate() - n += 1 - return n - - def _object_links(self) -> list[str | pystac.RelType]: - return [ - pystac.RelType.CHILD, - pystac.RelType.ITEM, - *pystac.EXTENSION_HOOKS.get_extended_object_links(self), - ] - - def map_items( - self, - item_mapper: Callable[[Item], Item | list[Item]], - ) -> Catalog: - """Creates a copy of a catalog, with each item passed through the - item_mapper function. - - Args: - item_mapper : A function that takes in an item, and returns - either an item or list of items. The item that is passed into the - item_mapper is a copy, so the method can mutate it safely. - - Returns: - Catalog: A full copy of this catalog, with items manipulated according - to the item_mapper function. - """ - - new_cat = self.full_copy() - - def process_catalog(catalog: Catalog) -> None: - for child in catalog.get_children(): - process_catalog(child) - - item_links: list[Link] = [] - for item_link in catalog.get_item_links(): - item_link.resolve_stac_object(root=self.get_root()) - mapped = item_mapper(cast(pystac.Item, item_link.target)) - if mapped is None: - raise Exception("item_mapper cannot return None.") - if isinstance(mapped, pystac.Item): - item_link.target = mapped - item_links.append(item_link) - else: - for i in mapped: - new_link = item_link.clone() - new_link.target = i - item_links.append(new_link) - catalog.clear_items() - catalog.add_links(item_links) - - process_catalog(new_cat) - return new_cat - - def map_assets( - self, - asset_mapper: Callable[ - [str, Asset], - Asset | tuple[str, Asset] | dict[str, Asset], - ], - ) -> Catalog: - """Creates a copy of a catalog, with each Asset for each Item passed - through the asset_mapper function. - - Args: - asset_mapper : A function that takes in a key and an Asset, and - returns either an Asset, a (key, Asset), or a dictionary of Assets with - unique keys. The Asset that is passed into the item_mapper is a copy, - so the method can mutate it safely. - - Returns: - Catalog: A full copy of this catalog, with assets manipulated according - to the asset_mapper function. - """ - - def apply_asset_mapper( - tup: tuple[str, Asset], - ) -> list[tuple[str, pystac.Asset]]: - k, v = tup - result = asset_mapper(k, v) - if result is None: - raise Exception("asset_mapper cannot return None.") - if isinstance(result, pystac.Asset): - return [(k, result)] - elif isinstance(result, tuple): - return [result] - else: - assets = list(result.items()) - if len(assets) < 1: - raise Exception("asset_mapper must return a non-empty list") - return assets - - def item_mapper(item: pystac.Item) -> pystac.Item: - new_assets = [ - x - for result in map(apply_asset_mapper, item.assets.items()) - for x in result - ] - item.assets = dict(new_assets) - return item - - return self.map_items(item_mapper) - - def describe(self, include_hrefs: bool = False, _indent: int = 0) -> None: - """Prints out information about this Catalog and all contained - STACObjects. - - Args: - include_hrefs (bool) - If True, print out each object's self link - HREF along with the object ID. - """ - s = "{}* {}".format(" " * _indent, self) - if include_hrefs: - s += f" {self.get_self_href()}" - print(s) - for child in self.get_children(): - child.describe(include_hrefs=include_hrefs, _indent=_indent + 4) - for item in self.get_items(): - s = "{}* {}".format(" " * (_indent + 2), item) - if include_hrefs: - s += f" {item.get_self_href()}" - print(s) - - @classmethod - def from_dict( - cls: type[C], - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> C: - if migrate: - info = identify_stac_object(d) - d = migrate_to_latest(d, info) - - if not cls.matches_object_type(d): - raise STACTypeError(d, cls) - - catalog_type = CatalogType.determine_type(d) - - if preserve_dict: - d = deepcopy(d) - - id = d.pop("id") - description = d.pop("description") - title = d.pop("title", None) - stac_extensions = d.pop("stac_extensions", None) - links = d.pop("links") - - d.pop("stac_version") - - cat = cls( - id=id, - description=description, - title=title, - stac_extensions=stac_extensions, - extra_fields=d, - href=href, - catalog_type=catalog_type or CatalogType.ABSOLUTE_PUBLISHED, - ) - - for link in links: - if link["rel"] == pystac.RelType.ROOT: - # Remove the link that's generated in Catalog's constructor. - cat.remove_links(pystac.RelType.ROOT) - - if link["rel"] != pystac.RelType.SELF or href is None: - cat.add_link(Link.from_dict(link)) - - if root: - cat.set_root(root) - - return cat - - def full_copy( - self, root: Catalog | None = None, parent: Catalog | None = None - ) -> Catalog: - return cast(Catalog, super().full_copy(root, parent)) - - @classmethod - def from_file(cls: type[C], href: HREF, stac_io: pystac.StacIO | None = None) -> C: - if stac_io is None: - stac_io = pystac.StacIO.default() - - result = super().from_file(href, stac_io) - result._stac_io = stac_io - - return result - - @classmethod - def matches_object_type(cls, d: dict[str, Any]) -> bool: - return identify_stac_object_type(d) == STACObjectType.CATALOG - - @property - def ext(self) -> CatalogExt: - """Accessor for extension classes on this catalog - - Example:: - - print(collection.ext.version) - """ - from pystac.extensions.ext import CatalogExt - - return CatalogExt(stac_object=self) diff --git a/pystac/collection.py b/pystac/collection.py deleted file mode 100644 index 536fd2830..000000000 --- a/pystac/collection.py +++ /dev/null @@ -1,899 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterable, Sequence -from copy import deepcopy -from datetime import datetime, timezone -from typing import ( - TYPE_CHECKING, - Any, - Optional, - TypeVar, - cast, -) - -import pystac -from pystac import CatalogType, STACObjectType -from pystac.asset import Asset, Assets -from pystac.catalog import Catalog -from pystac.errors import DeprecatedWarning, ExtensionNotImplemented, STACTypeError -from pystac.item_assets import ItemAssetDefinition, _ItemAssets -from pystac.layout import HrefLayoutStrategy -from pystac.link import Link -from pystac.provider import Provider -from pystac.serialization import ( - identify_stac_object, - identify_stac_object_type, - migrate_to_latest, -) -from pystac.summaries import Summaries -from pystac.utils import ( - datetime_to_str, - str_to_datetime, -) - -if TYPE_CHECKING: - from pystac.extensions.ext import CollectionExt - from pystac.item import Item - -#: Generalized version of :class:`Collection` -C = TypeVar("C", bound="Collection") - -Bboxes = list[list[float | int]] -TemporalIntervals = list[list[datetime]] | list[list[Optional[datetime]]] -TemporalIntervalsLike = TemporalIntervals | list[datetime] | list[Optional[datetime]] - - -class SpatialExtent: - """Describes the spatial extent of a Collection. - - Args: - bboxes : A list of bboxes that represent the spatial - extent of the collection. Each bbox can be 2D or 3D. The length of the bbox - array must be 2*n where n is the number of dimensions. For example, a - 2D Collection with only one bbox would be [[xmin, ymin, xmax, ymax]] - - extra_fields : Dictionary containing additional top-level fields defined on the - Spatial Extent object. - """ - - bboxes: Bboxes - """A list of bboxes that represent the spatial - extent of the collection. Each bbox can be 2D or 3D. The length of the bbox - array must be 2*n where n is the number of dimensions. For example, a - 2D Collection with only one bbox would be [[xmin, ymin, xmax, ymax]]""" - - extra_fields: dict[str, Any] - """Dictionary containing additional top-level fields defined on the Spatial - Extent object.""" - - def __init__( - self, - bboxes: Bboxes | Sequence[float | int], - extra_fields: dict[str, Any] | None = None, - ) -> None: - if not isinstance(bboxes, list): - raise TypeError("bboxes must be a list") - - # A common mistake is to pass in a single bbox instead of a list of bboxes. - # Account for this by transforming the input in that case. - if isinstance(bboxes[0], (float, int)): - self.bboxes = [cast(list[float | int], bboxes)] - else: - self.bboxes = cast(Bboxes, bboxes) - - self.extra_fields = extra_fields or {} - - def to_dict(self) -> dict[str, Any]: - """Returns this spatial extent as a dictionary. - - Returns: - dict: A serialization of the SpatialExtent. - """ - d = {"bbox": self.bboxes, **self.extra_fields} - return d - - def clone(self) -> SpatialExtent: - """Clones this object. - - Returns: - SpatialExtent: The clone of this object. - """ - cls = self.__class__ - return cls( - bboxes=deepcopy(self.bboxes), extra_fields=deepcopy(self.extra_fields) - ) - - @staticmethod - def from_dict(d: dict[str, Any]) -> SpatialExtent: - """Constructs a SpatialExtent from a dict. - - Returns: - SpatialExtent: The SpatialExtent deserialized from the JSON dict. - """ - return SpatialExtent( - bboxes=d["bbox"], extra_fields={k: v for k, v in d.items() if k != "bbox"} - ) - - @staticmethod - def from_coordinates( - coordinates: list[Any], extra_fields: dict[str, Any] | None = None - ) -> SpatialExtent: - """Constructs a SpatialExtent from a set of coordinates. - - This method will only produce a single bbox that covers all points - in the coordinate set. - - Args: - coordinates : Coordinates to derive the bbox from. - extra_fields : Dictionary containing additional top-level fields defined on - the SpatialExtent object. - - Returns: - SpatialExtent: A SpatialExtent with a single bbox that covers the - given coordinates. - """ - - def process_coords( - coord_lists: list[Any], - xmin: float | None = None, - ymin: float | None = None, - xmax: float | None = None, - ymax: float | None = None, - ) -> tuple[float | None, float | None, float | None, float | None]: - for coord in coord_lists: - if isinstance(coord[0], list): - xmin, ymin, xmax, ymax = process_coords( - coord, xmin, ymin, xmax, ymax - ) - else: - x, y = coord - if xmin is None or x < xmin: - xmin = x - elif xmax is None or xmax < x: - xmax = x - if ymin is None or y < ymin: - ymin = y - elif ymax is None or ymax < y: - ymax = y - return xmin, ymin, xmax, ymax - - xmin, ymin, xmax, ymax = process_coords(coordinates) - - if xmin is None or ymin is None or xmax is None or ymax is None: - raise ValueError( - f"Could not determine bounds from coordinate sequence {coordinates}" - ) - - return SpatialExtent( - bboxes=[[xmin, ymin, xmax, ymax]], extra_fields=extra_fields - ) - - -class TemporalExtent: - """Describes the temporal extent of a Collection. - - Args: - intervals : A list of two datetimes wrapped in a list, - representing the temporal extent of a Collection. Open date ranges are - supported by setting either the start (the first element of the interval) - or the end (the second element of the interval) to None. - - extra_fields : Dictionary containing additional top-level fields defined on the - Temporal Extent object. - Note: - Datetimes are required to be in UTC. - """ - - intervals: TemporalIntervals - """A list of two datetimes wrapped in a list, - representing the temporal extent of a Collection. Open date ranges are - represented by either the start (the first element of the interval) or the - end (the second element of the interval) being None.""" - - extra_fields: dict[str, Any] - """Dictionary containing additional top-level fields defined on the Temporal - Extent object.""" - - def __init__( - self, - intervals: TemporalIntervals | Sequence[datetime | None], - extra_fields: dict[str, Any] | None = None, - ): - if not isinstance(intervals, list): - raise TypeError("intervals must be a list") - # A common mistake is to pass in a single interval instead of a - # list of intervals. Account for this by transforming the input - # in that case. - if isinstance(intervals[0], datetime) or intervals[0] is None: - self.intervals = [cast(list[Optional[datetime]], intervals)] - else: - self.intervals = cast(TemporalIntervals, intervals) - - self.extra_fields = extra_fields or {} - - def to_dict(self) -> dict[str, Any]: - """Returns this temporal extent as a dictionary. - - Returns: - dict: A serialization of the TemporalExtent. - """ - encoded_intervals: list[list[str | None]] = [] - for i in self.intervals: - start = None - end = None - - if i[0] is not None: - start = datetime_to_str(i[0]) - - if i[1] is not None: - end = datetime_to_str(i[1]) - - encoded_intervals.append([start, end]) - - d = {"interval": encoded_intervals, **self.extra_fields} - return d - - def clone(self) -> TemporalExtent: - """Clones this object. - - Returns: - TemporalExtent: The clone of this object. - """ - cls = self.__class__ - return cls( - intervals=deepcopy(self.intervals), extra_fields=deepcopy(self.extra_fields) - ) - - @staticmethod - def from_dict(d: dict[str, Any]) -> TemporalExtent: - """Constructs an TemporalExtent from a dict. - - Returns: - TemporalExtent: The TemporalExtent deserialized from the JSON dict. - """ - parsed_intervals: list[list[datetime | None]] = [] - for i in d["interval"]: - if isinstance(i, str): - import warnings - - # d["interval"] is a list of strings, so we correct the list and - # try again - # https://github.com/stac-utils/pystac/issues/1221 - warnings.warn( - "A collection's temporal extent should be a list of lists, but " - "is instead a " - "list of strings. pystac is fixing this issue and continuing " - "deserialization, but note that the source " - "collection is invalid STAC.", - UserWarning, - ) - d["interval"] = [d["interval"]] - return TemporalExtent.from_dict(d) - start = None - end = None - - if i[0]: - start = str_to_datetime(i[0]) - if i[1]: - end = str_to_datetime(i[1]) - parsed_intervals.append([start, end]) - - return TemporalExtent( - intervals=parsed_intervals, - extra_fields={k: v for k, v in d.items() if k != "interval"}, - ) - - @staticmethod - def from_now() -> TemporalExtent: - """Constructs an TemporalExtent with a single open interval that has - the start time as the current time. - - Returns: - TemporalExtent: The resulting TemporalExtent. - """ - return TemporalExtent( - intervals=[[datetime.now(timezone.utc).replace(microsecond=0), None]] - ) - - -class Extent: - """Describes the spatiotemporal extents of a Collection. - - Args: - spatial : Potential spatial extent covered by the collection. - temporal : Potential temporal extent covered by the collection. - extra_fields : Dictionary containing additional top-level fields defined on the - Extent object. - """ - - spatial: SpatialExtent - """Potential spatial extent covered by the collection.""" - - temporal: TemporalExtent - """Potential temporal extent covered by the collection.""" - - extra_fields: dict[str, Any] - """Dictionary containing additional top-level fields defined on the Extent - object.""" - - def __init__( - self, - spatial: SpatialExtent, - temporal: TemporalExtent, - extra_fields: dict[str, Any] | None = None, - ): - self.spatial = spatial - self.temporal = temporal - self.extra_fields = extra_fields or {} - - def to_dict(self) -> dict[str, Any]: - """Returns this extent as a dictionary. - - Returns: - dict: A serialization of the Extent. - """ - d = { - "spatial": self.spatial.to_dict(), - "temporal": self.temporal.to_dict(), - **self.extra_fields, - } - - return d - - def clone(self) -> Extent: - """Clones this object. - - Returns: - Extent: The clone of this extent. - """ - cls = self.__class__ - return cls( - spatial=self.spatial.clone(), - temporal=self.temporal.clone(), - extra_fields=deepcopy(self.extra_fields), - ) - - @staticmethod - def from_dict(d: dict[str, Any]) -> Extent: - """Constructs an Extent from a dict. - - Returns: - Extent: The Extent deserialized from the JSON dict. - """ - return Extent( - spatial=SpatialExtent.from_dict(d["spatial"]), - temporal=TemporalExtent.from_dict(d["temporal"]), - extra_fields={ - k: v for k, v in d.items() if k not in {"spatial", "temporal"} - }, - ) - - @staticmethod - def from_items( - items: Iterable[Item], extra_fields: dict[str, Any] | None = None - ) -> Extent: - """Create an Extent based on the datetimes and bboxes of a list of items. - - Args: - items : A list of items to derive the extent from. - extra_fields : Optional dictionary containing additional top-level fields - defined on the Extent object. - - Returns: - Extent: An Extent that spatially and temporally covers all of the - given items. - """ - from dateutil import tz - - bounds_values: list[list[float]] = [ - [float("inf")], - [float("inf")], - [float("-inf")], - [float("-inf")], - ] - datetimes: list[datetime] = [] - starts: list[datetime] = [] - ends: list[datetime] = [] - - for item in items: - if item.bbox is not None: - for i in range(0, 4): - bounds_values[i].append(item.bbox[i]) - if item.datetime is not None: - datetimes.append(item.datetime) - if item.common_metadata.start_datetime is not None: - starts.append(item.common_metadata.start_datetime) - if item.common_metadata.end_datetime is not None: - ends.append(item.common_metadata.end_datetime) - - if not any(datetimes + starts): - start_timestamp = None - else: - start_timestamp = min( - [ - dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) - for dt in datetimes + starts - ] - ) - if not any(datetimes + ends): - end_timestamp = None - else: - end_timestamp = max( - [ - dt if dt.tzinfo else dt.replace(tzinfo=tz.UTC) - for dt in datetimes + ends - ] - ) - - spatial = SpatialExtent( - [ - [ - min(bounds_values[0]), - min(bounds_values[1]), - max(bounds_values[2]), - max(bounds_values[3]), - ] - ] - ) - temporal = TemporalExtent([[start_timestamp, end_timestamp]]) - - return Extent(spatial=spatial, temporal=temporal, extra_fields=extra_fields) - - -class Collection(Catalog, Assets): - """A Collection extends the Catalog spec with additional metadata that helps - enable discovery. - - Args: - id : Identifier for the collection. Must be unique within the STAC. - description : Detailed multi-line description to fully explain the - collection. `CommonMark 0.29 syntax `_ MAY - be used for rich text representation. - extent : Spatial and temporal extents that describe the bounds of - all items contained within this Collection. - title : Optional short descriptive one-line title for the - collection. - stac_extensions : Optional list of extensions the Collection - implements. - href : Optional HREF for this collection, which be set as the - collection's self link's HREF. - catalog_type : Optional catalog type for this catalog. Must - be one of the values in :class`~pystac.CatalogType`. - license : Collection's license(s) as a - `SPDX License identifier `_, - or `other`. If collection includes data with multiple - different licenses, use `other` and add a link for - each. The licenses `various` and `proprietary` are deprecated. - Defaults to 'other'. - keywords : Optional list of keywords describing the collection. - providers : Optional list of providers of this Collection. - summaries : An optional map of property summaries, - either a set of values or statistics such as a range. - extra_fields : Extra fields that are part of the top-level - JSON properties of the Collection. - assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All - :class:`~pystac.Asset` values in the dictionary will have their - :attr:`~pystac.Asset.owner` attribute set to the created Collection. - strategy : The layout strategy to use for setting the - HREFs of the catalog child objects and items. - If not provided, it will default to strategy of the parent and fallback to - :class:`~pystac.layout.BestPracticesLayoutStrategy`. - """ - - description: str - """Detailed multi-line description to fully explain the collection.""" - - extent: Extent - """Spatial and temporal extents that describe the bounds of all items contained - within this Collection.""" - - id: str - """Identifier for the collection.""" - - stac_extensions: list[str] - """List of extensions the Collection implements.""" - - title: str | None - """Optional short descriptive one-line title for the collection.""" - - keywords: list[str] | None - """Optional list of keywords describing the collection.""" - - providers: list[Provider] | None - """Optional list of providers of this Collection.""" - - summaries: Summaries - """A map of property summaries, either a set of values or statistics such as a - range.""" - - links: list[Link] - """A list of :class:`~pystac.Link` objects representing all links associated with - this Collection.""" - - extra_fields: dict[str, Any] - """Extra fields that are part of the top-level JSON properties of the Collection.""" - - STAC_OBJECT_TYPE = STACObjectType.COLLECTION - - DEFAULT_FILE_NAME = "collection.json" - """Default file name that will be given to this STAC object - in a canonical format.""" - - def __init__( - self, - id: str, - description: str, - extent: Extent, - title: str | None = None, - stac_extensions: list[str] | None = None, - href: str | None = None, - extra_fields: dict[str, Any] | None = None, - catalog_type: CatalogType | None = None, - license: str = "other", - keywords: list[str] | None = None, - providers: list[Provider] | None = None, - summaries: Summaries | None = None, - assets: dict[str, Asset] | None = None, - strategy: HrefLayoutStrategy | None = None, - ): - super().__init__( - id, - description, - title, - stac_extensions, - extra_fields, - href, - catalog_type or CatalogType.ABSOLUTE_PUBLISHED, - strategy, - ) - self.extent = extent - self.license = license - - self.stac_extensions: list[str] = stac_extensions or [] - self.keywords = keywords - self.providers = providers - self.summaries = summaries or Summaries.empty() - self._item_assets: _ItemAssets | None = None - - self.assets = {} - if assets is not None: - for k, asset in assets.items(): - self.add_asset(k, asset) - - def __repr__(self) -> str: - return f"" - - def add_item( - self, - item: Item, - title: str | None = None, - strategy: HrefLayoutStrategy | None = None, - set_parent: bool = True, - ) -> Link: - link = super().add_item(item, title, strategy, set_parent) - item.set_collection(self) - return link - - def to_dict( - self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> dict[str, Any]: - d = super().to_dict( - include_self_link=include_self_link, transform_hrefs=transform_hrefs - ) - d["extent"] = self.extent.to_dict() - d["license"] = self.license - if self.stac_extensions: - d["stac_extensions"] = self.stac_extensions - if self.keywords: - d["keywords"] = self.keywords - if self.providers: - d["providers"] = list(map(lambda x: x.to_dict(), self.providers)) - if not self.summaries.is_empty(): - d["summaries"] = self.summaries.to_dict() - if any(self.assets): - d["assets"] = {k: v.to_dict() for k, v in self.assets.items()} - - return d - - def clone(self) -> Collection: - cls = self.__class__ - clone = cls( - id=self.id, - description=self.description, - extent=self.extent.clone(), - title=self.title, - stac_extensions=self.stac_extensions.copy(), - extra_fields=deepcopy(self.extra_fields), - catalog_type=self.catalog_type, - license=self.license, - keywords=self.keywords.copy() if self.keywords is not None else None, - providers=deepcopy(self.providers), - summaries=self.summaries.clone(), - assets={k: asset.clone() for k, asset in self.assets.items()}, - ) - - clone._resolved_objects.cache(clone) - - for link in self.links: - if link.rel == pystac.RelType.ROOT: - # Collection __init__ sets correct root to clone; don't reset - # if the root link points to self - root_is_self = link.is_resolved() and link.target is self - if not root_is_self: - clone.set_root(None) - clone.add_link(link.clone()) - else: - clone.add_link(link.clone()) - - return clone - - @classmethod - def from_dict( - cls: type[C], - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> C: - import warnings - - from pystac.extensions.version import CollectionVersionExtension - - if migrate: - info = identify_stac_object(d) - d = migrate_to_latest(d, info) - - if not cls.matches_object_type(d): - raise STACTypeError(d, cls) - - catalog_type = CatalogType.determine_type(d) - - if preserve_dict: - d = deepcopy(d) - - id = d.pop("id") - description = d.pop("description") - license = d.pop("license") - if extent_dict := d.pop("extent", None): - extent = Extent.from_dict(extent_dict) - else: - warnings.warn( - "Collection is missing extent, setting default spatial and " - "temporal extents" - ) - extent = Extent( - spatial=SpatialExtent([-90, -180, 90, 180]), - temporal=TemporalExtent([None, None]), - ) - title = d.pop("title", None) - stac_extensions = d.pop("stac_extensions", None) - keywords = d.pop("keywords", None) - providers = d.pop("providers", None) - if providers is not None: - providers = list(map(lambda x: pystac.Provider.from_dict(x), providers)) - summaries = d.pop("summaries", None) - if summaries is not None: - summaries = Summaries(summaries) - - assets = d.pop("assets", None) - if assets: - assets = {k: Asset.from_dict(v) for k, v in assets.items()} - links = d.pop("links") - - d.pop("stac_version") - - collection = cls( - id=id, - description=description, - extent=extent, - title=title, - stac_extensions=stac_extensions, - extra_fields=d, - license=license, - keywords=keywords, - providers=providers, - summaries=summaries, - href=href, - catalog_type=catalog_type, - assets=assets, - ) - - for link in links: - if link["rel"] == pystac.RelType.ROOT: - # Remove the link that's generated in Catalog's constructor. - collection.remove_links(pystac.RelType.ROOT) - - if link["rel"] != pystac.RelType.SELF or href is None: - collection.add_link(Link.from_dict(link)) - - if root: - collection.set_root(root) - - try: - version = CollectionVersionExtension.ext(collection) - if version.deprecated: - warnings.warn( - f"The collection '{collection.id}' is deprecated.", - DeprecatedWarning, - ) - # Collection asset deprecation checks pending version extension support - except ExtensionNotImplemented: - pass - - return collection - - @classmethod - def from_items( - cls: type[Collection], - items: Iterable[Item] | pystac.ItemCollection, - *, - id: str | None = None, - strategy: HrefLayoutStrategy | None = None, - ) -> Collection: - """Create a :class:`Collection` from iterable of items or an - :class:`~pystac.ItemCollection`. - - Will try to pull collection attributes from - :attr:`~pystac.ItemCollection.extra_fields` and items when possible. - - Args: - items : Iterable of :class:`~pystac.Item` instances to include in the - :class:`Collection`. This can be a :class:`~pystac.ItemCollection`. - id : Identifier for the collection. If not set, must be available on the - items and they must all match. - strategy : The layout strategy to use for setting the - HREFs of the catalog child objects and items. - If not provided, it will default to strategy of the parent and fallback - to :class:`~pystac.layout.BestPracticesLayoutStrategy`. - """ - - def extract(attr: str) -> Any: - """Extract attrs from items or item.properties as long as they all match""" - value = None - values = {getattr(item, attr, None) for item in items} - if len(values) == 1: - value = next(iter(values)) - if value is None: - values = {item.properties.get(attr, None) for item in items} - if len(values) == 1: - value = next(iter(values)) - return value - - if isinstance(items, pystac.ItemCollection): - extra_fields = deepcopy(items.extra_fields) - links = extra_fields.pop("links", {}) - providers = extra_fields.pop("providers", None) - if providers is not None: - providers = [pystac.Provider.from_dict(p) for p in providers] - else: - extra_fields = {} - links = {} - providers = [] - - id = id or extract("collection_id") - if id is None: - raise ValueError( - "Collection id must be defined. Either by specifying collection_id " - "on every item, or as a keyword argument to this function." - ) - - collection = cls( - id=id, - description=extract("description"), - extent=Extent.from_items(items), - title=extract("title"), - providers=providers, - extra_fields=extra_fields, - strategy=strategy, - ) - collection.add_items(items) - - for link in links: - collection.add_link(Link.from_dict(link)) - - return collection - - def get_item(self, id: str, recursive: bool = False) -> Item | None: - """Returns an item with a given ID. - - Args: - id : The ID of the item to find. - recursive : If True, search this collection and all children for the - item; otherwise, only search the items of this collection. Defaults - to False. - - Return: - Item or None: The item with the given ID, or None if not found. - """ - try: - return next(self.get_items(id, recursive=recursive), None) - except TypeError as e: - if any("recursive" in arg for arg in e.args): - # For inherited classes that do not yet support recursive - # See https://github.com/stac-utils/pystac-client/issues/485 - return super().get_item(id, recursive=recursive) - raise e - - @property - def item_assets(self) -> dict[str, ItemAssetDefinition]: - """Accessor for `item_assets - `__ - on this collection. - - Example:: - - .. code-block:: python - - >>> print(collection.item_assets) - {'thumbnail': , - 'metadata': , - 'B5': , - 'B6': , - 'B7': , - 'B8': } - >>> collection.item_assets["thumbnail"].title - 'Thumbnail' - - Set attributes on :class:`~pystac.ItemAssetDefinition` objects - - .. code-block:: python - - >>> collection.item_assets["thumbnail"].title = "New Title" - - Add to the ``item_assets`` dict: - - .. code-block:: python - - >>> collection.item_assets["B4"] = { - 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', - 'eo:bands': [{'name': 'B4', 'common_name': 'red'}] - } - >>> collection.item_assets["B4"].owner == collection - True - """ - if self._item_assets is None: - self._item_assets = _ItemAssets(self) - return self._item_assets - - @item_assets.setter - def item_assets( - self, item_assets: dict[str, ItemAssetDefinition | dict[str, Any]] | None - ) -> None: - # clear out the cached value - self._item_assets = None - - if item_assets is None: - self.extra_fields.pop("item_assets") - else: - self.extra_fields["item_assets"] = { - k: v if isinstance(v, dict) else v.to_dict() - for k, v in item_assets.items() - } - - def update_extent_from_items(self) -> None: - """ - Update datetime and bbox based on all items to a single bbox and time window. - """ - self.extent = Extent.from_items(self.get_items(recursive=True)) - - def full_copy( - self, root: Catalog | None = None, parent: Catalog | None = None - ) -> Collection: - return cast(Collection, super().full_copy(root, parent)) - - @classmethod - def matches_object_type(cls, d: dict[str, Any]) -> bool: - return identify_stac_object_type(d) == STACObjectType.COLLECTION - - @property - def ext(self) -> CollectionExt: - """Accessor for extension classes on this collection - - Example:: - - print(collection.ext.xarray) - """ - from pystac.extensions.ext import CollectionExt - - return CollectionExt(stac_object=self) diff --git a/pystac/item.py b/pystac/item.py deleted file mode 100644 index 8eb53ef55..000000000 --- a/pystac/item.py +++ /dev/null @@ -1,534 +0,0 @@ -from __future__ import annotations - -from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, TypeVar, cast - -import pystac -from pystac import RelType, STACError, STACObjectType -from pystac.asset import Asset, Assets -from pystac.catalog import Catalog -from pystac.collection import Collection -from pystac.errors import DeprecatedWarning, ExtensionNotImplemented -from pystac.link import Link -from pystac.serialization import ( - identify_stac_object, - identify_stac_object_type, - migrate_to_latest, -) -from pystac.stac_object import STACObject -from pystac.utils import ( - datetime_to_str, - is_absolute_href, - make_absolute_href, - make_relative_href, - str_to_datetime, -) - -#: Generalized version of :class:`Item` -T = TypeVar("T", bound="Item") - -if TYPE_CHECKING: - # avoids conflicts since there are also kwargs and attrs called `datetime` - from datetime import datetime as Datetime - - from pystac.extensions.ext import ItemExt - - -class Item(STACObject, Assets): - """An Item is the core granular entity in a STAC, containing the core metadata - that enables any client to search or crawl online catalogs of spatial 'assets' - - satellite imagery, derived data, DEM's, etc. - - Args: - id : Provider identifier. Must be unique within the STAC. - geometry : Defines the full footprint of the asset represented by this - item, formatted according to - `RFC 7946, section 3.1 (GeoJSON) `_. - bbox : Bounding Box of the asset represented by this item - using either 2D or 3D geometries. The length of the array must be 2*n - where n is the number of dimensions. Could also be None in the case of a - null geometry. - datetime : datetime associated with this item. If None, - a start_datetime and end_datetime must be supplied. - properties : A dictionary of additional metadata for the item. - start_datetime : Optional inclusive start datetime, part of common metadata. - This value will override any `start_datetime` key in properties. - end_datetime : Optional inclusive end datetime, part of common metadata. - This value will override any `end_datetime` key in properties. - stac_extensions : Optional list of extensions the Item implements. - href : Optional HREF for this item, which be set as the item's - self link's HREF. - collection : The Collection or Collection ID that this item - belongs to. - extra_fields : Extra fields that are part of the top-level JSON - properties of the Item. - assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All - :class:`~pystac.Asset` values in the dictionary will have their - :attr:`~pystac.Asset.owner` attribute set to the created Item. - """ - - assets: dict[str, Asset] - """Dictionary of :class:`~pystac.Asset` objects, each with a unique key.""" - - bbox: list[float] | None - """Bounding Box of the asset represented by this item using either 2D or 3D - geometries. The length of the array is 2*n where n is the number of dimensions. - Could also be None in the case of a null geometry.""" - - collection: Collection | None - """:class:`~pystac.Collection` to which this Item belongs, if any.""" - - collection_id: str | None - """The Collection ID that this item belongs to, if any.""" - - datetime: Datetime | None - """Datetime associated with this item. If ``None``, then - :attr:`~pystac.CommonMetadata.start_datetime` and - :attr:`~pystac.CommonMetadata.end_datetime` in :attr:`~pystac.Item.common_metadata` - will supply the datetime range of the Item.""" - - extra_fields: dict[str, Any] - """Extra fields that are part of the top-level JSON fields the Item.""" - - geometry: dict[str, Any] | None - """Defines the full footprint of the asset represented by this item, formatted - according to `RFC 7946, section 3.1 (GeoJSON) - `_.""" - - id: str - """Provider identifier. Unique within the STAC.""" - - links: list[Link] - """A list of :class:`~pystac.Link` objects representing all links associated with - this Item.""" - - properties: dict[str, Any] - """A dictionary of additional metadata for the Item.""" - - stac_extensions: list[str] - """List of extensions the Item implements.""" - - STAC_OBJECT_TYPE = STACObjectType.ITEM - - def __init__( - self, - id: str, - geometry: dict[str, Any] | None, - bbox: list[float] | None, - datetime: Datetime | None, - properties: dict[str, Any], - start_datetime: Datetime | None = None, - end_datetime: Datetime | None = None, - stac_extensions: list[str] | None = None, - href: str | None = None, - collection: str | Collection | None = None, - extra_fields: dict[str, Any] | None = None, - assets: dict[str, Asset] | None = None, - ): - super().__init__(stac_extensions or []) - - self.id = id - self.geometry = geometry - self.bbox = bbox - self.properties = properties - if extra_fields is None: - self.extra_fields = {} - else: - self.extra_fields = extra_fields - - self.assets: dict[str, Asset] = {} - - self.datetime: Datetime | None = None - if start_datetime: - properties["start_datetime"] = datetime_to_str(start_datetime) - if end_datetime: - properties["end_datetime"] = datetime_to_str(end_datetime) - if datetime is None: - if "start_datetime" not in properties or "end_datetime" not in properties: - raise STACError( - "Invalid Item: If datetime is None, " - "a start_datetime and end_datetime " - "must be supplied." - ) - self.datetime = None - else: - self.datetime = datetime - - if href is not None: - self.set_self_href(href) - - self.collection_id: str | None = None - if collection is None: - self.collection = None - else: - if isinstance(collection, Collection): - self.set_collection(collection) - else: - self.collection_id = collection - - self.assets = {} - if assets is not None: - for k, asset in assets.items(): - self.add_asset(k, asset) - - def __repr__(self) -> str: - return f"" - - def __getstate__(self) -> dict[str, Any]: - """Ensure that pystac does not encode too much information when pickling""" - d = self.__dict__.copy() - - d["links"] = [ - ( - link.to_dict(transform_href=False) - if link.get_href(transform_href=False) - else link - ) - for link in d["links"] - ] - - return d - - def __setstate__(self, state: dict[str, Any]) -> None: - """Ensure that pystac knows how to decode the pickled object""" - d = state.copy() - - d["links"] = [ - Link.from_dict(link).set_owner(self) if isinstance(link, dict) else link - for link in d["links"] - ] - - self.__dict__ = d - - def set_self_href(self, href: str | None) -> None: - """Sets the absolute HREF that is represented by the ``rel == 'self'`` - :class:`~pystac.Link`. - - Changing the self HREF of the item will ensure that all asset HREFs - remain valid. If asset HREFs are relative, the HREFs will change - to point to the same location based on the new item self HREF, - either by making them relative to the new location or making them - absolute links if the new location does not share the same protocol - as the old location. - - Args: - href : The absolute HREF of this object. If the given HREF - is not absolute, it will be transformed to an absolute - HREF based on the current working directory. If this is None - the call will clear the self HREF link. - """ - prev_href = self.get_self_href() - super().set_self_href(href) - new_href = self.get_self_href() # May have been made absolute. - - if prev_href is not None and new_href is not None: - # Make sure relative asset links remain valid. - for asset in self.assets.values(): - asset_href = asset.href - if not is_absolute_href(asset_href): - abs_href = make_absolute_href(asset_href, prev_href) - new_relative_href = make_relative_href(abs_href, new_href) - asset.href = new_relative_href - - def get_datetime(self, asset: Asset | None = None) -> Datetime | None: - """Gets an Item or an Asset datetime. - - If an Asset is supplied and the Item property exists on the Asset, - returns the Asset's value. Otherwise returns the Item's value. - - Returns: - datetime or None - """ - if asset is None or "datetime" not in asset.extra_fields: - return self.datetime - else: - asset_dt = asset.extra_fields.get("datetime") - if asset_dt is None: - return None - else: - return str_to_datetime(asset_dt) - - def set_datetime(self, datetime: Datetime, asset: Asset | None = None) -> None: - """Set an Item or an Asset datetime. - - If an Asset is supplied, sets the property on the Asset. - Otherwise sets the Item's value. - """ - if asset is None: - self.datetime = datetime - else: - asset.extra_fields["datetime"] = datetime_to_str(datetime) - - def set_collection(self, collection: Collection | None) -> Item: - """Set the collection of this item. - - This method will replace any existing Collection link and attribute for - this item. - - Args: - collection : The collection to set as this - item's collection. If None, will clear the collection. - - Returns: - Item: self - """ - self.remove_links(pystac.RelType.COLLECTION) - self.collection_id = None - if collection is not None: - self.add_link(Link.collection(collection)) - self.collection_id = collection.id - - return self - - def get_collection(self) -> Collection | None: - """Gets the collection of this item, if one exists. - - Returns: - Collection or None: If this item belongs to a collection, returns - a reference to the collection. Otherwise returns None. - """ - collection_link = self.get_single_link(pystac.RelType.COLLECTION) - if collection_link is None: - return None - else: - return cast( - Collection, collection_link.resolve_stac_object(self.get_root()).target - ) - - def add_derived_from(self, *items: Item | str) -> Item: - """Add one or more items that this is derived from. - - This method will add to any existing "derived_from" links. - - Args: - items : Items (or href of items) to add to derived_from links. - - Returns: - Item: self - """ - for item in items: - self.add_link(Link.derived_from(item)) - return self - - def remove_derived_from(self, item_id: str) -> None: - """Remove an item that this is derived from. - - This method will remove from existing "derived_from" links. - - Args: - item_id : ID of item to remove from derived_from links. - """ - new_links: list[pystac.Link] = [] - - for link in self.links: - if link.rel != pystac.RelType.DERIVED_FROM: - new_links.append(link) - else: - try: - item = cast(Item, link.resolve_stac_object().target) - except Exception as e: - raise pystac.STACError( - "Link failed to resolve. Use remove_links instead." - ) from e - if item.id != item_id: - new_links.append(link) - self.links = new_links - - def get_derived_from(self) -> list[Item]: - """Get the items that this is derived from. - - Returns: - List[Item]: Returns a reference to the derived_from items. - """ - links = self.get_links(pystac.RelType.DERIVED_FROM) - try: - return [cast(Item, link.resolve_stac_object().target) for link in links] - except Exception as e: - raise pystac.STACError( - "Link failed to resolve. Use get_links instead." - ) from e - - def to_dict( - self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> dict[str, Any]: - links = self.links - if not include_self_link: - links = [x for x in links if x.rel != pystac.RelType.SELF] - - assets = {k: v.to_dict() for k, v in self.assets.items()} - - if self.datetime is not None: - self.properties["datetime"] = datetime_to_str(self.datetime) - else: - self.properties["datetime"] = None - - d: dict[str, Any] = { - "type": "Feature", - "stac_version": pystac.get_stac_version(), - "stac_extensions": self.stac_extensions if self.stac_extensions else [], - "id": self.id, - "geometry": self.geometry, - "bbox": self.bbox if self.bbox is not None else [], - "properties": self.properties, - "links": [link.to_dict(transform_href=transform_hrefs) for link in links], - "assets": assets, - } - - if self.collection_id: - d["collection"] = self.collection_id - - for key in self.extra_fields: - d[key] = self.extra_fields[key] - - # This field is prohibited if there's no geometry - if not self.geometry: - d.pop("bbox") - - return d - - def clone(self) -> Item: - cls = self.__class__ - clone = cls( - id=self.id, - geometry=deepcopy(self.geometry), - bbox=copy(self.bbox), - datetime=copy(self.datetime), - properties=deepcopy(self.properties), - stac_extensions=deepcopy(self.stac_extensions), - collection=self.collection_id, - assets={k: asset.clone() for k, asset in self.assets.items()}, - extra_fields=deepcopy(self.extra_fields), - ) - for link in self.links: - clone.add_link(link.clone()) - - return clone - - def _object_links(self) -> list[str | pystac.RelType]: - return [ - pystac.RelType.COLLECTION, - *pystac.EXTENSION_HOOKS.get_extended_object_links(self), - ] - - @classmethod - def from_dict( - cls: type[T], - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> T: - import warnings - - from pystac.extensions.version import ItemVersionExtension - - if preserve_dict: - d = deepcopy(d) - - if migrate: - info = identify_stac_object(d) - d = migrate_to_latest(d, info) - - if not cls.matches_object_type(d): - raise pystac.STACTypeError(d, cls) - - # some fields are passed through to __init__ - pass_through_fields = [ - "id", - "geometry", - "bbox", - "stac_extensions", - "collection", - ] - - # some fields need some parsing or special handling - parse_fields = ["links", "assets", "properties"] - links = d.get("links", []) - assets = d.get("assets", {}) - properties = d.get("properties", {}) - datetime = properties.get("datetime") - if datetime is not None: - datetime = str_to_datetime(datetime) - - # some fields are excluded from __init__ entirely - exclude_fields = ["type", "stac_version"] - - # get all the fields that are not passed through, parsed, or excluded - extra_fields = { - k: v - for k, v in d.items() - if k not in [*pass_through_fields, *parse_fields, *exclude_fields] - } - - item = cls( - **{k: d.get(k) for k in pass_through_fields}, # type: ignore - datetime=datetime, - properties=properties, - extra_fields=extra_fields, - href=href, - assets={k: Asset.from_dict(v) for k, v in assets.items()}, - ) - - for link in links: - if href is None or link.get("rel", None) != RelType.SELF: - item.add_link(Link.from_dict(link)) - - if root: - item.set_root(root) - - try: - version = ItemVersionExtension.ext(item) - if version.deprecated: - warnings.warn( - f"The item '{item.id}' is deprecated.", - DeprecatedWarning, - ) - # Item asset deprecation checks pending version extension support - except ExtensionNotImplemented: - pass - - return item - - @property - def common_metadata(self) -> pystac.CommonMetadata: - """Access the item's common metadata fields as a - :class:`~pystac.CommonMetadata` object.""" - return pystac.CommonMetadata(self) - - def full_copy( - self, root: Catalog | None = None, parent: Catalog | None = None - ) -> Item: - return cast(Item, super().full_copy(root, parent)) - - @classmethod - def matches_object_type(cls, d: dict[str, Any]) -> bool: - for field in ("type", "stac_version"): - if field not in d: - raise pystac.STACTypeError(d, cls, f"'{field}' is missing.") - return identify_stac_object_type(d) == STACObjectType.ITEM - - @property - def __geo_interface__(self) -> dict[str, Any]: - """Returns this item as a dictionary. - - This just calls `to_dict` without self links or transforming any hrefs. - - https://gist.github.com/sgillies/2217756 - - Returns: - Dict[str, Any]: This item as a dictionary. - """ - return self.to_dict(include_self_link=False, transform_hrefs=False) - - @property - def ext(self) -> ItemExt: - """Accessor for extension classes on this item - - Example:: - - item.ext.proj.code = "EPSG:4326" - """ - from pystac.extensions.ext import ItemExt - - return ItemExt(stac_object=self) diff --git a/pystac/item_collection.py b/pystac/item_collection.py deleted file mode 100644 index 2e6f9e32c..000000000 --- a/pystac/item_collection.py +++ /dev/null @@ -1,257 +0,0 @@ -from __future__ import annotations - -from collections.abc import Collection, Iterable, Iterator -from typing import ( - Any, - TypeAlias, - TypeVar, -) - -import pystac -from pystac.errors import STACTypeError -from pystac.serialization.identify import identify_stac_object_type -from pystac.utils import HREF, is_absolute_href, make_absolute_href, make_posix_style - -ItemLike: TypeAlias = pystac.Item | dict[str, Any] - -#: Generalized version of :class:`ItemCollection` -C = TypeVar("C", bound="ItemCollection") - - -class ItemCollection(Collection[pystac.Item]): - """Implementation of a GeoJSON FeatureCollection whose features are all STAC - Items, - as defined by `STAC API - ItemCollection Fragment `_. - - All :class:`~pystac.Item` instances passed to the :class:`ItemCollection` instance - during instantiation are cloned and have their ``"root"`` URL cleared. Instances of - this class implement the abstract methods of :class:`typing.Collection` and can also - be added together (see below for examples using these methods). - - Any additional top-level fields in the FeatureCollection are retained in - :attr:`~pystac.ItemCollection.extra_fields` by the - :meth:`~pystac.ItemCollection.from_dict` and - :meth:`~pystac.ItemCollection.from_file` - methods and will be present in the serialized file - from :meth:`~pystac.ItemCollection.save_object`. - - Arguments: - - items : List of :class:`~pystac.Item` instances to include in the - :class:`ItemCollection`. - extra_fields : Dictionary of additional top-level fields included in the - :class:`ItemCollection`. - clone_items : Optional flag indicating whether :class:`~pystac.Item` instances - should be cloned before storing in the :class:`ItemCollection`. Setting to - ``False`` will result in faster instantiation, but changes made to - :class:`~pystac.Item` instances in the :class:`ItemCollection` will mutate - the original ``Item``. Defaults to ``True``. - - Examples: - - Loop over all items in the :class`ItemCollection` - - >>> item_collection: ItemCollection = ... - >>> for item in item_collection: - ... ... - - Get the number of :class:`~pystac.Item` instances in the - :class:`ItemCollection` - - >>> length: int = len(item_collection) - - Check if an :class:`~pystac.Item` is in the :class:`ItemCollection`. Note - that the ``clone_items`` argument must be ``False`` for this to return - ``True``, since equality of PySTAC objects is currently evaluated using default - object equality (i.e. ``item_1 is item_2``). - - >>> item: Item = ... - >>> item_collection = ItemCollection(items=[item], clone_items=False) - >>> assert item in item_collection - - Combine :class:`ItemCollection` instances - - >>> item_1: Item = ... - >>> item_2: Item = ... - >>> item_3: Item = ... - >>> item_collection_1 = ItemCollection(items=[item_1, item_2]) - >>> item_collection_2 = ItemCollection(items=[item_2, item_3]) - >>> combined = item_collection_1 + item_collection_2 - >>> assert len(combined) == 4 - # If an item is present in both ItemCollections it will occur twice - """ - - items: list[pystac.Item] - """List of :class:`pystac.Item` instances contained in this ``ItemCollection``.""" - - extra_fields: dict[str, Any] - """Dictionary of additional top-level fields for the GeoJSON - FeatureCollection.""" - - def __init__( - self, - items: Iterable[ItemLike], - extra_fields: dict[str, Any] | None = None, - clone_items: bool = True, - ): - def map_item(item_or_dict: ItemLike) -> pystac.Item: - # Converts dicts to pystac.Items and clones if necessary - if isinstance(item_or_dict, pystac.Item): - return item_or_dict.clone() if clone_items else item_or_dict - else: - return pystac.Item.from_dict(item_or_dict, preserve_dict=clone_items) - - self.items = list(map(map_item, items)) - self.extra_fields = extra_fields or {} - - def __getitem__(self, idx: int) -> pystac.Item: - return self.items[idx] - - def __iter__(self) -> Iterator[pystac.Item]: - return iter(self.items) - - def __len__(self) -> int: - return len(self.items) - - def __contains__(self, __x: object) -> bool: - return __x in self.items - - def __add__(self, other: object) -> ItemCollection: - if not isinstance(other, ItemCollection): - return NotImplemented - - combined = [*self.items, *other.items] - return ItemCollection(items=combined) - - def to_dict(self, transform_hrefs: bool = False) -> dict[str, Any]: - """Serializes an :class:`ItemCollection` instance to a dictionary. - - Args: - transform_hrefs: If True, transform the HREF of hierarchical links - of Items based on the type of catalog the Item belongs to (if any). - I.e. if the item belongs to a root catalog that is - RELATIVE_PUBLISHED or SELF_CONTAINED, - hierarchical link HREFs will be transformed to be relative to the - catalog root. This can be slow if the Items have root links that - have not yet been resolved. Defaults to False. - """ - return { - "type": "FeatureCollection", - "features": [ - item.to_dict(transform_hrefs=transform_hrefs) for item in self.items - ], - **self.extra_fields, - } - - def _repr_html_(self) -> str: - from html import escape - - from pystac.html.jinja_env import get_jinja_env - - jinja_env = get_jinja_env() - if jinja_env: - template = jinja_env.get_template("JSON.jinja2") - return str(template.render(dict=self.to_dict(), plain=escape(repr(self)))) - else: - return escape(repr(self)) - - def clone(self) -> ItemCollection: - """Creates a clone of this instance. This clone is a deep copy; all - :class:`~pystac.Item` instances are cloned and all additional top-level fields - are deep copied.""" - from copy import deepcopy - - return self.__class__( - items=[item.clone() for item in self.items], - extra_fields=deepcopy(self.extra_fields), - ) - - @classmethod - def from_dict( - cls: type[C], - d: dict[str, Any], - preserve_dict: bool = True, - root: pystac.Catalog | None = None, - ) -> C: - """Creates a :class:`ItemCollection` instance from a dictionary. - - Arguments: - d : The dictionary from which the :class:`ItemCollection` will be created - preserve_dict: If False, the dict parameter ``d`` may be modified - during this method call. Otherwise, the dict is not mutated. - Defaults to True, which results in a deepcopy of the - parameter. Set to False when possible to avoid the performance - hit of a deepcopy. - """ - if not cls.is_item_collection(d): - raise STACTypeError(d, cls) - - items = [ - pystac.Item.from_dict(item, preserve_dict=preserve_dict, root=root) - for item in d.get("features", []) - ] - extra_fields = {k: v for k, v in d.items() if k not in ("features", "type")} - - return cls(items=items, extra_fields=extra_fields) - - @classmethod - def from_file(cls: type[C], href: HREF, stac_io: pystac.StacIO | None = None) -> C: - """Reads a :class:`ItemCollection` from a JSON file. - - Arguments: - href : Path to the file. - stac_io : A :class:`~pystac.StacIO` instance to use for file I/O - """ - if stac_io is None: - stac_io = pystac.StacIO.default() - - href = make_posix_style(href) - if not is_absolute_href(href): - href = make_absolute_href(href) - - d = stac_io.read_json(href) - - return cls.from_dict(d, preserve_dict=False) - - def save_object( - self, - dest_href: str, - stac_io: pystac.StacIO | None = None, - ) -> None: - """Saves this instance to the ``dest_href`` location. - - Args: - dest_href : Location to which the file will be saved. - stac_io: Optional :class:`~pystac.StacIO` instance to use. If not provided, - will use the default instance. - """ - if stac_io is None: - stac_io = pystac.StacIO.default() - - stac_io.save_json(dest_href, self.to_dict()) - - @staticmethod - def is_item_collection(d: dict[str, Any]) -> bool: - """Checks if the given dictionary represents a valid :class:`ItemCollection`. - - Args: - d : Dictionary to check - """ - typ = d.get("type") - - # All ItemCollections are GeoJSON FeatureCollections - if typ != "FeatureCollection": - return False - - # If it is a FeatureCollection and has a "stac_version" field, then it is an - # ItemCollection. This will cover ItemCollections from STAC 0.9 to - # <1.0.0-beta.1, when ItemCollections were removed from the core STAC Spec - if "stac_version" in d: - return True - - # Prior to STAC 0.9 ItemCollections did not have a stac_version field and could - # only be identified by the fact that all of their 'features' are STAC Items. - return all( - identify_stac_object_type(feature) == pystac.STACObjectType.ITEM - for feature in d.get("features", []) - ) diff --git a/pystac/link.py b/pystac/link.py deleted file mode 100644 index eabbe2339..000000000 --- a/pystac/link.py +++ /dev/null @@ -1,528 +0,0 @@ -from __future__ import annotations - -import os -from typing import TYPE_CHECKING, Any, TypeVar - -import pystac -from pystac.errors import STACError -from pystac.utils import ( - HREF as HREF, -) -from pystac.utils import ( - is_absolute_href, - make_absolute_href, - make_posix_style, - make_relative_href, -) - -if TYPE_CHECKING: - from pystac.catalog import Catalog - from pystac.collection import Collection - from pystac.extensions.ext import LinkExt - from pystac.item import Item - from pystac.stac_object import STACObject - - PathLike = os.PathLike[str] - -else: - PathLike = os.PathLike - -#: Generalized version of :class:`Link` -L = TypeVar("L", bound="Link") - -#: Hierarchical links provide structure to STAC catalogs. -HIERARCHICAL_LINKS = [ - pystac.RelType.ROOT, - pystac.RelType.CHILD, - pystac.RelType.PARENT, - pystac.RelType.COLLECTION, - pystac.RelType.ITEM, - pystac.RelType.ITEMS, -] - - -class Link(PathLike): - """A link connects a :class:`~pystac.STACObject` to another entity. - - The target of a link can be either another STACObject, or - an HREF. When serialized, links always refer to the HREF of the target. - Links are lazily deserialized - this is, when you read in a link, it does - not automatically load in the STACObject that is the target - (if the link is pointing to a STACObject). When a user is crawling - through a catalog or when additional metadata is required, PySTAC uses the - :func:`~pystac.Link.resolve_stac_object` method - to load in and deserialize STACObjects. This mechanism is used within - the PySTAC codebase and normally does not need to be considered by the user - - ideally the lazy deserialization of STACObjects is transparent to clients of PySTAC. - - Args: - rel : The relation of the link (e.g. 'child', 'item'). Registered rel Types - are preferred. See :class:`~pystac.RelType` for common media types. - target : The target of the link. If the link is - unresolved, or the link is to something that is not a STACObject, - the target is an HREF. If resolved, the target is a STACObject. - media_type : Optional description of the media type. Registered Media Types - are preferred. See :class:`~pystac.MediaType` for common media types. - title : Optional title for this link. - extra_fields : Optional, additional fields for this link. This is used - by extensions as a way to serialize and deserialize properties on link - object JSON. - """ - - rel: str | pystac.RelType - """The relation of the link (e.g. 'child', 'item'). Registered rel Types are - preferred. See :class:`~pystac.RelType` for common media types.""" - - media_type: str | pystac.MediaType | None - """Optional description of the media type. Registered Media Types are preferred. - See :class:`~pystac.MediaType` for common media types.""" - - extra_fields: dict[str, Any] - """Optional, additional fields for this link. This is used by extensions as a - way to serialize and deserialize properties on link object JSON.""" - - owner: STACObject | None - """The owner of this link. The link will use its owner's root catalog - :class:`~pystac.cache.ResolvedObjectCache` to resolve objects, and - will create absolute HREFs from relative HREFs against the owner's self HREF.""" - - _target_href: str | None - _target_object: STACObject | None - _title: str | None - - def __init__( - self, - rel: str | pystac.RelType, - target: str | STACObject, - media_type: str | pystac.MediaType | None = None, - title: str | None = None, - extra_fields: dict[str, Any] | None = None, - ) -> None: - self.rel = rel - if isinstance(target, str): - if rel == pystac.RelType.SELF: - self._target_href = make_absolute_href(target) - else: - self._target_href = make_posix_style(target) - self._target_object = None - else: - self._target_href = None - self._target_object = target - self.media_type = media_type - self.title = title - self.extra_fields = extra_fields or {} - self.owner = None - - def set_owner(self, owner: STACObject | None) -> Link: - """Sets the owner of this link. - - Args: - owner: The owner of this link. Pass None to clear. - """ - self.owner = owner - return self - - @property - def title(self) -> str | None: - """Optional title for this link. If not provided during instantiation, this will - attempt to get the title from the STAC object that the link references.""" - if self._title is not None: - return self._title - if self._target_object is not None and isinstance( - self._target_object, pystac.Catalog - ): - return self._target_object.title - return None - - @title.setter - def title(self, v: str | None) -> None: - self._title = v - - @property - def href(self) -> str: - """Returns the HREF for this link. - - If the href is None, this will throw an exception. - Use get_href if there may not be an HREF. - """ - result = self.get_href() - if result is None: - raise ValueError(f"{self} does not have an HREF set.") - return result - - def get_href(self, transform_href: bool = True) -> str | None: - """Gets the HREF for this link. - - Args: - transform_href: If True, transform the HREF based on the type of - catalog the owner belongs to (if any). I.e. if the link owner - belongs to a root catalog that is RELATIVE_PUBLISHED or SELF_CONTAINED, - the HREF will be transformed to be relative to the catalog root - if this is a hierarchical link relation. - - Returns: - str: Returns this link's HREF. If there is an owner of the link and - the root catalog (if there is one) is of type RELATIVE_PUBLISHED, - then the HREF returned will be relative. - In all other cases, this method will return an absolute HREF. - """ - # get the self href - if self._target_object: - href = self._target_object.get_self_href() - else: - href = self._target_href - - if ( - transform_href - and href - and is_absolute_href(href) - and self.owner - and self.owner.get_root() - ): - root = self.owner.get_root() - rel_links = [ - *HIERARCHICAL_LINKS, - *pystac.EXTENSION_HOOKS.get_extended_object_links(self.owner), - ] - # if a hierarchical link with an owner and root, and relative catalog - if root and root.is_relative(): - if self.rel in rel_links or root.target_in_hierarchy(self.target): - owner_href = self.owner.get_self_href() - if owner_href is not None: - href = make_relative_href(href, owner_href) - - return href - - @property - def absolute_href(self) -> str: - """Returns the absolute HREF for this link. - - If the href is None, this will throw an exception. - Use get_absolute_href if there may not be an HREF set. - """ - result = self.get_absolute_href() - if result is None: - raise ValueError(f"{self} does not have an HREF set.") - return result - - def get_absolute_href(self) -> str | None: - """Gets the absolute href for this link, if possible. - - Returns: - str: Returns this link's HREF. It attempts to derive an absolute HREF - from this link; however, if the link is relative, has no owner, - and has an unresolved target, this will return a relative HREF. - """ - if self._target_object: - href = self._target_object.get_self_href() - else: - href = self._target_href - - if href is not None and self.owner is not None: - href = make_absolute_href(href, self.owner.get_self_href()) - - return href - - @property - def target(self) -> str | STACObject: - """The target of the link. If the link is unresolved, or the link is to - something that is not a STACObject, the target is an HREF. If resolved, the - target is a STACObject.""" - if self._target_object: - return self._target_object - elif self._target_href: - return self._target_href - else: - raise ValueError("No target defined for link.") - - @target.setter - def target(self, target: str | STACObject) -> None: - """Sets this link's target to a string or a STAC object.""" - if isinstance(target, str): - self._target_href = target - self._target_object = None - else: - self._target_href = None - self._target_object = target - - def get_target_str(self) -> str | None: - """Returns this link's target as a string. - - If a string href was provided, returns that. If not, tries to resolve - the self link of the target object. - """ - if self._target_href: - return self._target_href - elif self._target_object: - return self._target_object.get_self_href() - else: - return None - - def has_target_href(self) -> bool: - """Returns true if this link has a string href in its target information.""" - return self._target_href is not None - - def __fspath__(self) -> str: - return self.absolute_href - - def __repr__(self) -> str: - return f"" - - def _repr_html_(self) -> str: - from html import escape - - from pystac.html.jinja_env import get_jinja_env - - jinja_env = get_jinja_env() - if jinja_env: - template = jinja_env.get_template("JSON.jinja2") - return str( - template.render( - dict=self.to_dict(transform_href=False), plain=escape(repr(self)) - ) - ) - else: - return escape(repr(self)) - - def resolve_stac_object(self, root: Catalog | None = None) -> Link: - """Resolves a STAC object from the HREF of this link, if the link is not - already resolved. - - Args: - root : Optional root of the catalog for this link. - If provided, the root's resolved object cache is used to search for - previously resolved instances of the STAC object. - """ - if self._target_object: - pass - elif self._target_href: - target_href = self._target_href - - # If it's a relative link, base it off the parent. - if not is_absolute_href(target_href): - if self.owner is None: - raise pystac.STACError( - "Relative path {} encountered " - "without owner or start_href.".format(target_href) - ) - start_href = self.owner.get_self_href() - - if start_href is None: - raise pystac.STACError( - "Relative path {} encountered " - 'without owner "self" link set.'.format(target_href) - ) - - target_href = make_absolute_href(target_href, start_href) - obj = None - - stac_io: pystac.StacIO | None = None - - if root is not None: - obj = root._resolved_objects.get_by_href(target_href) - stac_io = root._stac_io - - if obj is None: - if stac_io is None: - if self.owner is not None: - if isinstance(self.owner, pystac.Catalog): - stac_io = self.owner._stac_io - elif self.rel != pystac.RelType.ROOT: - owner_root = self.owner.get_root() - if owner_root is not None: - stac_io = owner_root._stac_io - if stac_io is None: - stac_io = pystac.StacIO.default() - try: - obj = stac_io.read_stac_object(target_href, root=root) - except Exception as e: - raise STACError( - f"HREF: '{target_href}' does not resolve to a STAC object" - ) from e - obj.set_self_href(target_href) - if root is not None: - obj = root._resolved_objects.get_or_cache(obj) - obj.set_root(root) - self._target_object = obj - else: - raise ValueError("Cannot resolve STAC object without a target") - - if ( - self.owner - and self.rel in [pystac.RelType.CHILD, pystac.RelType.ITEM] - and isinstance(self.owner, pystac.Catalog) - ): - assert self._target_object - # Do nothing if the object wants to keep its parent - # https://github.com/stac-utils/pystac/issues/1116 - if self._target_object._allow_parent_to_override_href: - self._target_object.set_parent(self.owner) - - return self - - def is_resolved(self) -> bool: - """Determines if the link's target is a resolved STACObject. - - Returns: - bool: True if this link is resolved. - """ - return self._target_object is not None - - def is_hierarchical(self) -> bool: - """Returns true if this link's rel type is hierarchical. - - Hierarchical links are used to build relationships in STAC, e.g. - "parent", "child", "item", etc. For a complete list of hierarchical - relation types, see :py:const:`~pystac.link.HIERARCHICAL_LINKS`. - - Returns: - bool: True if the link's rel type is hierarchical. - """ - return self.rel in HIERARCHICAL_LINKS - - def to_dict(self, transform_href: bool = True) -> dict[str, Any]: - """Returns this link as a dictionary. - - Args: - transform_href : If ``True``, transform the HREF based on the type of - catalog the owner belongs to (if any). I.e. if the link owner - belongs to a root catalog that is RELATIVE_PUBLISHED or SELF_CONTAINED, - the HREF will be transformed to be relative to the catalog root - if this is a hierarchical link relation. - Returns: - dict : A serialization of the Link. - """ - - d: dict[str, Any] = { - "rel": str(self.rel), - "href": self.get_href(transform_href=transform_href), - } - - if self.media_type is not None: - d["type"] = str(self.media_type) - - if self.title is not None: - d["title"] = self.title - - for k, v in self.extra_fields.items(): - d[k] = v - - return d - - def clone(self) -> Link: - """Clones this link. - - This makes a copy of all link information, but does not clone a STACObject - if one is the target of this link. - - Returns: - Link: The cloned link. - """ - cls = self.__class__ - return cls( - rel=self.rel, - target=self.target, - media_type=self.media_type, - title=self.title, - ) - - @classmethod - def from_dict(cls: type[L], d: dict[str, Any]) -> L: - """Deserializes a :class:`Link` from a dict. - - Args: - d : The dict that represents the Link in JSON - - Returns: - Link: Link instance constructed from the dict. - """ - from copy import copy - - d = copy(d) - rel = d.pop("rel") - href = d.pop("href") - media_type = d.pop("type", None) - title = d.pop("title", None) - - extra_fields = None - if any(d): - extra_fields = d - - return cls( - rel=rel, - target=href, - media_type=media_type, - title=title, - extra_fields=extra_fields, - ) - - @classmethod - def root(cls: type[L], c: Catalog) -> L: - """Creates a link to a root Catalog or Collection.""" - return cls(pystac.RelType.ROOT, c, media_type=pystac.MediaType.JSON) - - @classmethod - def parent(cls: type[L], c: Catalog) -> L: - """Creates a link to a parent Catalog or Collection.""" - return cls(pystac.RelType.PARENT, c, media_type=pystac.MediaType.JSON) - - @classmethod - def collection(cls: type[L], c: Collection) -> L: - """Creates a link to a Collection.""" - return cls(pystac.RelType.COLLECTION, c, media_type=pystac.MediaType.JSON) - - @classmethod - def self_href(cls: type[L], href: HREF) -> L: - """Creates a self link to a file's location.""" - href_str = str(os.fspath(href)) - return cls(pystac.RelType.SELF, href_str, media_type=pystac.MediaType.JSON) - - @classmethod - def child(cls: type[L], c: Catalog, title: str | None = None) -> L: - """Creates a link to a child Catalog or Collection.""" - return cls( - pystac.RelType.CHILD, c, title=title, media_type=pystac.MediaType.JSON - ) - - @classmethod - def item(cls: type[L], item: Item, title: str | None = None) -> L: - """Creates a link to an Item.""" - return cls( - pystac.RelType.ITEM, item, title=title, media_type=pystac.MediaType.GEOJSON - ) - - @classmethod - def derived_from(cls: type[L], item: Item | str, title: str | None = None) -> L: - """Creates a link to a derived_from Item.""" - return cls( - pystac.RelType.DERIVED_FROM, - item, - title=title, - media_type=pystac.MediaType.JSON, - ) - - @classmethod - def canonical( - cls: type[L], - item_or_collection: Item | Collection, - title: str | None = None, - ) -> L: - """Creates a canonical link to an Item or Collection.""" - return cls( - pystac.RelType.CANONICAL, - item_or_collection, - title=title, - media_type=pystac.MediaType.JSON, - ) - - @property - def ext(self) -> LinkExt: - """Accessor for extension classes on this link - - Example:: - - link.ext.file.size = 8675309 - """ - from pystac.extensions.ext import LinkExt - - return LinkExt(stac_object=self) diff --git a/pystac/stac_object.py b/pystac/stac_object.py deleted file mode 100644 index b80ebdd21..000000000 --- a/pystac/stac_object.py +++ /dev/null @@ -1,701 +0,0 @@ -from __future__ import annotations - -import warnings -from abc import ABC, abstractmethod -from collections.abc import Callable, Iterable -from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, cast - -import pystac -from pystac import STACError -from pystac.link import Link -from pystac.utils import ( - HREF, - StringEnum, - is_absolute_href, - make_absolute_href, - make_posix_style, -) - -if TYPE_CHECKING: - from pystac.catalog import Catalog - -S = TypeVar("S", bound="STACObject") - -OptionalMediaType: TypeAlias = str | pystac.MediaType | None - - -class STACObjectType(StringEnum): - CATALOG = "Catalog" - COLLECTION = "Collection" - ITEM = "Feature" - - -class STACObject(ABC): - """A base class for other PySTAC classes that contains a variety of useful - methods for dealing with links, copying objects, accessing extensions, and reading - and writing files. You shouldn't use STACObject directly, but instead access this - functionality through the implementing classes. - """ - - id: str - """The ID of the STAC Object.""" - - links: list[Link] - """A list of :class:`~pystac.Link` objects representing all links associated with - this STAC Object.""" - - stac_extensions: list[str] - """A list of schema URIs for STAC Extensions implemented by this STAC Object.""" - - STAC_OBJECT_TYPE: STACObjectType - - _allow_parent_to_override_href: bool = True - """Private attribute for whether parent objects should override on normalization""" - - def __init__(self, stac_extensions: list[str]) -> None: - self.links = [] - self.stac_extensions = stac_extensions - - def validate( - self, - validator: pystac.validation.stac_validator.STACValidator | None = None, - ) -> list[Any]: - """Validate this STACObject. - - Returns a list of validation results, which depends on the validation - implementation. For JSON Schema validation (default validator), this - will be a list of schema URIs that were used during validation. - - Args: - validator : A custom validator to use for validation of the object. - If omitted, the default validator from - :class:`~pystac.validation.RegisteredValidator` - will be used instead. - Raises: - STACValidationError - """ - import pystac.validation - - return pystac.validation.validate(self, validator=validator) - - def add_link(self, link: Link) -> None: - """Add a link to this object's set of links. - - Args: - link : The link to add. - """ - link.set_owner(self) - self.links.append(link) - - def add_links(self, links: list[Link]) -> None: - """Add links to this object's set of links. - - Args: - links : The links to add. - """ - - for link in links: - self.add_link(link) - - def remove_links(self, rel: str | pystac.RelType) -> None: - """Remove links to this object's set of links that match the given ``rel``. - - Args: - rel : The :class:`~pystac.Link` ``rel`` to match on. - """ - - self.links = [link for link in self.links if link.rel != rel] - - def remove_hierarchical_links(self, add_canonical: bool = False) -> list[Link]: - """Removes all hierarchical links from this object. - - See :py:const:`pystac.link.HIERARCHICAL_LINKS` for a list of all - hierarchical links. If the object has a ``self`` href and - ``add_canonical`` is True, a link with ``rel="canonical"`` is added. - - Args: - add_canonical : If true, and this item has a ``self`` href, that - href is used to build a ``canonical`` link. - - Returns: - List[Link]: All removed links - """ - keep = list() - self_href = self.get_self_href() - if add_canonical and self_href is not None: - keep.append( - Link("canonical", self_href, media_type=pystac.MediaType.GEOJSON) - ) - remove = list() - for link in self.links: - if link.is_hierarchical(): - remove.append(link) - else: - keep.append(link) - self.links = keep - return remove - - def target_in_hierarchy(self, target: str | STACObject) -> bool: - """Determine if target is somewhere in the hierarchical link tree of - a STACObject. - - Args: - target: A string or STACObject to search for - - Returns: - bool: Returns True if the target was found in the hierarchical link tree - for the current STACObject - """ - - def traverse(obj: str | STACObject, visited: set[str | STACObject]) -> bool: - if obj == target: - return True - if isinstance(obj, str): - return False - - new_targets = [ - link.target - for link in obj.links - if link.is_hierarchical() and link.target not in visited - ] - if target in new_targets: - return True - - for subtree in new_targets: - visited.add(subtree) - if traverse(subtree, visited): - return True - - return False - - return traverse(self, {self}) - - def get_single_link( - self, - rel: str | pystac.RelType | None = None, - media_type: OptionalMediaType | Iterable[OptionalMediaType] = None, - ) -> Link | None: - """Get a single :class:`~pystac.Link` instance associated with this - object. - - Args: - rel : If set, filter links such that only those - matching this relationship are returned. - media_type: If set, filter the links such that only - those matching media_type are returned. media_type can - be a single value or a list of values. - - Returns: - :class:`~pystac.Link` | None: First link that matches ``rel`` - and/or ``media_type``, or else the first link associated with - this object. - """ - if rel is None and media_type is None: - return next(iter(self.links), None) - if media_type and isinstance(media_type, (str, pystac.MediaType)): - media_type = [media_type] - return next( - ( - link - for link in self.links - if (rel is None or link.rel == rel) - and (media_type is None or link.media_type in media_type) - ), - None, - ) - - def get_links( - self, - rel: str | pystac.RelType | None = None, - media_type: OptionalMediaType | Iterable[OptionalMediaType] = None, - ) -> list[Link]: - """Gets the :class:`~pystac.Link` instances associated with this object. - - Args: - rel : If set, filter links such that only those - matching this relationship are returned. - media_type: If set, filter the links such that only - those matching media_type are returned. media_type can - be a single value or a list of values. - - Returns: - List[:class:`~pystac.Link`]: A list of links that match ``rel`` and/ - or ``media_type`` if set, or else all links associated with this - object. - """ - if rel is None and media_type is None: - return self.links - if media_type and isinstance(media_type, (str, pystac.MediaType)): - media_type = [media_type] - return [ - link - for link in self.links - if (rel is None or link.rel == rel) - and (media_type is None or link.media_type in media_type) - ] - - def clear_links(self, rel: str | pystac.RelType | None = None) -> None: - """Clears all :class:`~pystac.Link` instances associated with this object. - - Args: - rel : If set, only clear links that match this relationship. - """ - if rel is not None: - self.links = [link for link in self.links if link.rel != rel] - else: - self.links = [] - - def get_root_link(self) -> Link | None: - """Get the :class:`~pystac.Link` representing - the root for this object. - - Returns: - :class:`~pystac.Link` or None: The root link for this object, - or ``None`` if no root link is set. - """ - return self.get_single_link( - rel=pystac.RelType.ROOT, - media_type=pystac.media_type.STAC_JSON, - ) - - @property - def self_href(self) -> str: - """Gets the absolute HREF that is represented by the ``rel == 'self'`` - :class:`~pystac.Link`. - - Raises: - ValueError: If the self_href is not set, this method will throw - a ValueError. Use get_self_href if there may not be an HREF set. - """ - result = self.get_self_href() - if result is None: - raise ValueError(f"{self} does not have a self_href set.") - return result - - def get_self_href(self) -> str | None: - """Gets the absolute HREF that is represented by the ``rel == 'self'`` - :class:`~pystac.Link`. - - Returns: - str or None: The absolute HREF of this object, or ``None`` if - there is no self link defined. - - Note: - A self link can exist for objects, even if the link is not read or - written to the JSON-serialized version of the object. Any object - read from :func:`STACObject.from_file ` will - have the HREF the file was read from set as it's self HREF. All self - links have absolute (as opposed to relative) HREFs. - """ - self_link = self.get_single_link(pystac.RelType.SELF) - if self_link and self_link.has_target_href(): - return self_link.get_target_str() - else: - return None - - def set_self_href(self, href: str | None) -> None: - """Sets the absolute HREF that is represented by the ``rel == 'self'`` - :class:`~pystac.Link`. - - Args: - href : The absolute HREF of this object. If the given HREF - is not absolute, it will be transformed to an absolute - HREF based on the current working directory. If this is None - the call will clear the self HREF link. - """ - root_link = self.get_root_link() - if root_link is not None and root_link.is_resolved(): - cast(pystac.Catalog, root_link.target)._resolved_objects.remove(self) - - self.remove_links(pystac.RelType.SELF) - if href is not None: - self.add_link(Link.self_href(href)) - - if root_link is not None and root_link.is_resolved(): - cast(pystac.Catalog, root_link.target)._resolved_objects.cache(self) - - def get_root(self) -> Catalog | None: - """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to - the root for this object. The root is represented by a - :class:`~pystac.Link` with ``rel == 'root'``. - - Returns: - Catalog, Collection, or None: - The root object for this object, or ``None`` if no root link is set. - """ - root_link = self.get_root_link() - if root_link: - if not root_link.is_resolved(): - root_link.resolve_stac_object() - # Use set_root, so Catalogs can merge ResolvedObjectCache instances. - self.set_root(cast(pystac.Catalog, root_link.target)) - return cast(pystac.Catalog, root_link.target) - else: - return None - - def set_root(self, root: Catalog | None) -> None: - """Sets the root :class:`~pystac.Catalog` or :class:`~pystac.Collection` - for this object. - - Args: - root : The root - object to set. Passing in None will clear the root. - """ - root_link_index = next( - iter( - [ - i - for i, link in enumerate(self.links) - if link.rel == pystac.RelType.ROOT - ] - ), - None, - ) - - # Remove from old root resolution cache - if root_link_index is not None: - root_link = self.links[root_link_index] - if root_link.is_resolved(): - cast(pystac.Catalog, root_link.target)._resolved_objects.remove(self) - - if root is None: - self.remove_links(pystac.RelType.ROOT) - else: - new_root_link = Link.root(root) - if root_link_index is not None: - self.links[root_link_index] = new_root_link - new_root_link.set_owner(self) - else: - self.add_link(new_root_link) - root._resolved_objects.cache(self) - - def get_parent(self) -> Catalog | None: - """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to - the parent for this object. The root is represented by a - :class:`~pystac.Link` with ``rel == 'parent'``. - - Returns: - Catalog, Collection, or None: - The parent object for this object, - or ``None`` if no root link is set. - """ - parent_link = self.get_single_link(pystac.RelType.PARENT) - if parent_link: - return cast(pystac.Catalog, parent_link.resolve_stac_object().target) - else: - return None - - def set_parent(self, parent: Catalog | None) -> None: - """Sets the parent :class:`~pystac.Catalog` or :class:`~pystac.Collection` - for this object. - - Args: - parent : The parent - object to set. Passing in None will clear the parent. - """ - - self.remove_links(pystac.RelType.PARENT) - if parent is not None: - self.add_link(Link.parent(parent)) - - def get_stac_objects( - self, - rel: str | pystac.RelType, - typ: type[STACObject] | None = None, - modify_links: Callable[[list[Link]], list[Link]] | None = None, - ) -> Iterable[STACObject]: - """Gets the :class:`STACObject` instances that are linked to - by links with their ``rel`` property matching the passed in argument. - - Args: - rel : The relation to match each :class:`~pystac.Link`'s - ``rel`` property against. - typ : If not ``None``, objects will only be yielded if they are instances of - ``typ``. - modify_links : A function that modifies the list of links before they are - iterated over. For instance, this option can be used to sort the list - so that links matching a particular pattern are earlier in the iterator. - - Returns: - Iterable[STACObject]: A possibly empty iterable of STACObjects that are - connected to this object through links with the given ``rel`` and are of - type ``typ`` (if given). - """ - links = self.links[:] - if modify_links: - links = modify_links(links) - - has_next_link = False - for i in range(0, len(links)): - link = links[i] - if link.rel == rel: - link.resolve_stac_object(root=self.get_root()) - if typ is None or isinstance(link.target, typ): - yield cast(STACObject, link.target) - if link.rel == "next": - has_next_link = True - if has_next_link: - warnings.warn( - "This STAC object has a 'next' link, but pystac does not support " - "pagination. If you need to paginate through responses from a STAC " - "API, use pystac-client: https://github.com/stac-utils/pystac-client" - ) - - def save_object( - self, - include_self_link: bool = True, - dest_href: str | None = None, - stac_io: pystac.StacIO | None = None, - ) -> None: - """Saves this :class:`STACObject` to it's 'self' HREF. - - Args: - include_self_link : If this is true, include the 'self' link with - this object. Otherwise, leave out the self link. - dest_href : Optional HREF to save the file to. If None, the object - will be saved to the object's self href. - stac_io: Optional instance of StacIO to use. If not provided, will use the - instance set on the object's root if available, otherwise will use the - default instance. - - - Raises: - STACError: If no self href is set, this error will be raised. - - Note: - When to include a self link is described in the :stac-spec:`Use of Links - section of the STAC best practices document - ` - """ - if stac_io is None: - root = self.get_root() - if root is not None: - root_stac_io = root._stac_io - if root_stac_io is not None: - stac_io = root_stac_io - - if stac_io is None: - stac_io = pystac.StacIO.default() - - if dest_href is None: - self_href = self.get_self_href() - if self_href is None: - raise STACError( - "Self HREF must be set before saving without an explicit dest_href." - ) - dest_href = self_href - - stac_io.save_json(dest_href, self.to_dict(include_self_link=include_self_link)) - - def full_copy( - self, - root: Catalog | None = None, - parent: Catalog | None = None, - ) -> STACObject: - """Create a full copy of this STAC object and any STAC objects linked to by - this object. - - Args: - root : Optional root to set as the root of the copied object, - and any other copies that are contained by this object. - parent : Optional parent to set as the parent of the copy - of this object. - - Returns: - STACObject: A full copy of this object, as well as any objects this object - links to. - """ - clone = self.clone() - - if root is None and isinstance(clone, pystac.Catalog): - root = clone - - # Set the root of the STAC Object using the base class, - # avoiding child class overrides - # extra logic which can be incompatible with the full copy. - STACObject.set_root(clone, cast(pystac.Catalog, root)) - - if parent: - clone.set_parent(parent) - - link_rels = set(self._object_links()) - for link in clone.links: - if link.rel in link_rels: - link.resolve_stac_object() - target = cast(STACObject, link.target) - if root is not None and target in root._resolved_objects: - cached_target = root._resolved_objects.get(target) - assert cached_target is not None - target = cached_target - else: - target_parent = None - if link.rel in [ - pystac.RelType.CHILD, - pystac.RelType.ITEM, - ] and isinstance(clone, pystac.Catalog): - target_parent = clone - copied_target = target.full_copy(root=root, parent=target_parent) - if root is not None: - root._resolved_objects.cache(copied_target) - target = copied_target - if link.rel in [pystac.RelType.CHILD, pystac.RelType.ITEM]: - target.set_root(root) - if isinstance(clone, pystac.Catalog): - target.set_parent(clone) - link.target = target - - return clone - - def resolve_links(self) -> None: - """Ensure all STACObjects linked to by this STACObject are - resolved. This is important for operations such as changing - HREFs. - - This method mutates the entire catalog tree. - """ - link_rels = set(self._object_links()) | { - pystac.RelType.ROOT, - pystac.RelType.PARENT, - } - - for link in self.links: - if link.rel in link_rels: - if not link.is_resolved(): - link.resolve_stac_object(root=self.get_root()) - - @abstractmethod - def _object_links(self) -> list[str]: - """Inherited classes return a list of link 'rel' types that represent - STACObjects linked to by this object (not including root, parent or self). - This can include optional relations (which may not be present). - """ - raise NotImplementedError - - @abstractmethod - def to_dict( - self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> dict[str, Any]: - """Returns this object as a dictionary. - - Args: - include_self_link : If True, the dict will contain a self link - to this object. If False, the self link will be omitted. - transform_hrefs: If True, transform the HREF of hierarchical links - based on the type of catalog this object belongs to (if any). - I.e. if this object belongs to a root catalog that is - RELATIVE_PUBLISHED or SELF_CONTAINED, - hierarchical link HREFs will be transformed to be relative to the - catalog root. - - dict: A serialization of the object. - """ - raise NotImplementedError - - def _repr_html_(self) -> str: - from html import escape - - from pystac.html.jinja_env import get_jinja_env - - jinja_env = get_jinja_env() - if jinja_env: - template = jinja_env.get_template("JSON.jinja2") - return str( - template.render( - dict=self.to_dict(transform_hrefs=False), plain=escape(repr(self)) - ) - ) - else: - return escape(repr(self)) - - @abstractmethod - def clone(self) -> STACObject: - """Clones this object. - - Cloning an object will make a copy of all properties and links of the object; - however, it will not make copies of the targets of links (i.e. it is not a - deep copy). To copy a STACObject fully, with all linked elements also copied, - use :func:`~pystac.STACObject.full_copy`. - - Returns: - STACObject: The clone of this object. - """ - raise NotImplementedError - - @classmethod - def from_file(cls: type[S], href: HREF, stac_io: pystac.StacIO | None = None) -> S: - """Reads a STACObject implementation from a file. - - Args: - href : The HREF to read the object from. - stac_io: Optional instance of StacIO to use. If not provided, will use the - default instance. - - Returns: - The specific STACObject implementation class that is represented - by the JSON read from the file located at HREF. - """ - if cls == STACObject: - return cast(S, pystac.read_file(href)) - - href = make_posix_style(href) - - if stac_io is None: - stac_io = pystac.StacIO.default() - - if not is_absolute_href(href): - href = make_absolute_href(href) - - d = stac_io.read_json(href) - o = cls.from_dict(d, href=href, migrate=True, preserve_dict=False) - - # If this is a root catalog, set the root to the catalog instance. - root_link = o.get_root_link() - if root_link is not None: - if not root_link.is_resolved(): - if root_link.get_absolute_href() == href: - o.set_root(cast(pystac.Catalog, o)) - return o - - @classmethod - @abstractmethod - def from_dict( - cls: type[S], - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> S: - """Parses this STACObject from the passed in dictionary. - - Args: - d : The dict to parse. - href : Optional href that is the file location of the object being - parsed. - root : Optional root catalog for this object. - If provided, the root of the returned STACObject will be set - to this parameter. - migrate: By default, STAC objects and extensions are migrated to - their latest supported version. Set this to False to disable - this behavior. - preserve_dict: If False, the dict parameter ``d`` may be modified - during this method call. Otherwise, the dict is not mutated. - Defaults to True, which results in a deepcopy of the - parameter. Set to False when possible to avoid the performance - hit of a deepcopy. - - Returns: - STACObject: The STACObject parsed from this dict. - """ - raise NotImplementedError - - @classmethod - @abstractmethod - def matches_object_type(cls, d: dict[str, Any]) -> bool: - """Returns a boolean indicating whether the given dictionary represents a valid - instance of this :class:`~pystac.STACObject` subclass. - - Args: - d : A dictionary to identify - """ - raise NotImplementedError diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py new file mode 100644 index 000000000..ac495c907 --- /dev/null +++ b/src/pystac/__init__.py @@ -0,0 +1,85 @@ +from typing import Any + +from typing_extensions import deprecated + +from .asset import Asset +from .catalog import Catalog +from .collection import ( + Collection, + Extent, + Provider, + ProviderRole, + SpatialExtent, + TemporalExtent, +) +from .common_metadata import CommonMetadata +from .constants import DEFAULT_STAC_VERSION +from .deserialize import from_dict +from .errors import ( + ExtensionNotImplemented, + ExtensionTypeError, + STACError, + STACTypeError, + STACValidationError, +) +from .io import read_file +from .item import Item +from .item_assets import ItemAssetDefinition +from .item_collection import ItemCollection +from .link import HIERARCHICAL_LINKS, Link +from .media_type import MediaType +from .stac_object import STACObject + + +def __getattr__(name: str) -> Any: + match name: + case "StacIO": + from .stac_io import StacIO + + return StacIO + + case "STACObjectType": + from .stac_object import STACObjectType + + return STACObjectType + case "CatalogType": + from .catalog import CatalogType + + return CatalogType + case "get_stac_version": + + @deprecated("get_stac_version is deprecated") + def get_stac_version() -> str: + return DEFAULT_STAC_VERSION + + return get_stac_version # pyright: ignore[reportDeprecated] + case _: + raise AttributeError(f"module {__name__} has no attribute {name}") + + +__all__ = [ + "Asset", + "Catalog", + "Collection", + "CommonMetadata", + "ExtensionNotImplemented", + "ExtensionTypeError", + "Extent", + "HIERARCHICAL_LINKS", + "DEFAULT_STAC_VERSION", + "Item", + "ItemAssetDefinition", + "ItemCollection", + "Link", + "MediaType", + "Provider", + "from_dict", + "ProviderRole", + "STACError", + "STACObject", + "STACTypeError", + "STACValidationError", + "SpatialExtent", + "TemporalExtent", + "read_file", +] diff --git a/src/pystac/asset.py b/src/pystac/asset.py new file mode 100644 index 000000000..d12f858d7 --- /dev/null +++ b/src/pystac/asset.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import copy +from typing import TYPE_CHECKING, Any, Protocol, override + +from typing_extensions import deprecated + +from .media_type import MediaType +from .utils import is_absolute_href, make_absolute_href, make_relative_href +from .writer import Writer + +if TYPE_CHECKING: + from .stac_object import STACObject + + +class ItemAsset: + def __init__( + self, + title: str | None = None, + description: str | None = None, + type: str | None = None, + roles: list[str] | None = None, + **kwargs: Any, + ): + self.title: str | None = title + self.description: str | None = description + self.type: str | None = type + self.roles: list[str] | None = roles + self.extra_fields: dict[str, Any] = kwargs + + @classmethod + def try_from[T: ItemAsset](cls: type[T], data: T | dict[str, Any]) -> T: + if isinstance(data, cls): + return data + elif isinstance(data, dict): + return cls.from_dict(data) + else: + raise ValueError(f"Invalid data type for item asset: {type(data)}") + + @classmethod + def from_dict[T: ItemAsset](cls: type[T], data: dict[str, Any]) -> T: + return cls(**data) + + def clone[T: ItemAsset](self: T) -> T: + return self.from_dict(self.to_dict()) + + def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) + if self.title is not None: + data["title"] = self.title + if self.description is not None: + data["description"] = self.description + if self.type is not None: + data["type"] = self.type + if self.roles is not None: + data["roles"] = self.roles + return data + + +class Asset(ItemAsset): + def __init__( + self, + href: str, + title: str | None = None, + description: str | None = None, + type: str | None = None, + roles: list[str] | None = None, + **kwargs: Any, + ): + super().__init__( + title=title, description=description, type=type, roles=roles, **kwargs + ) + self.href: str = href + + self._owner: STACObject | None = None + + @staticmethod + def update_hrefs( + assets: dict[str, Asset], start_href: str | None, end_href: str | None + ) -> None: + if start_href and end_href: + for asset in assets.values(): + if not is_absolute_href(asset.href): + asset.href = make_relative_href( + make_absolute_href(asset.href, start_href), end_href + ) + + @property + def owner(self) -> STACObject | None: + return self._owner + + def set_owner(self, owner: STACObject | None) -> None: + self._owner = owner + + def get_absolute_href(self) -> str | None: + if is_absolute_href(self.href): + return self.href + elif self._owner and (owner_href := self._owner.get_self_href()): + return make_absolute_href(self.href, owner_href, False) + else: + return None + + @override + def to_dict(self) -> dict[str, Any]: + data = super().to_dict() + data["href"] = self.href + return data + + +class Assets(Protocol): + assets: dict[str, Asset] + writer: Writer + + def get_self_href(self) -> str | None: ... + + def get_assets( + self, media_type: MediaType | None = None, role: str | None = None + ) -> dict[str, Asset]: + return { + key: copy.deepcopy(asset) + for (key, asset) in self.assets.items() + if (not media_type or asset.type == media_type) + and (not role or (asset.roles and role in asset.roles)) + } + + @deprecated( + "add_asset is deprecated, just add the asset directly to the assets dictionary" + ) + def add_asset(self, key: str, asset: Asset) -> None: + self.assets[key] = asset + + def make_asset_hrefs_relative(self) -> None: + if self.get_self_href(): + # TODO actually implement + pass + else: + raise ValueError( + "Cannot make asset hrefs relative, item does not have a self href" + ) + + # TODO do we want to deprecate this? I think so... + def delete_asset(self, key: str) -> None: + asset = self.assets[key] + try: + self.writer.delete(make_absolute_href(asset.href, self.get_self_href())) + except Exception as e: + raise ValueError(f"Cannot delete file {asset.href}: {e}") + del self.assets[key] diff --git a/pystac/cache.py b/src/pystac/cache.py similarity index 99% rename from pystac/cache.py rename to src/pystac/cache.py index 4a923332f..aa369b9a3 100644 --- a/pystac/cache.py +++ b/src/pystac/cache.py @@ -1,5 +1,12 @@ from __future__ import annotations +import warnings + +warnings.warn( + "pystac.cache is deprecated", + DeprecationWarning, +) + from collections import ChainMap from copy import copy from typing import TYPE_CHECKING, Any, cast diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py new file mode 100644 index 000000000..63d18b9f1 --- /dev/null +++ b/src/pystac/catalog.py @@ -0,0 +1,114 @@ +from __future__ import annotations + +from enum import StrEnum +from typing import Any, ClassVar, override + +from typing_extensions import deprecated + +from .constants import DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE +from .link import Link +from .stac_object import Container + + +class Catalog(Container): + type: ClassVar[STAC_OBJECT_TYPE] = "Catalog" + + def __init__( + self, + id: str, + description: str, + type: str = "Catalog", + stac_version: str = DEFAULT_STAC_VERSION, + stac_extensions: list[str] | None = None, + links: list[Link] | list[dict[str, Any]] | None = None, + **kwargs: Any, + ): + super().__init__( + type=type, + id=id, + stac_version=stac_version, + stac_extensions=stac_extensions, + links=links, + **kwargs, + ) + self.description: str = description + + @override + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Catalog: + return cls(**data) + + @override + def to_dict( + self, include_self_link: bool = True, transform_hrefs: bool | None = None + ) -> dict[str, Any]: + data = super().to_dict( + include_self_link=include_self_link, transform_hrefs=transform_hrefs + ) + data["description"] = self.description + return data + + +@deprecated("CatalogType is deprecated") +class CatalogType(StrEnum): + SELF_CONTAINED = "SELF_CONTAINED" + """A 'self-contained catalog' is one that is designed for portability. + Users may want to download an online catalog from and be able to use it on their + local computer, so all links need to be relative. + + See: + :stac-spec:`The best practices documentation on self-contained catalogs + ` + """ + + ABSOLUTE_PUBLISHED = "ABSOLUTE_PUBLISHED" + """ + Absolute Published Catalog is a catalog that uses absolute links for everything, + both in the links objects and in the asset hrefs. + + See: + :stac-spec:`The best practices documentation on published catalogs + ` + """ + + RELATIVE_PUBLISHED = "RELATIVE_PUBLISHED" + """ + Relative Published Catalog is a catalog that uses relative links for everything, but + includes an absolute self link at the root catalog, to identify its online location. + + See: + :stac-spec:`The best practices documentation on published catalogs + ` + """ + + @classmethod + def determine_type(cls, stac_json: dict[str, Any]) -> CatalogType | None: + """Determines the catalog type based on a STAC JSON dict. + + Only applies to Catalogs or Collections + + Args: + stac_json : The STAC JSON dict to determine the catalog type + + Returns: + Optional[CatalogType]: The catalog type of the catalog or collection. + Will return None if it cannot be determined. + """ + self_link = None + relative = False + for link in stac_json["links"]: + if link["rel"] == pystac.RelType.SELF: + self_link = link + else: + relative |= not is_absolute_href(link["href"]) + + if self_link: + if relative: + return cls.RELATIVE_PUBLISHED + else: + return cls.ABSOLUTE_PUBLISHED + else: + if relative: + return cls.SELF_CONTAINED + else: + return None diff --git a/src/pystac/collection.py b/src/pystac/collection.py new file mode 100644 index 000000000..89b535550 --- /dev/null +++ b/src/pystac/collection.py @@ -0,0 +1,264 @@ +from __future__ import annotations + +import copy +import datetime as dt +from enum import StrEnum +from typing import Any, ClassVar, TypedDict, cast, override + +from typing_extensions import deprecated + +from .asset import Asset, Assets, ItemAsset +from .constants import DEFAULT_LICENSE, DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE +from .link import Link +from .stac_object import Container +from .utils import datetime_to_str + +SpatialExtentBboxType = list[float | int] | list[list[float | int]] +TemporalExtentIntervalType = ( + list[dt.datetime | str | None] | list[list[dt.datetime | str | None]] +) + + +class SpatialExtentDict(TypedDict): + bbox: SpatialExtentBboxType + + +class TemporalExtentDict(TypedDict): + interval: TemporalExtentIntervalType + + +class Collection(Container, Assets): + type: ClassVar[STAC_OBJECT_TYPE] = "Collection" + + def __init__( + self, + id: str, + description: str, + extent: dict[str, Any] | Extent | None = None, + type: str = "Collection", + title: str | None = None, + keywords: list[str] | None = None, + license: str = DEFAULT_LICENSE, + providers: list[Provider | dict[str, Any]] | None = None, + summaries: dict[str, Any] | None = None, + assets: dict[str, Asset | dict[str, Any]] | None = None, + item_assets: dict[str, ItemAsset] | None = None, + stac_version: str = DEFAULT_STAC_VERSION, + stac_extensions: list[str] | None = None, + links: list[Link] | list[dict[str, Any]] | None = None, + **kwargs: Any, + ) -> None: + super().__init__(type, id, stac_version, stac_extensions, links, **kwargs) + self.description: str = description + self.title: str | None = title + self.keywords: list[str] | None = keywords + self.license: str = license + self.providers: list[Provider] | None = ( + [Provider.try_from(provider) for provider in providers] + if providers is not None + else None + ) + self.extent: Extent = Extent.try_from(extent) + self.summaries: dict[str, Any] | None = summaries + self.assets: dict[str, Asset] = ( + {key: Asset.try_from(value) for (key, value) in assets.items()} + if assets is not None + else {} + ) + self.item_assets: dict[str, ItemAsset] | None = ( + {key: ItemAsset.try_from(value) for (key, value) in item_assets.items()} + if item_assets is not None + else None + ) + + @override + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Collection: + return Collection(**data) + + @override + def set_self_href(self, href: str | None) -> None: + if self.assets: + Asset.update_hrefs(self.assets, self.get_self_href(), href) + return super().set_self_href(href) + + @override + def to_dict( + self, include_self_link: bool = True, transform_hrefs: bool | None = None + ) -> dict[str, Any]: + data = super().to_dict( + include_self_link=include_self_link, transform_hrefs=transform_hrefs + ) + data["description"] = self.description + if self.title: + data["title"] = self.title + if self.keywords: + data["keywords"] = self.keywords + data["license"] = self.license + if self.providers: + data["providers"] = [provider.to_dict() for provider in self.providers] + data["extent"] = self.extent.to_dict() + if self.summaries: + data["summaries"] = self.summaries + if self.assets: + data["assets"] = { + key: asset.to_dict() for key, asset in self.assets.items() + } + if self.item_assets: + data["item_assets"] = { + key: item_asset.to_dict() + for key, item_asset in self.item_assets.items() + } + return data + + +class Extent: + def __init__( + self, + spatial: SpatialExtent | SpatialExtentDict | None = None, + temporal: TemporalExtent | TemporalExtentDict | None = None, + ): + self.spatial: SpatialExtent = SpatialExtent.try_from(spatial) + self.temporal: TemporalExtent = TemporalExtent.try_from(temporal) + + @classmethod + def try_from(cls, extent: Extent | dict[str, Any] | None) -> Extent: + if isinstance(extent, Extent): + return extent + elif isinstance(extent, dict): + return Extent.from_dict(extent) + else: + return Extent() + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Extent: + return Extent(**data) + + def to_dict(self) -> dict[str, Any]: + return { + "spatial": self.spatial.to_dict(), + "temporal": self.temporal.to_dict(), + } + + +class TemporalExtent: + def __init__(self, interval: list[list[str | None]] | None = None) -> None: + self.interval: list[list[str | None]] = interval or [ + [datetime_to_str(dt.datetime.now()), None] + ] + + @classmethod + def try_from( + cls, + data: TemporalExtent | TemporalExtentDict | None, + ) -> TemporalExtent: + if isinstance(data, TemporalExtent): + return data + elif not data: + return TemporalExtent() + elif isinstance(data["interval"][0], list): + return TemporalExtent( + [ + to_interval(cast(list[dt.datetime | str | None], interval)) + for interval in data["interval"] + ] + ) + else: + return TemporalExtent( + [to_interval(cast(list[dt.datetime | str | None], data["interval"]))] + ) + + @classmethod + @deprecated("Use default initializer instead") + def from_now(cls) -> TemporalExtent: + return TemporalExtent() + + def to_dict(self) -> dict[str, Any]: + return {"interval": self.interval} + + +class SpatialExtent: + def __init__(self, bbox: list[list[float | int]] | None = None) -> None: + self.bbox: list[list[float | int]] = bbox or [[-180, -90, 180, 90]] + + @classmethod + def try_from( + cls, + data: SpatialExtent | SpatialExtentDict | None, + ) -> SpatialExtent: + if isinstance(data, SpatialExtent): + return data + elif not data: + return SpatialExtent() + elif isinstance(data["bbox"][0], list): + return SpatialExtent(cast(list[list[float | int]], data["bbox"])) + else: + return SpatialExtent([cast(list[float | int], data["bbox"])]) + + @classmethod + @deprecated("Use try_from instead") + def from_coordinates(cls, coordinates: Any) -> SpatialExtent: + return SpatialExtent.try_from({"bbox": coordinates}) + + def to_dict(self) -> dict[str, Any]: + return {"bbox": self.bbox} + + +def to_interval(interval: list[dt.datetime | str | None]) -> list[str | None]: + if len(interval) != 2: + raise ValueError("Interval must have exactly two elements") + return [to_datetime_str(interval[0]), to_datetime_str(interval[1])] + + +def to_datetime_str(datetime: dt.datetime | str | None) -> str | None: + if datetime is None: + return None + elif isinstance(datetime, str): + return datetime + else: + return datetime_to_str(datetime) + + +class Provider: + def __init__( + self, + name: str, + description: str | None = None, + roles: list[ProviderRole | str] | None = None, + url: str | None = None, + **kwargs: Any, + ) -> None: + self.name: str = name + self.description: str | None = description + self.roles: list[ProviderRole] | None = roles + self.url: str | None = url + self.extra_fields: dict[str, Any] = kwargs + + @classmethod + def try_from(cls, data: Provider | dict[str, Any]) -> Provider: + if isinstance(data, Provider): + return data + else: + return Provider.from_dict(data) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Provider: + return cls(**data) + + def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) + data["name"] = self.name + if self.description: + data["description"] = self.description + if self.roles: + data["roles"] = self.roles + if self.url: + data["url"] = self.url + return data + + +class ProviderRole(StrEnum): + LICENSOR = "licensor" + PRODUCER = "producer" + PROCESSOR = "processor" + HOST = "host" diff --git a/pystac/common_metadata.py b/src/pystac/common_metadata.py similarity index 98% rename from pystac/common_metadata.py rename to src/pystac/common_metadata.py index 4dac97dae..b3046db27 100644 --- a/pystac/common_metadata.py +++ b/src/pystac/common_metadata.py @@ -1,7 +1,7 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast +from typing import TYPE_CHECKING, Any, TypeVar, cast import pystac from pystac import utils @@ -52,7 +52,7 @@ def _set_field(self, prop_name: str, v: Any | None) -> None: def _get_field(self, prop_name: str, _typ: type[P]) -> P | None: if hasattr(self.object, prop_name): - return cast(Optional[P], getattr(self.object, prop_name)) + return cast(P | None, getattr(self.object, prop_name)) elif hasattr(self.object, "properties"): item = cast(pystac.Item, self.object) return item.properties.get(prop_name) diff --git a/src/pystac/constants.py b/src/pystac/constants.py new file mode 100644 index 000000000..caad78e6c --- /dev/null +++ b/src/pystac/constants.py @@ -0,0 +1,5 @@ +from typing import Literal + +STAC_OBJECT_TYPE = Literal["Catalog", "Collection", "Feature"] +DEFAULT_LICENSE = "other" +DEFAULT_STAC_VERSION = "1.1.0" diff --git a/src/pystac/deserialize.py b/src/pystac/deserialize.py new file mode 100644 index 000000000..ba9f6adc1 --- /dev/null +++ b/src/pystac/deserialize.py @@ -0,0 +1,21 @@ +from typing import Any + +from .stac_object import STACObject + + +def from_dict(data: dict[str, Any]) -> STACObject: + match data.get("type"): + case "Catalog": + from .catalog import Catalog + + return Catalog.from_dict(data) + case "Collection": + from .collection import Collection + + return Collection.from_dict(data) + case "Feature": + from .item import Item + + return Item.from_dict(data) + case _: + raise ValueError(f"Unknown STAC type: {data['type']}") diff --git a/pystac/errors.py b/src/pystac/errors.py similarity index 100% rename from pystac/errors.py rename to src/pystac/errors.py diff --git a/pystac/extensions/__init__.py b/src/pystac/extensions/__init__.py similarity index 100% rename from pystac/extensions/__init__.py rename to src/pystac/extensions/__init__.py diff --git a/pystac/extensions/base.py b/src/pystac/extensions/base.py similarity index 100% rename from pystac/extensions/base.py rename to src/pystac/extensions/base.py diff --git a/pystac/extensions/classification.py b/src/pystac/extensions/classification.py similarity index 100% rename from pystac/extensions/classification.py rename to src/pystac/extensions/classification.py diff --git a/pystac/extensions/datacube.py b/src/pystac/extensions/datacube.py similarity index 100% rename from pystac/extensions/datacube.py rename to src/pystac/extensions/datacube.py diff --git a/pystac/extensions/eo.py b/src/pystac/extensions/eo.py similarity index 100% rename from pystac/extensions/eo.py rename to src/pystac/extensions/eo.py diff --git a/pystac/extensions/ext.py b/src/pystac/extensions/ext.py similarity index 100% rename from pystac/extensions/ext.py rename to src/pystac/extensions/ext.py diff --git a/pystac/extensions/file.py b/src/pystac/extensions/file.py similarity index 100% rename from pystac/extensions/file.py rename to src/pystac/extensions/file.py diff --git a/pystac/extensions/grid.py b/src/pystac/extensions/grid.py similarity index 100% rename from pystac/extensions/grid.py rename to src/pystac/extensions/grid.py diff --git a/pystac/extensions/hooks.py b/src/pystac/extensions/hooks.py similarity index 100% rename from pystac/extensions/hooks.py rename to src/pystac/extensions/hooks.py diff --git a/pystac/extensions/item_assets.py b/src/pystac/extensions/item_assets.py similarity index 100% rename from pystac/extensions/item_assets.py rename to src/pystac/extensions/item_assets.py diff --git a/pystac/extensions/label.py b/src/pystac/extensions/label.py similarity index 100% rename from pystac/extensions/label.py rename to src/pystac/extensions/label.py diff --git a/pystac/extensions/mgrs.py b/src/pystac/extensions/mgrs.py similarity index 100% rename from pystac/extensions/mgrs.py rename to src/pystac/extensions/mgrs.py diff --git a/pystac/extensions/mlm.py b/src/pystac/extensions/mlm.py similarity index 100% rename from pystac/extensions/mlm.py rename to src/pystac/extensions/mlm.py diff --git a/pystac/extensions/pointcloud.py b/src/pystac/extensions/pointcloud.py similarity index 100% rename from pystac/extensions/pointcloud.py rename to src/pystac/extensions/pointcloud.py diff --git a/pystac/extensions/projection.py b/src/pystac/extensions/projection.py similarity index 100% rename from pystac/extensions/projection.py rename to src/pystac/extensions/projection.py diff --git a/pystac/extensions/raster.py b/src/pystac/extensions/raster.py similarity index 99% rename from pystac/extensions/raster.py rename to src/pystac/extensions/raster.py index cbd9f5bd1..7160fd862 100644 --- a/pystac/extensions/raster.py +++ b/src/pystac/extensions/raster.py @@ -786,9 +786,7 @@ def __init__(self, item_asset: pystac.ItemAssetDefinition): self.asset_definition = item_asset def __repr__(self) -> str: - return "".format( - self.asset_definition - ) + return f"" class SummariesRasterExtension(SummariesExtension): diff --git a/pystac/extensions/render.py b/src/pystac/extensions/render.py similarity index 100% rename from pystac/extensions/render.py rename to src/pystac/extensions/render.py diff --git a/pystac/extensions/sar.py b/src/pystac/extensions/sar.py similarity index 100% rename from pystac/extensions/sar.py rename to src/pystac/extensions/sar.py diff --git a/pystac/extensions/sat.py b/src/pystac/extensions/sat.py similarity index 100% rename from pystac/extensions/sat.py rename to src/pystac/extensions/sat.py diff --git a/pystac/extensions/scientific.py b/src/pystac/extensions/scientific.py similarity index 99% rename from pystac/extensions/scientific.py rename to src/pystac/extensions/scientific.py index 9f6316b70..9029b3d9c 100644 --- a/pystac/extensions/scientific.py +++ b/src/pystac/extensions/scientific.py @@ -286,9 +286,7 @@ def __init__(self, collection: pystac.Collection): super().__init__(self.collection) def __repr__(self) -> str: - return "".format( - self.collection.id - ) + return f"" class ItemScientificExtension(ScientificExtension[pystac.Item]): diff --git a/pystac/extensions/storage.py b/src/pystac/extensions/storage.py similarity index 100% rename from pystac/extensions/storage.py rename to src/pystac/extensions/storage.py diff --git a/pystac/extensions/table.py b/src/pystac/extensions/table.py similarity index 100% rename from pystac/extensions/table.py rename to src/pystac/extensions/table.py diff --git a/pystac/extensions/timestamps.py b/src/pystac/extensions/timestamps.py similarity index 100% rename from pystac/extensions/timestamps.py rename to src/pystac/extensions/timestamps.py diff --git a/pystac/extensions/version.py b/src/pystac/extensions/version.py similarity index 100% rename from pystac/extensions/version.py rename to src/pystac/extensions/version.py diff --git a/pystac/extensions/view.py b/src/pystac/extensions/view.py similarity index 100% rename from pystac/extensions/view.py rename to src/pystac/extensions/view.py diff --git a/pystac/extensions/xarray_assets.py b/src/pystac/extensions/xarray_assets.py similarity index 100% rename from pystac/extensions/xarray_assets.py rename to src/pystac/extensions/xarray_assets.py diff --git a/src/pystac/geo_interface.py b/src/pystac/geo_interface.py new file mode 100644 index 000000000..9a02d1af0 --- /dev/null +++ b/src/pystac/geo_interface.py @@ -0,0 +1,7 @@ +from typing import Any, Protocol, runtime_checkable + + +@runtime_checkable +class GeoInterface(Protocol): + @property + def __geo_interface__(self) -> dict[str, Any]: ... diff --git a/pystac/html/JSON.jinja2 b/src/pystac/html/JSON.jinja2 similarity index 100% rename from pystac/html/JSON.jinja2 rename to src/pystac/html/JSON.jinja2 diff --git a/pystac/html/Macros.jinja2 b/src/pystac/html/Macros.jinja2 similarity index 100% rename from pystac/html/Macros.jinja2 rename to src/pystac/html/Macros.jinja2 diff --git a/pystac/html/__init__.py b/src/pystac/html/__init__.py similarity index 100% rename from pystac/html/__init__.py rename to src/pystac/html/__init__.py diff --git a/pystac/html/jinja_env.py b/src/pystac/html/jinja_env.py similarity index 100% rename from pystac/html/jinja_env.py rename to src/pystac/html/jinja_env.py diff --git a/src/pystac/io.py b/src/pystac/io.py new file mode 100644 index 000000000..ae9f79ace --- /dev/null +++ b/src/pystac/io.py @@ -0,0 +1,10 @@ +from pathlib import Path + +from .deserialize import from_dict +from .reader import DEFAULT_READER, Reader +from .stac_object import STACObject + + +def read_file(href: str | Path, reader: Reader = DEFAULT_READER) -> STACObject: + data = reader.get_json(href) + return from_dict(data) diff --git a/src/pystac/item.py b/src/pystac/item.py new file mode 100644 index 000000000..d3716707c --- /dev/null +++ b/src/pystac/item.py @@ -0,0 +1,251 @@ +from __future__ import annotations + +import copy +import datetime as dt +import warnings +from typing import TYPE_CHECKING, Any, ClassVar, override + +from typing_extensions import deprecated + +from pystac.rel_type import RelType + +from .asset import Asset, Assets +from .constants import DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE +from .geo_interface import GeoInterface +from .link import Link +from .stac_object import Container, STACObject +from .utils import datetime_to_str, str_to_datetime + +if TYPE_CHECKING: + from .collection import Collection + + +class Item(STACObject, Assets): + """The STAC Item object is the most important object in a STAC system. + + An Item is the entity that contains metadata for a scene and links to the + assets. Item objects are the leaf nodes for a graph of Catalog and + Collection objects. + """ + + type: ClassVar[STAC_OBJECT_TYPE] = "Feature" + + def __init__( + self, + id: str, + geometry: dict[str, Any] | GeoInterface | None = None, + bbox: list[float | int] | None = None, + datetime: dt.datetime | str | None = None, + properties: dict[str, Any] | Properties | None = None, + type: str = "Feature", + stac_version: str = DEFAULT_STAC_VERSION, + stac_extensions: list[str] | None = None, + links: list[Link] | list[dict[str, Any]] | None = None, + assets: dict[str, Asset] | dict[str, dict[str, Any]] | None = None, + collection: str | Collection | None = None, + **kwargs: Any, + ): + super().__init__( + type=type, + id=id, + stac_version=stac_version, + stac_extensions=stac_extensions, + links=links, + **kwargs, + ) + + if isinstance(geometry, GeoInterface): + self.geometry: dict[str, Any] | None = copy.deepcopy( + geometry.__geo_interface__ + ) + else: + self.geometry = geometry + + # TODO add validation + self.bbox: list[float | int] | None = list(bbox) if bbox else None + + self.properties: Properties = Properties.try_from(properties) + if isinstance(datetime, dt.datetime): + self.properties.datetime = datetime + elif isinstance(datetime, str): + self.properties.datetime = str_to_datetime(datetime) + + if assets: + self.assets: dict[str, Asset] = {} + for key, value in assets.items(): + asset = Asset.try_from(value) + asset.set_owner(self) + self.assets[key] = asset + else: + self.assets = {} + + self._collection: Collection | str | None = collection + + @override + @classmethod + def from_dict( + cls, + data: dict[str, Any], + migrate: bool | None = None, + preserve_dict: bool | None = None, + root: Container | None = None, + ) -> Item: + item = cls(**data) + + # TODO deprecate migrate + if migrate: + raise NotImplementedError + # TODO deprecate preserve_dict + if preserve_dict: + raise NotImplementedError + if root: + warnings.warn( + "The root parameter is deprecated, call `set_root` directly after " + "creating the item", + DeprecationWarning, + ) + item.set_root(root) + return item + + @property + def datetime(self) -> dt.datetime | None: + return self.properties.datetime + + @deprecated("Get the datetime from the asset directly") + def get_datetime(self, asset: Asset | None = None) -> dt.datetime | None: + if asset and (datetime := asset.extra_fields.get("datetime")): + return str_to_datetime(datetime) + else: + return self.datetime + + @deprecated("Set the datetime on the asset directly") + def set_datetime(self, datetime: dt.datetime, asset: Asset | None = None) -> None: + if asset: + asset.extra_fields["datetime"] = datetime_to_str(datetime) + else: + self.properties.datetime = datetime + + @override + def set_self_href(self, href: str | None) -> None: + Asset.update_hrefs(self.assets, self.get_self_href(), href) + return super().set_self_href(href) + + def get_collection(self) -> Collection | None: + from .collection import Collection + + if isinstance(self._collection, Collection): + return self._collection + elif self._collection is None and ( + collection_link := self.get_link(RelType.COLLECTION) + ): + # TODO can we just make get_target just take a STACObject? + stac_object = collection_link.get_target(self.get_self_href(), self.reader) + if isinstance(stac_object, Collection): + if root := self.get_root(): + stac_object.set_root(root) + self._collection = stac_object + return stac_object + else: + return None + else: + return None + + @property + @deprecated("Use get_collection") + def collection(self) -> Collection | None: + return self.get_collection() + + @property + def collection_id(self) -> str | None: + from .collection import Collection + + if isinstance(self._collection, Collection): + return self._collection.id + elif isinstance(self._collection, str): + return self._collection + else: + return None + + def set_collection(self, collection: Collection | str | None) -> None: + self._collection = collection + self.remove_links("collection") + + @override + def to_dict( + self, + include_self_link: bool = True, + transform_hrefs: bool | None = None, + order_keys: bool = True, + ) -> dict[str, Any]: + data = super().to_dict( + include_self_link=include_self_link, transform_hrefs=transform_hrefs + ) + data.update( + { + "geometry": copy.deepcopy(self.geometry), + "properties": self.properties.to_dict(), + "assets": {key: asset.to_dict() for key, asset in self.assets.items()}, + } + ) + if self.bbox: + data["bbox"] = self.bbox + if self.collection_id: + data["collection"] = self.collection_id + if order_keys: + ordered_data: dict[str, Any] = {} + for key in ( + "type", + "stac_version", + "stac_extensions", + "id", + "geometry", + "bbox", + "properties", + "links", + "assets", + "collection", + ): + if key in data: + ordered_data[key] = data.pop(key) + ordered_data.update(data) + return ordered_data + else: + return data + + @property + def __geo_interface__(self) -> dict[str, Any]: + return self.to_dict(include_self_link=False) + + +class Properties: + def __init__(self, datetime: dt.datetime | str | None = None, **kwargs: Any): + if isinstance(datetime, str): + self.datetime: dt.datetime | None = str_to_datetime(datetime) + elif isinstance(datetime, dt.datetime): + self.datetime = datetime + else: + # TODO check for start and end datetime + self.datetime = dt.datetime.now(tz=dt.UTC) + + self.extra_fields: dict[str, Any] = kwargs + + @classmethod + def try_from( + cls, + data: Properties | dict[str, Any] | None, + ) -> Properties: + if isinstance(data, Properties): + return data + elif not data: + return Properties() + else: + return Properties.from_dict(data) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Properties: + return cls(**data) + + def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) + data["datetime"] = datetime_to_str(self.datetime) if self.datetime else None + return data diff --git a/pystac/item_assets.py b/src/pystac/item_assets.py similarity index 100% rename from pystac/item_assets.py rename to src/pystac/item_assets.py diff --git a/src/pystac/item_collection.py b/src/pystac/item_collection.py new file mode 100644 index 000000000..d922052a6 --- /dev/null +++ b/src/pystac/item_collection.py @@ -0,0 +1,22 @@ +from collections.abc import Iterator +from typing import override + +from .item import Item + + +class ItemCollection: + def __init__(self, items: list[Item]): + self.items: list[Item] = items + + def __len__(self) -> int: + return len(self.items) + + def __getitem__(self, index: int) -> Item: + return self.items[index] + + def __iter__(self) -> Iterator[Item]: + return iter(self.items) + + @override + def __repr__(self) -> str: + return f"ItemCollection({self.items})" diff --git a/src/pystac/jsonschema.py b/src/pystac/jsonschema.py new file mode 100644 index 000000000..2098de62f --- /dev/null +++ b/src/pystac/jsonschema.py @@ -0,0 +1,110 @@ +import importlib.resources +import json +import urllib.parse +import urllib.request +import warnings +from collections.abc import Iterator +from typing import Any +from urllib.request import Request + +import referencing.retrieval +from jsonschema.protocols import Validator +from jsonschema.validators import Draft7Validator +from referencing import Registry + +from .constants import STAC_OBJECT_TYPE +from .utils import get_stac_type, get_user_agent + + +class JSONSchemaValidator: + def __init__(self) -> None: + self.registry: Registry = Registry(retrieve=cached_retrieve).with_contents( + registry_contents() + ) + self.cache: dict[str, Validator] = {} + + def get_validator(self, type: STAC_OBJECT_TYPE, version: str) -> Any: + stac_type = get_stac_type(type).lower() + path = f"stac/v{version}/{stac_type}.json" + if path not in self.cache: + try: + schema_data = read_schema(path) + except FileNotFoundError: + warnings.warn(f"Local schema not found for {stac_type} v{version}") + url = f"https://schemas.stacspec.org/v{version}/{stac_type}-spec/json-schema/{stac_type}.json" + schema_data = json.loads(get_text(url)) + self.cache[path] = Draft7Validator(schema_data, registry=self.registry) + return self.cache[path] + + def validate_core( + self, type: STAC_OBJECT_TYPE, version: str, data: dict[str, Any] + ) -> None: + validator = self.get_validator(type, version) + validator.validate(data) + + def validate_extension(self, extension: str, data: dict[str, Any]) -> None: + if extension not in self.cache: + schema_data = json.loads(get_text(extension)) + self.cache[extension] = Draft7Validator(schema_data, registry=self.registry) + validator = self.cache[extension] + validator.validate(data) + + +@referencing.retrieval.to_cached_resource() +def cached_retrieve(uri: str) -> str: + return get_text(uri) + + +def get_text(uri: str) -> str: + if urllib.parse.urlparse(uri).scheme: + request = Request(uri, headers={"User-Agent": get_user_agent()}) + with urllib.request.urlopen(request) as response: + return str(response.read(), encoding="utf-8") + else: + with open(uri) as f: + return f.read() + + +def registry_contents() -> Iterator[tuple[str, dict[str, Any]]]: + for name in ( + "bands", + "basics", + "common", + "data-values", + "item", + "datetime", + "instrument", + "licensing", + "provider", + ): + uri = f"https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/{name}.json" + path = f"stac/v1.1.0/{name}.json" + yield uri, read_schema(path) + + for name in ( + "basics", + "datetime", + "instrument", + "item", + "licensing", + "provider", + ): + uri = f"https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/{name}.json" + path = f"stac/v1.0.0/{name}.json" + yield uri, read_schema(path) + + for name in ( + "Feature", + "Geometry", + ): + uri = f"https://geojson.org/schema/{name}.json" + path = f"geojson/{name}.json" + yield uri, read_schema(path) + + +def read_schema(path: str) -> dict[str, Any]: + with importlib.resources.files("pystac.schemas").joinpath(path).open("r") as f: + return json.load(f) + + +DEFAULT_JSON_SCHEMA_VALIDATOR = JSONSchemaValidator() diff --git a/pystac/layout.py b/src/pystac/layout.py similarity index 97% rename from pystac/layout.py rename to src/pystac/layout.py index cb380bb13..ed790471a 100644 --- a/pystac/layout.py +++ b/src/pystac/layout.py @@ -1,5 +1,12 @@ from __future__ import annotations +import warnings + +warnings.warn( + message="pystac.layout is deprecated", + category=DeprecationWarning, +) + import posixpath from abc import ABC, abstractmethod from collections import OrderedDict @@ -132,10 +139,8 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any dt = stac_object.common_metadata.start_datetime if dt is None: raise pystac.TemplateError( - "Item {} does not have a datetime or " - "datetime range set; cannot template {} in {}".format( - stac_object, template_var, self.template - ) + f"Item {stac_object} does not have a datetime or " + f"datetime range set; cannot template {template_var} in {self.template}" ) if template_var == "year": @@ -157,18 +162,14 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any ) else: raise pystac.TemplateError( - '"{}" cannot be used to template non-Item {} in {}'.format( - template_var, stac_object, self.template - ) + f'"{template_var}" cannot be used to template non-Item {stac_object} in {self.template}' ) # Allow dot-notation properties for arbitrary object values. props = template_var.split(".") prop_source: pystac.STACObject | dict[str, Any] | None = None error = pystac.TemplateError( - "Cannot find property {} on {} for template {}".format( - template_var, stac_object, self.template - ) + f"Cannot find property {template_var} on {stac_object} for template {self.template}" ) try: diff --git a/src/pystac/link.py b/src/pystac/link.py new file mode 100644 index 000000000..a991c0248 --- /dev/null +++ b/src/pystac/link.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import copy +import warnings +from typing import TYPE_CHECKING, Any, override + +from typing_extensions import deprecated + +from pystac.errors import STACError +from pystac.rel_type import RelType +from pystac.utils import make_absolute_href + +from .reader import Reader + +if TYPE_CHECKING: + from .stac_object import STACObject + +HIERARCHICAL_LINKS = [ + RelType.SELF, + RelType.CHILD, + RelType.PARENT, + RelType.ROOT, + RelType.COLLECTION, +] + + +class Link: + def __init__( + self, + rel: str, + # target is mostly for backwards compatibility, if we were to design it + # from scratch we wouldn't do it this way, + target: str | STACObject | None = None, + href: str | None = None, + type: str | None = None, + title: str | None = None, + method: str | None = None, + headers: dict[str, str | list[str]] | None = None, + body: Any | None = None, + **kwargs: Any, + ): + from .stac_object import STACObject + + self.rel: str = rel + self.type: str | None = type + self.title: str | None = title + self.method: str | None = method + self.headers: dict[str, str | list[str]] | None = headers + self.body: Any | None = body + self.extra_fields: dict[str, Any] = kwargs + + if isinstance(target, STACObject): + self._href: str | None = href or target.get_self_href() + self.target: STACObject | None = target + elif href and target: + raise ValueError("Both target and href were provided as strings") + elif not href and not target: + raise ValueError("Neither href nor target were provided") + else: + self._href = href or target + self.target = None + + @classmethod + def try_from(cls, data: dict[str, Any] | Link) -> Link: + if isinstance(data, Link): + return data + else: + return cls(**data) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Link: + return cls(**data) + + @property + @deprecated("media_type is now called type") + def media_type(self) -> str | None: + return self.type + + def is_hierarchical(self) -> bool: + return self.rel in HIERARCHICAL_LINKS + + def is_self(self) -> bool: + return self.rel == RelType.SELF + + def is_item(self) -> bool: + return self.rel == RelType.ITEM + + def is_child(self) -> bool: + return self.rel == RelType.CHILD + + def is_derived_from(self) -> bool: + return self.rel == RelType.DERIVED_FROM + + def get_href(self) -> str | None: + return self._href or self.target and self.target.get_self_href() + + def get_target(self, start_href: str | None, reader: Reader) -> STACObject: + from .stac_object import STACObject + + if not self.target: + if not self._href: + raise ValueError("No target and no href on the link") + + href = make_absolute_href(self._href, start_href, start_is_dir=False) + try: + self.target = STACObject.from_file(href, reader=reader) + except Exception as e: + raise STACError(f"Error while resolving link: {e}") + + return self.target + + def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: + if transform_href is not None: + warnings.warn( + "Transforming href is deprecated and will be removed in a " + "future version.", + DeprecationWarning, + ) + if transform_href: + raise NotImplementedError + data = copy.deepcopy(self.extra_fields) + if self._href: + data["href"] = self._href + elif self.target and (href := self.target.get_self_href()): + data["href"] = href + else: + raise ValueError("No href or target self href on the link") + data["rel"] = self.rel + if self.type is not None: + data["type"] = self.type + if self.title is not None: + data["title"] = self.title + if self.method is not None: + data["method"] = self.method + if self.headers is not None: + data["headers"] = self.headers + if self.body is not None: + data["body"] = self.body + return data + + @override + def __repr__(self) -> str: + return f"Link(rel={self.rel}, href={self._href})" diff --git a/pystac/media_type.py b/src/pystac/media_type.py similarity index 100% rename from pystac/media_type.py rename to src/pystac/media_type.py diff --git a/pystac/py.typed b/src/pystac/py.typed similarity index 100% rename from pystac/py.typed rename to src/pystac/py.typed diff --git a/src/pystac/reader.py b/src/pystac/reader.py new file mode 100644 index 000000000..8f062c2ce --- /dev/null +++ b/src/pystac/reader.py @@ -0,0 +1,37 @@ +import json +import urllib.parse +import urllib.request +from pathlib import Path +from typing import Any, Protocol +from urllib.request import Request + +from .utils import get_user_agent + + +class Reader(Protocol): + def get_json(self, href: str | Path) -> dict[str, Any]: ... + + +class StandardLibraryReader: + def get_json(self, href: str | Path) -> dict[str, Any]: + if isinstance(href, Path): + with open(href) as f: + return json.load(f) + parsed_url = urllib.parse.urlparse(href) + if parsed_url.scheme in ["http", "https"]: + request = Request(href, headers={"User-Agent": get_user_agent()}) + with urllib.request.urlopen(request) as f: + return json.load(f) + elif not parsed_url.scheme: + with open(href) as f: + return json.load(f) + else: + raise ValueError(f"Unsupported scheme: {parsed_url.scheme}") + + +def set_default_reader(reader: Reader) -> None: + global DEFAULT_READER + DEFAULT_READER = reader # pyright: ignore[reportConstantRedefinition] + + +DEFAULT_READER = StandardLibraryReader() diff --git a/pystac/rel_type.py b/src/pystac/rel_type.py similarity index 100% rename from pystac/rel_type.py rename to src/pystac/rel_type.py diff --git a/src/pystac/schemas/geojson/Feature.json b/src/pystac/schemas/geojson/Feature.json new file mode 100644 index 000000000..89136eafe --- /dev/null +++ b/src/pystac/schemas/geojson/Feature.json @@ -0,0 +1,505 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://geojson.org/schema/Feature.json", + "title": "GeoJSON Feature", + "type": "object", + "required": [ + "type", + "properties", + "geometry" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Feature" + ] + }, + "id": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "properties": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object" + } + ] + }, + "geometry": { + "oneOf": [ + { + "type": "null" + }, + { + "title": "GeoJSON Point", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Point" + ] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON LineString", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "LineString" + ] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON Polygon", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Polygon" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiPoint", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiPoint" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiLineString", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiLineString" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiPolygon", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiPolygon" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON GeometryCollection", + "type": "object", + "required": [ + "type", + "geometries" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "GeometryCollection" + ] + }, + "geometries": { + "type": "array", + "items": { + "oneOf": [ + { + "title": "GeoJSON Point", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Point" + ] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON LineString", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "LineString" + ] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON Polygon", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Polygon" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiPoint", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiPoint" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiLineString", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiLineString" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiPolygon", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiPolygon" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + } + ] + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + } + ] + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } +} \ No newline at end of file diff --git a/src/pystac/schemas/geojson/Geometry.json b/src/pystac/schemas/geojson/Geometry.json new file mode 100644 index 000000000..6f5ed9833 --- /dev/null +++ b/src/pystac/schemas/geojson/Geometry.json @@ -0,0 +1,218 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://geojson.org/schema/Geometry.json", + "title": "GeoJSON Geometry", + "oneOf": [ + { + "title": "GeoJSON Point", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Point" + ] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON LineString", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "LineString" + ] + }, + "coordinates": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON Polygon", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Polygon" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiPoint", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiPoint" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiLineString", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiLineString" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + }, + { + "title": "GeoJSON MultiPolygon", + "type": "object", + "required": [ + "type", + "coordinates" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MultiPolygon" + ] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "minItems": 4, + "items": { + "type": "array", + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + }, + "bbox": { + "type": "array", + "minItems": 4, + "items": { + "type": "number" + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/pystac/schemas/stac/v1.0.0/basics.json b/src/pystac/schemas/stac/v1.0.0/basics.json new file mode 100644 index 000000000..68e8f37ae --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/basics.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/basics.json#", + "title": "Basic Descriptive Fields", + "type": "object", + "properties": { + "title": { + "title": "Item Title", + "description": "A human-readable title describing the Item.", + "type": "string" + }, + "description": { + "title": "Item Description", + "description": "Detailed multi-line description to fully explain the Item.", + "type": "string" + } + } +} \ No newline at end of file diff --git a/src/pystac/schemas/stac/v1.0.0/catalog.json b/src/pystac/schemas/stac/v1.0.0/catalog.json new file mode 100644 index 000000000..31b2c8f8a --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/catalog.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/catalog-spec/json-schema/catalog.json#", + "title": "STAC Catalog Specification", + "description": "This object represents Catalogs in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/catalog" + } + ], + "definitions": { + "catalog": { + "title": "STAC Catalog", + "type": "object", + "required": [ + "stac_version", + "type", + "id", + "description", + "links" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, + "type": { + "title": "Type of STAC entity", + "const": "Catalog" + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "title": { + "title": "Title", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "minLength": 1 + }, + "links": { + "title": "Links", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + } + } + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + } + } + } + } +} diff --git a/src/pystac/schemas/stac/v1.0.0/collection.json b/src/pystac/schemas/stac/v1.0.0/collection.json new file mode 100644 index 000000000..2ad20902d --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/collection.json @@ -0,0 +1,270 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/collection-spec/json-schema/collection.json#", + "title": "STAC Collection Specification", + "description": "This object represents Collections in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/collection" + } + ], + "definitions": { + "collection": { + "title": "STAC Collection", + "description": "These are the fields specific to a STAC Collection. All other fields are inherited from STAC Catalog.", + "type": "object", + "required": [ + "stac_version", + "type", + "id", + "description", + "license", + "extent", + "links" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, + "type": { + "title": "Type of STAC entity", + "const": "Collection" + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "title": { + "title": "Title", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "minLength": 1 + }, + "keywords": { + "title": "Keywords", + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "title": "Collection License Name", + "type": "string", + "pattern": "^[\\w\\-\\.\\+]+$" + }, + "providers": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "Organization name", + "type": "string" + }, + "description": { + "title": "Organization description", + "type": "string" + }, + "roles": { + "title": "Organization roles", + "type": "array", + "items": { + "type": "string", + "enum": [ + "producer", + "licensor", + "processor", + "host" + ] + } + }, + "url": { + "title": "Organization homepage", + "type": "string", + "format": "iri" + } + } + } + }, + "extent": { + "title": "Extents", + "type": "object", + "required": [ + "spatial", + "temporal" + ], + "properties": { + "spatial": { + "title": "Spatial extent object", + "type": "object", + "required": [ + "bbox" + ], + "properties": { + "bbox": { + "title": "Spatial extents", + "type": "array", + "minItems": 1, + "items": { + "title": "Spatial extent", + "type": "array", + "oneOf": [ + { + "minItems":4, + "maxItems":4 + }, + { + "minItems":6, + "maxItems":6 + } + ], + "items": { + "type": "number" + } + } + } + } + }, + "temporal": { + "title": "Temporal extent object", + "type": "object", + "required": [ + "interval" + ], + "properties": { + "interval": { + "title": "Temporal extents", + "type": "array", + "minItems": 1, + "items": { + "title": "Temporal extent", + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + } + } + } + } + } + } + }, + "assets": { + "$ref": "../../item-spec/json-schema/item.json#/definitions/assets" + }, + "links": { + "title": "Links", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + }, + "summaries": { + "$ref": "#/definitions/summaries" + } + } + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + } + } + }, + "summaries": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "title": "JSON Schema", + "type": "object", + "minProperties": 1, + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema" + } + ] + }, + { + "title": "Range", + "type": "object", + "required": [ + "minimum", + "maximum" + ], + "properties": { + "minimum": { + "title": "Minimum value", + "type": [ + "number", + "string" + ] + }, + "maximum": { + "title": "Maximum value", + "type": [ + "number", + "string" + ] + } + } + }, + { + "title": "Set of values", + "type": "array", + "minItems": 1, + "items": { + "description": "For each field only the original data type of the property can occur (except for arrays), but we can't validate that in JSON Schema yet. See the sumamry description in the STAC specification for details." + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/pystac/schemas/stac/v1.0.0/datetime.json b/src/pystac/schemas/stac/v1.0.0/datetime.json new file mode 100644 index 000000000..4c7a3a143 --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/datetime.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#", + "title": "Date and Time Fields", + "type": "object", + "dependencies": { + "start_datetime": { + "required": [ + "end_datetime" + ] + }, + "end_datetime": { + "required": [ + "start_datetime" + ] + } + }, + "properties": { + "datetime": { + "title": "Date and Time", + "description": "The searchable date/time of the assets, in UTC (Formatted in RFC 3339) ", + "type": ["string", "null"], + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "start_datetime": { + "title": "Start Date and Time", + "description": "The searchable start date/time of the assets, in UTC (Formatted in RFC 3339) ", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "end_datetime": { + "title": "End Date and Time", + "description": "The searchable end date/time of the assets, in UTC (Formatted in RFC 3339) ", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "created": { + "title": "Creation Time", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "updated": { + "title": "Last Update Time", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + } + } +} \ No newline at end of file diff --git a/src/pystac/schemas/stac/v1.0.0/instrument.json b/src/pystac/schemas/stac/v1.0.0/instrument.json new file mode 100644 index 000000000..688c4a497 --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/instrument.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json#", + "title": "Instrument Fields", + "type": "object", + "properties": { + "platform": { + "title": "Platform", + "type": "string" + }, + "instruments": { + "title": "Instruments", + "type": "array", + "items": { + "type": "string" + } + }, + "constellation": { + "title": "Constellation", + "type": "string" + }, + "mission": { + "title": "Mission", + "type": "string" + }, + "gsd": { + "title": "Ground Sample Distance", + "type": "number", + "exclusiveMinimum": 0 + } + } +} \ No newline at end of file diff --git a/src/pystac/schemas/stac/v1.0.0/item.json b/src/pystac/schemas/stac/v1.0.0/item.json new file mode 100644 index 000000000..8d4286783 --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/item.json @@ -0,0 +1,272 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#", + "title": "STAC Item", + "type": "object", + "description": "This object represents the metadata for an item in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/core" + } + ], + "definitions": { + "common_metadata": { + "allOf": [ + { + "$ref": "basics.json" + }, + { + "$ref": "datetime.json" + }, + { + "$ref": "instrument.json" + }, + { + "$ref": "licensing.json" + }, + { + "$ref": "provider.json" + } + ] + }, + "core": { + "allOf": [ + { + "$ref": "https://geojson.org/schema/Feature.json" + }, + { + "oneOf": [ + { + "type": "object", + "required": [ + "geometry", + "bbox" + ], + "properties": { + "geometry": { + "$ref": "https://geojson.org/schema/Geometry.json" + }, + "bbox": { + "type": "array", + "oneOf": [ + { + "minItems": 4, + "maxItems": 4 + }, + { + "minItems": 6, + "maxItems": 6 + } + ], + "items": { + "type": "number" + } + } + } + }, + { + "type": "object", + "required": [ + "geometry" + ], + "properties": { + "geometry": { + "type": "null" + }, + "bbox": { + "not": {} + } + } + } + ] + }, + { + "type": "object", + "required": [ + "stac_version", + "id", + "links", + "assets", + "properties" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.0.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, + "id": { + "title": "Provider ID", + "description": "Provider item ID", + "type": "string", + "minLength": 1 + }, + "links": { + "title": "Item links", + "description": "Links to item relations", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + }, + "assets": { + "$ref": "#/definitions/assets" + }, + "properties": { + "allOf": [ + { + "$ref": "#/definitions/common_metadata" + }, + { + "anyOf": [ + { + "required": [ + "datetime" + ], + "properties": { + "datetime": { + "not": { + "type": "null" + } + } + } + }, + { + "required": [ + "datetime", + "start_datetime", + "end_datetime" + ] + } + ] + } + ] + } + }, + "if": { + "properties": { + "links": { + "contains": { + "required": [ + "rel" + ], + "properties": { + "rel": { + "const": "collection" + } + } + } + } + } + }, + "then": { + "required": [ + "collection" + ], + "properties": { + "collection": { + "title": "Collection ID", + "description": "The ID of the STAC Collection this Item references to.", + "type": "string", + "minLength": 1 + } + } + }, + "else": { + "properties": { + "collection": { + "not": {} + } + } + } + } + ] + }, + "link": { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + } + } + }, + "assets": { + "title": "Asset links", + "description": "Links to assets", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/asset" + } + }, + "asset": { + "allOf": [ + { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "title": "Asset reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "title": { + "title": "Asset title", + "type": "string" + }, + "description": { + "title": "Asset description", + "type": "string" + }, + "type": { + "title": "Asset type", + "type": "string" + }, + "roles": { + "title": "Asset roles", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "$ref": "#/definitions/common_metadata" + } + ] + } + } +} diff --git a/src/pystac/schemas/stac/v1.0.0/licensing.json b/src/pystac/schemas/stac/v1.0.0/licensing.json new file mode 100644 index 000000000..ca0eed8b9 --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/licensing.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/licensing.json#", + "title": "Licensing Fields", + "type": "object", + "properties": { + "license": { + "type": "string", + "pattern": "^[\\w\\-\\.\\+]+$" + } + } +} \ No newline at end of file diff --git a/src/pystac/schemas/stac/v1.0.0/provider.json b/src/pystac/schemas/stac/v1.0.0/provider.json new file mode 100644 index 000000000..01cfadcec --- /dev/null +++ b/src/pystac/schemas/stac/v1.0.0/provider.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/provider.json#", + "title": "Provider Fields", + "type": "object", + "properties": { + "providers": { + "title": "Providers", + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "Organization name", + "type": "string", + "minLength": 1 + }, + "description": { + "title": "Organization description", + "type": "string" + }, + "roles": { + "title": "Organization roles", + "type": "array", + "items": { + "type": "string", + "enum": [ + "producer", + "licensor", + "processor", + "host" + ] + } + }, + "url": { + "title": "Organization homepage", + "type": "string", + "format": "iri" + } + } + } + } + } +} \ No newline at end of file diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json b/src/pystac/schemas/stac/v1.1.0/bands.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json rename to src/pystac/schemas/stac/v1.1.0/bands.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json b/src/pystac/schemas/stac/v1.1.0/basics.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json rename to src/pystac/schemas/stac/v1.1.0/basics.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json b/src/pystac/schemas/stac/v1.1.0/catalog.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json rename to src/pystac/schemas/stac/v1.1.0/catalog.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json b/src/pystac/schemas/stac/v1.1.0/collection.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json rename to src/pystac/schemas/stac/v1.1.0/collection.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json b/src/pystac/schemas/stac/v1.1.0/common.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json rename to src/pystac/schemas/stac/v1.1.0/common.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json b/src/pystac/schemas/stac/v1.1.0/data-values.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json rename to src/pystac/schemas/stac/v1.1.0/data-values.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json b/src/pystac/schemas/stac/v1.1.0/datetime.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json rename to src/pystac/schemas/stac/v1.1.0/datetime.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json b/src/pystac/schemas/stac/v1.1.0/instrument.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json rename to src/pystac/schemas/stac/v1.1.0/instrument.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json b/src/pystac/schemas/stac/v1.1.0/item.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json rename to src/pystac/schemas/stac/v1.1.0/item.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json b/src/pystac/schemas/stac/v1.1.0/licensing.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json rename to src/pystac/schemas/stac/v1.1.0/licensing.json diff --git a/pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json b/src/pystac/schemas/stac/v1.1.0/provider.json similarity index 100% rename from pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json rename to src/pystac/schemas/stac/v1.1.0/provider.json diff --git a/pystac/serialization/__init__.py b/src/pystac/serialization/__init__.py similarity index 80% rename from pystac/serialization/__init__.py rename to src/pystac/serialization/__init__.py index 3c89c7903..e782b1ed6 100644 --- a/pystac/serialization/__init__.py +++ b/src/pystac/serialization/__init__.py @@ -1,3 +1,10 @@ +import warnings + +warnings.warn( + "pystac.serialization is deprecated", + DeprecationWarning, +) + __all__ = [ "merge_common_properties", "migrate_to_latest", diff --git a/pystac/serialization/common_properties.py b/src/pystac/serialization/common_properties.py similarity index 96% rename from pystac/serialization/common_properties.py rename to src/pystac/serialization/common_properties.py index 5109782c8..ac94e0ef5 100644 --- a/pystac/serialization/common_properties.py +++ b/src/pystac/serialization/common_properties.py @@ -89,9 +89,7 @@ def merge_common_properties( collection_props = collection["properties"] else: raise ValueError( - "{} is expected to be a Collection or dict but is neither.".format( - collection - ) + f"{collection} is expected to be a Collection or dict but is neither." ) if collection_props is not None: diff --git a/pystac/serialization/identify.py b/src/pystac/serialization/identify.py similarity index 100% rename from pystac/serialization/identify.py rename to src/pystac/serialization/identify.py diff --git a/pystac/serialization/migrate.py b/src/pystac/serialization/migrate.py similarity index 100% rename from pystac/serialization/migrate.py rename to src/pystac/serialization/migrate.py diff --git a/pystac/stac_io.py b/src/pystac/stac_io.py similarity index 99% rename from pystac/stac_io.py rename to src/pystac/stac_io.py index 9baa9b443..69cc9d432 100644 --- a/pystac/stac_io.py +++ b/src/pystac/stac_io.py @@ -1,5 +1,11 @@ from __future__ import annotations +import warnings + +warnings.warn( + "pystac.stac_io is deprecated. Use Reader and Writer instead", DeprecationWarning +) + import json import os from abc import ABC, abstractmethod diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py new file mode 100644 index 000000000..972b5cdad --- /dev/null +++ b/src/pystac/stac_object.py @@ -0,0 +1,447 @@ +from __future__ import annotations + +import copy +import warnings +from abc import ABC, abstractmethod +from collections.abc import Iterator +from enum import StrEnum +from pathlib import Path +from typing import TYPE_CHECKING, Any, ClassVar, Protocol + +from typing_extensions import deprecated + +from pystac.errors import STACTypeError +from pystac.rel_type import RelType +from pystac.writer import DEFAULT_WRITER + +from .constants import STAC_OBJECT_TYPE +from .link import Link +from .reader import DEFAULT_READER, Reader +from .utils import is_absolute_href, make_absolute_href, make_relative_href +from .validator import Validator +from .writer import Writer + +if TYPE_CHECKING: + from .collection import Collection + from .item import Item + + +def __getattr__(name: str) -> Any: + from typing import TypeVar + + if name == "S": + warnings.warn( + "pystac.stac_object.S is deprecated, just use `type[STACObject]` instead" + ) + return TypeVar("S", bound="STACObject") + raise AttributeError(f"module {__name__} has no attribute {name}") + + +class HrefGenerator(Protocol): + def get_root(self, prefix: str, container: Container) -> str: ... + def get_child(self, parent_href: str, container: Container) -> str: ... + def get_item(self, parent_href: str, item: Item) -> str: ... + + +class BestPracticesHrefGenerator: + def get_root(self, prefix: str, container: Container) -> str: + from .catalog import Catalog + from .collection import Collection + + if isinstance(container, Catalog): + return make_absolute_href(prefix, "./catalog.json", start_is_dir=True) + elif isinstance(container, Collection): + return make_absolute_href(prefix, "./collection.json", start_is_dir=True) + else: + raise ValueError(f"Unsupported root type: {type(container)}") + + def get_child(self, parent_href: str, container: Container) -> str: + from .catalog import Catalog + from .collection import Collection + + if isinstance(container, Catalog): + file_name = "catalog.json" + elif isinstance(container, Collection): + file_name = "collection.json" + else: + raise ValueError(f"Unsupported child type: {type(container)}") + + return make_absolute_href( + parent_href, "/".join((container.id, file_name)), start_is_dir=False + ) + + def get_item(self, parent_href: str, item: Item) -> str: + return make_absolute_href( + parent_href, "/".join((item.id, f"{item.id}.json")), start_is_dir=False + ) + + +DEFAULT_HREF_GENERATOR = BestPracticesHrefGenerator() + + +@deprecated("STACObjectType is deprecated") +class STACObjectType(StrEnum): + CATALOG = "Catalog" + COLLECTION = "Collection" + ITEM = "Feature" + + +class STACObject(ABC): + type: ClassVar[STAC_OBJECT_TYPE] + + def __init__( + self, + type: str, + id: str, + stac_version: str, + stac_extensions: list[str] | None, + links: list[Link] | list[dict[str, Any]] | None, + **kwargs: Any, + ) -> None: + if type != self.type: + raise STACTypeError(f"Expected {self.type}, got {type}") + + self.id: str = id + self.stac_version: str = stac_version + self.stac_extensions: list[str] | None = stac_extensions + if links: + self.links: list[Link] = [Link.try_from(link) for link in links] + else: + self.links = [] + self.extra_fields: dict[str, Any] = kwargs + + self.reader: Reader = DEFAULT_READER + self.writer: Writer = DEFAULT_WRITER + + self._root: Container | None = None + self._href: str | None = None + + @classmethod + def from_file[T: STACObject]( + cls: type[T], path: str | Path, reader: Reader = DEFAULT_READER + ) -> T: + href = make_absolute_href(str(path)) + data = reader.get_json(href) + stac_object = cls.from_dict(data) + if not isinstance(stac_object, cls): + raise ValueError( + f"Expected {cls.__name__}, got {type(stac_object).__name__}" + ) + stac_object.reader = reader + stac_object.set_self_href(href) + return stac_object + + @classmethod + @abstractmethod + def from_dict[T: STACObject](cls: type[T], data: dict[str, Any]) -> T: + from .deserialize import from_dict + + return from_dict(data) # pyright: ignore[reportReturnType] + + @abstractmethod + def to_dict( + self, include_self_link: bool = True, transform_hrefs: bool | None = None + ) -> dict[str, Any]: + if transform_hrefs is not None: + warnings.warn( + "transform_hrefs is deprecated as an argument in `to_dict`", + DeprecationWarning, + ) + if transform_hrefs: + raise NotImplementedError + data = copy.deepcopy(self.extra_fields) + links = [ + link.to_dict() + for link in self.links + # Keep the self link if include_self_link is true and the object + # doesn't have an href + if include_self_link and not self._href or not link.is_self() + ] + if include_self_link and self._href: + links.append(Link(rel="self", target=self._href).to_dict()) + data.update( + { + "type": self.type, + "stac_version": self.stac_version, + "id": self.id, + "links": links, + } + ) + if self.stac_extensions is not None: + data["stac_extensions"] = self.stac_extensions + return data + + def get_link(self, rel: str) -> Link | None: + return next((link for link in self.links if link.rel == rel), None) + + def get_links(self, rel: str) -> list[Link]: + return list(link for link in self.links if link.rel == rel) + + @deprecated("Use get_link instead") + def get_single_link(self, rel: str) -> Link | None: + return self.get_link(rel) + + def get_derived_from(self) -> list[STACObject]: + derived_from: list[STACObject] = [] + for link in self.links: + if link.is_derived_from(): + derived_from.append(link.get_target(self._href, self.reader)) + return derived_from + + @deprecated("Just append to the links array") + def add_link(self, link: Link) -> None: + self.links.append(link) + + def add_derived_from(self, *values: STACObject | str) -> None: + for value in values: + if isinstance(value, STACObject): + self.links.append(Link(target=value, rel=RelType.DERIVED_FROM)) + else: + self.links.append(Link(target=value, rel=RelType.DERIVED_FROM)) + + def remove_derived_from(self, id: str) -> None: + links: list[Link] = [] + for link in self.links: + if link.is_derived_from(): + stac_object = link.get_target(self._href, self.reader) + if stac_object.id == id: + continue + links.append(link) + self.links = links + + def remove_links(self, rel: str) -> None: + self.links = list(link for link in self.links if not link.rel == rel) + + def remove_hierarchical_links(self, add_canonical: bool = False) -> None: + self.links = list(link for link in self.links if not link.is_hierarchical()) + if add_canonical and self._href: + self.links.append(Link(rel="canonical", target=self._href)) + + @property + @deprecated("Use get_self_href") + def self_href(self) -> str | None: + return self.get_self_href() + + def get_self_href(self) -> str | None: + return self._href + + def set_self_href(self, href: str | None) -> None: + self._href = href + self.links = list(link for link in self.links if not link.is_self()) + if href: + self.links.append(Link(rel="self", target=href)) + + def get_root(self) -> Container | None: + if self._root is None: + root = self._maybe_get_link_target("root") + if isinstance(root, Container): + self._root = root + else: + warnings.warn( + "The 'root' link does not point to a collection or catalog" + ) + return self._root + + def set_root(self, root: Container) -> None: + self._root = root + + def validate( + self, validate_extensions: bool = True, validator: Validator | None = None + ) -> None: + if validator is None: + from .jsonschema import DEFAULT_JSON_SCHEMA_VALIDATOR + + validator = DEFAULT_JSON_SCHEMA_VALIDATOR + + data = self.to_dict() + validator.validate_core(self.type, self.stac_version, data) + + if validate_extensions and self.stac_extensions: + for extension in self.stac_extensions: + if not is_absolute_href(extension): + extension = make_absolute_href(extension, self._href, False) + validator.validate_extension(extension, data) + + def render( + self, + root: Container | None = None, + parent: Container | None = None, + collection: Collection | None = None, + use_absolute_links: bool = False, + href_generator: HrefGenerator = DEFAULT_HREF_GENERATOR, + ) -> Iterator[STACObject]: + from .item import Item + + self_href = self.get_self_href() + if self_href is None: + raise ValueError("Cannot render a self href") + if root is None and isinstance(self, Container): + root = self + links: list[Link] = [] + non_hierarchical_links: list[Link] = [] + if root and (root_href := root.get_self_href()): + links.append( + self._make_link( + rel=RelType.ROOT, + target=root, + href=root_href, + make_absolute=use_absolute_links, + ) + ) + if parent and (parent_href := parent.get_self_href()): + links.append( + self._make_link( + rel=RelType.PARENT, + target=parent, + href=parent_href, + make_absolute=use_absolute_links, + ) + ) + if collection and (collection_href := collection.get_self_href()): + links.append( + self._make_link( + rel=RelType.COLLECTION, + target=collection, + href=collection_href, + make_absolute=use_absolute_links, + ) + ) + for link in self.links: + if link.is_child() or link.is_item(): + stac_object = link.get_target(self.get_self_href(), self.reader) + href = stac_object.get_self_href() + if isinstance(stac_object, Container): + if not href: + href = href_generator.get_child(self_href, stac_object) + stac_object.set_self_href(href) + links.append( + self._make_link( + rel=RelType.CHILD, + target=stac_object, + href=href, + make_absolute=use_absolute_links, + ) + ) + elif isinstance(stac_object, Item): + if not href: + href = href_generator.get_item(self_href, stac_object) + stac_object.set_self_href(href) + links.append( + self._make_link( + rel=RelType.ITEM, + target=stac_object, + href=href, + make_absolute=use_absolute_links, + ) + ) + + if isinstance(self, Container): + yield from stac_object.render( + root=root, + parent=self, + use_absolute_links=use_absolute_links, + href_generator=href_generator, + ) + else: + yield from stac_object.render( + root=root, + use_absolute_links=use_absolute_links, + href_generator=href_generator, + ) + elif not link.is_hierarchical(): + non_hierarchical_links.append(link) + + self.links = links + non_hierarchical_links + yield self + + def save_object( + self, include_self_link: bool = True, dest_href: str | None = None + ) -> None: + href = dest_href or self._href + if not href: + raise ValueError( + "dest_href was not provided, and object does not have a self href" + ) + href = make_absolute_href(href) + self.writer.put_json(self.to_dict(include_self_link), href) + + def clone[T: STACObject](self: T) -> T: + return self.from_dict(self.to_dict()) + + def _make_link( + self, rel: str, target: STACObject, href: str, make_absolute: bool + ) -> Link: + self_href = self.get_self_href() + href = make_absolute_href(href, self_href, start_is_dir=False) + if not make_absolute and self_href: + href = make_relative_href(href, self_href, start_is_dir=False) + return Link(rel=rel, target=target, href=href) + + def _maybe_get_link_target(self, rel: str) -> STACObject | None: + link = self.get_link(rel) + if link: + return link.get_target(start_href=self._href, reader=self.reader) + else: + return None + + +class Container(STACObject, ABC): + def get_items(self, recursive: bool = False) -> Iterator[Item]: + for link in self.links: + if link.is_item(): + from .item import Item + + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Item): + yield stac_object + elif recursive and link.is_child(): + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Container): + yield from stac_object.get_items(recursive=True) + + def add_item(self, item: Item) -> None: + self.links.append(Link(target=item, rel=RelType.ITEM)) + + def get_child(self, id: str) -> Container | None: + for link in self.links: + if link.is_child(): + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Container) and stac_object.id == id: + return stac_object + + def add_child(self, child: Container) -> None: + link = Link(target=child, rel=RelType.CHILD) + self.links.append(link) + + @deprecated("Use render instead") + def normalize_hrefs(self, root_href: str) -> None: + href_generator = DEFAULT_HREF_GENERATOR + self.set_self_href(href_generator.get_root(root_href, self)) + for _ in self.render(): + pass + + def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: + from .item import Item + + self_href = self.get_self_href() + children: list[Container] = [] + items: list[Item] = [] + for link in self.links: + if link.is_child() or link.is_item(): + stac_object = link.get_target(self_href, self.reader) + if isinstance(stac_object, Container): + children.append(stac_object) + elif isinstance(stac_object, Item): + items.append(stac_object) + + yield (self, children, items) + + for child in children: + yield from child.walk() + + def target_in_hierarchy(self, target: STACObject) -> bool: + for root, _, items in self.walk(): + if root == target or any(item == target for item in items): + return True + + return False diff --git a/pystac/summaries.py b/src/pystac/summaries.py similarity index 99% rename from pystac/summaries.py rename to src/pystac/summaries.py index 6a0106251..8cdf14848 100644 --- a/pystac/summaries.py +++ b/src/pystac/summaries.py @@ -3,7 +3,7 @@ from abc import abstractmethod from collections.abc import Iterable from enum import Enum -from functools import lru_cache +from functools import cache from typing import ( TYPE_CHECKING, Any, @@ -93,7 +93,7 @@ def __repr__(self) -> str: return self.to_dict().__repr__() -@lru_cache(maxsize=None) +@cache def _get_fields_json(url: str | None) -> dict[str, Any]: if url is None: import importlib.resources diff --git a/pystac/utils.py b/src/pystac/utils.py similarity index 97% rename from pystac/utils.py rename to src/pystac/utils.py index 589e905e2..978a4fcc1 100644 --- a/pystac/utils.py +++ b/src/pystac/utils.py @@ -3,10 +3,11 @@ import os import posixpath from collections.abc import Callable -from datetime import datetime, timezone +from datetime import UTC, datetime from enum import Enum from typing import ( Any, + Literal, TypeAlias, TypeVar, cast, @@ -16,10 +17,20 @@ from pystac.errors import RequiredPropertyMissing +from .constants import STAC_OBJECT_TYPE +from .version import __version__ + #: HREF string or path-like object. HREF: TypeAlias = str | os.PathLike[str] +def get_stac_type(type: STAC_OBJECT_TYPE) -> Literal["Catalog", "Collection", "Item"]: + if type == "Feature": + return "Item" + else: + return type + + def make_posix_style(href: HREF) -> str: """Converts double backslashes and single backslashes to single forward slashes for converting Windows paths to Posix style. @@ -419,7 +430,7 @@ def datetime_to_str(dt: datetime, timespec: str = "auto") -> str: str: The ISO8601 (RFC 3339) formatted string representing the datetime. """ if dt.tzinfo is None: - dt = dt.replace(tzinfo=timezone.utc) + dt = dt.replace(tzinfo=UTC) timestamp = dt.isoformat(timespec=timespec) zulu = "+00:00" @@ -448,7 +459,7 @@ def str_to_datetime(s: str) -> datetime: def now_in_utc() -> datetime: """Returns a datetime value of now with the UTC timezone applied""" - return datetime.now(timezone.utc) + return datetime.now(UTC) def now_to_rfc3339_str() -> str: @@ -623,3 +634,7 @@ def _is_url(href: str) -> bool: """Checks if an HREF is a url rather than a local path""" parsed = safe_urlparse(href) return parsed.scheme not in ["", "file"] + + +def get_user_agent() -> str: + return f"pystac/{__version__}" diff --git a/pystac/__init__.py b/src/pystac/v1/__init__.py similarity index 100% rename from pystac/__init__.py rename to src/pystac/v1/__init__.py diff --git a/pystac/client.py b/src/pystac/v1/client.py similarity index 100% rename from pystac/client.py rename to src/pystac/v1/client.py diff --git a/pystac/provider.py b/src/pystac/v1/provider.py similarity index 100% rename from pystac/provider.py rename to src/pystac/v1/provider.py diff --git a/pystac/static/__init__.py b/src/pystac/v1/static/__init__.py similarity index 100% rename from pystac/static/__init__.py rename to src/pystac/v1/static/__init__.py diff --git a/pystac/static/fields-normalized.json b/src/pystac/v1/static/fields-normalized.json similarity index 100% rename from pystac/static/fields-normalized.json rename to src/pystac/v1/static/fields-normalized.json diff --git a/pystac/validation/__init__.py b/src/pystac/validation/__init__.py similarity index 98% rename from pystac/validation/__init__.py rename to src/pystac/validation/__init__.py index a179976af..f33206079 100644 --- a/pystac/validation/__init__.py +++ b/src/pystac/validation/__init__.py @@ -1,5 +1,12 @@ from __future__ import annotations +import warnings + +warnings.warn( + "pystac.validation` is deprecated. Use `pystac.validator` instead", + DeprecationWarning, +) + from collections.abc import Iterable, Mapping from typing import TYPE_CHECKING, Any, cast diff --git a/pystac/validation/jsonschemas/__init__.py b/src/pystac/validation/jsonschemas/__init__.py similarity index 100% rename from pystac/validation/jsonschemas/__init__.py rename to src/pystac/validation/jsonschemas/__init__.py diff --git a/pystac/validation/jsonschemas/geojson/Feature.json b/src/pystac/validation/jsonschemas/geojson/Feature.json similarity index 100% rename from pystac/validation/jsonschemas/geojson/Feature.json rename to src/pystac/validation/jsonschemas/geojson/Feature.json diff --git a/pystac/validation/jsonschemas/geojson/Geometry.json b/src/pystac/validation/jsonschemas/geojson/Geometry.json similarity index 100% rename from pystac/validation/jsonschemas/geojson/Geometry.json rename to src/pystac/validation/jsonschemas/geojson/Geometry.json diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json new file mode 100644 index 000000000..94ec087a2 --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/bands.json", + "title": "Bands Field", + "type": "object", + "properties": { + "bands": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "allOf": [ + { + "$ref": "common.json" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json new file mode 100644 index 000000000..cc2f49a61 --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/basics.json", + "title": "Basic Descriptive Fields", + "type": "object", + "properties": { + "title": { + "title": "Title", + "description": "A human-readable title describing the entity.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "Detailed multi-line description to fully explain the entity.", + "type": "string", + "minLength": 1 + }, + "keywords": { + "title": "Keywords", + "description": "List of keywords describing the entity.", + "type": "array", + "items": { + "type": "string" + } + }, + "roles": { + "title": "Roles", + "type": "array", + "items": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json new file mode 100644 index 000000000..ca7efc15e --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/catalog-spec/json-schema/catalog.json", + "title": "STAC Catalog Specification", + "description": "This object represents Catalogs in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/catalog" + }, + { + "$ref": "../../item-spec/json-schema/common.json" + } + ], + "definitions": { + "catalog": { + "title": "STAC Catalog", + "type": "object", + "$comment": "title and description is validated through the common metadata.", + "required": [ + "stac_version", + "type", + "id", + "description", + "links" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.1.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, + "type": { + "title": "Type of STAC entity", + "const": "Catalog" + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "links": { + "$ref": "../../item-spec/json-schema/item.json#/definitions/links" + } + } + } + } +} diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json new file mode 100644 index 000000000..e9ef74311 --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json @@ -0,0 +1,230 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/collection-spec/json-schema/collection.json", + "title": "STAC Collection Specification", + "description": "This object represents Collections in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/collection" + }, + { + "$ref": "../../item-spec/json-schema/common.json" + } + ], + "definitions": { + "collection": { + "title": "STAC Collection", + "description": "These are the fields specific to a STAC Collection.", + "type": "object", + "$comment": "title, description, keywords, providers and license is validated through the common metadata.", + "required": [ + "stac_version", + "type", + "id", + "description", + "license", + "extent", + "links" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.1.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, + "type": { + "title": "Type of STAC entity", + "const": "Collection" + }, + "id": { + "title": "Identifier", + "type": "string", + "minLength": 1 + }, + "extent": { + "title": "Extents", + "type": "object", + "required": [ + "spatial", + "temporal" + ], + "properties": { + "spatial": { + "title": "Spatial extent object", + "type": "object", + "required": [ + "bbox" + ], + "properties": { + "bbox": { + "title": "Spatial extents", + "type": "array", + "oneOf": [ + { + "minItems": 1, + "maxItems": 1 + }, + { + "minItems": 3 + } + ], + "items": { + "title": "Spatial extent", + "type": "array", + "oneOf": [ + { + "minItems": 4, + "maxItems": 4 + }, + { + "minItems": 6, + "maxItems": 6 + } + ], + "items": { + "type": "number" + } + } + } + } + }, + "temporal": { + "title": "Temporal extent object", + "type": "object", + "required": [ + "interval" + ], + "properties": { + "interval": { + "title": "Temporal extents", + "type": "array", + "minItems": 1, + "items": { + "title": "Temporal extent", + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + } + } + } + } + } + } + }, + "assets": { + "$ref": "../../item-spec/json-schema/item.json#/definitions/assets" + }, + "item_assets": { + "additionalProperties": { + "allOf": [ + { + "type": "object", + "minProperties": 2, + "properties": { + "href": { + "title": "Disallow href", + "not": {} + }, + "title": { + "title": "Asset title", + "type": "string" + }, + "description": { + "title": "Asset description", + "type": "string" + }, + "type": { + "title": "Asset type", + "type": "string" + }, + "roles": { + "title": "Asset roles", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "$ref": "../../item-spec/json-schema/common.json" + } + ] + } + }, + "links": { + "$ref": "../../item-spec/json-schema/item.json#/definitions/links" + }, + "summaries": { + "$ref": "#/definitions/summaries" + } + } + }, + "summaries": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "title": "JSON Schema", + "type": "object", + "minProperties": 1, + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema" + } + ] + }, + { + "title": "Range", + "type": "object", + "required": [ + "minimum", + "maximum" + ], + "properties": { + "minimum": { + "title": "Minimum value", + "type": [ + "number", + "string" + ] + }, + "maximum": { + "title": "Maximum value", + "type": [ + "number", + "string" + ] + } + } + }, + { + "title": "Set of values", + "type": "array", + "minItems": 1, + "items": { + "description": "For each field only the original data type of the property can occur (except for arrays), but we can't validate that in JSON Schema yet. See the sumamry description in the STAC specification for details." + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json new file mode 100644 index 000000000..2b947b03d --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/commonjson", + "title": "STAC Common Metadata", + "type": "object", + "description": "This schema includes all common metadata fields.", + "allOf": [ + { + "$ref": "basics.json" + }, + { + "$ref": "bands.json" + }, + { + "$ref": "datetime.json" + }, + { + "$ref": "data-values.json" + }, + { + "$ref": "instrument.json" + }, + { + "$ref": "licensing.json" + }, + { + "$ref": "provider.json" + } + ] +} diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json new file mode 100644 index 000000000..55fea87cf --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/data-values.json#", + "title": "Fields related to data values", + "type": "object", + "properties": { + "data_type": { + "title": "Data type of the values", + "type": "string", + "enum": [ + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "float16", + "float32", + "float64", + "cint16", + "cint32", + "cfloat32", + "cfloat64", + "other" + ] + }, + "nodata": { + "title": "No data value", + "oneOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "nan", + "inf", + "-inf" + ] + } + ] + }, + "statistics": { + "title": "Statistics", + "type": "object", + "minProperties": 1, + "properties": { + "minimum": { + "title": "Minimum value of all the data values", + "type": "number" + }, + "maximum": { + "title": "Maximum value of all the data values", + "type": "number" + }, + "mean": { + "title": "Mean value of all the data values", + "type": "number" + }, + "stddev": { + "title": "Standard deviation value of all the data values", + "type": "number" + }, + "count": { + "title": "Total number of all data values", + "type": "integer", + "minimum": 0 + }, + "valid_percent": { + "title": "Percentage of valid (not nodata) values", + "type": "number", + "minimum": 0, + "maximum": 100 + } + } + }, + "unit": { + "title": "Unit denomination of the data value", + "type": "string" + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json new file mode 100644 index 000000000..b4bc9261f --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/datetime.json", + "title": "Date and Time Fields", + "type": "object", + "dependencies": { + "start_datetime": { + "required": [ + "end_datetime" + ] + }, + "end_datetime": { + "required": [ + "start_datetime" + ] + } + }, + "properties": { + "datetime": { + "title": "Date and Time", + "description": "The searchable date/time of the data, in UTC (Formatted in RFC 3339) ", + "type": ["string", "null"], + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "start_datetime": { + "title": "Start Date and Time", + "description": "The searchable start date/time of the data, in UTC (Formatted in RFC 3339) ", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "end_datetime": { + "title": "End Date and Time", + "description": "The searchable end date/time of the data, in UTC (Formatted in RFC 3339) ", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "created": { + "title": "Creation Time", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + }, + "updated": { + "title": "Last Update Time", + "type": "string", + "format": "date-time", + "pattern": "(\\+00:00|Z)$" + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json new file mode 100644 index 000000000..ab9742713 --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/instrument.json", + "title": "Instrument Fields", + "type": "object", + "properties": { + "platform": { + "title": "Platform", + "type": "string" + }, + "instruments": { + "title": "Instruments", + "type": "array", + "items": { + "type": "string" + } + }, + "constellation": { + "title": "Constellation", + "type": "string" + }, + "mission": { + "title": "Mission", + "type": "string" + }, + "gsd": { + "title": "Ground Sample Distance", + "type": "number", + "exclusiveMinimum": 0 + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json new file mode 100644 index 000000000..1c8c945b9 --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json @@ -0,0 +1,347 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/item.json", + "title": "STAC Item", + "type": "object", + "description": "This object represents the metadata for an item in a SpatioTemporal Asset Catalog.", + "allOf": [ + { + "$ref": "#/definitions/core" + } + ], + "definitions": { + "core": { + "allOf": [ + { + "$ref": "https://geojson.org/schema/Feature.json" + }, + { + "oneOf": [ + { + "type": "object", + "required": [ + "geometry", + "bbox" + ], + "properties": { + "geometry": { + "$ref": "https://geojson.org/schema/Geometry.json" + }, + "bbox": { + "type": "array", + "oneOf": [ + { + "minItems": 4, + "maxItems": 4 + }, + { + "minItems": 6, + "maxItems": 6 + } + ], + "items": { + "type": "number" + } + } + } + }, + { + "type": "object", + "required": [ + "geometry" + ], + "properties": { + "geometry": { + "type": "null" + }, + "bbox": { + "not": {} + } + } + } + ] + }, + { + "type": "object", + "required": [ + "stac_version", + "id", + "links", + "assets", + "properties" + ], + "properties": { + "stac_version": { + "title": "STAC version", + "type": "string", + "const": "1.1.0" + }, + "stac_extensions": { + "title": "STAC extensions", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Reference to a JSON Schema", + "type": "string", + "format": "iri" + } + }, + "id": { + "title": "Provider ID", + "description": "Provider item ID", + "type": "string", + "minLength": 1 + }, + "links": { + "$ref": "#/definitions/links" + }, + "assets": { + "$ref": "#/definitions/assets" + }, + "properties": { + "allOf": [ + { + "$ref": "common.json" + }, + { + "anyOf": [ + { + "required": [ + "datetime" + ], + "properties": { + "datetime": { + "not": { + "type": "null" + } + } + } + }, + { + "required": [ + "datetime", + "start_datetime", + "end_datetime" + ] + } + ] + } + ] + } + }, + "$comment": "Rules enforcement for STAC Item", + "allOf": [ + { + "if": { + "properties": { + "links": { + "contains": { + "required": [ + "rel" + ], + "properties": { + "rel": { + "const": "collection" + } + } + } + } + } + }, + "then": { + "required": [ + "collection" + ], + "properties": { + "collection": { + "title": "Collection ID", + "description": "The ID of the STAC Collection this Item references to.", + "type": "string", + "minLength": 1 + } + } + }, + "else": { + "properties": { + "collection": { + "not": {} + } + } + } + }, + { + "$comment": "The if-then-else below checks whether the bands field is given in assets or not. If not, allows bands in properties (then), otherwise, disallows bands in properties (else).", + "if": { + "$comment": "If there is no asset with bands...", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "object", + "additionalProperties": { + "properties": { + "bands": false + } + } + } + } + }, + "then": { + "$comment": "... then bands are not allowed in properties...", + "properties": { + "properties": { + "properties": { + "bands": false + } + } + } + }, + "else": { + "$comment": "... otherwise bands are allowed in properties.", + "properties": { + "properties": { + "$ref": "bands.json" + } + } + } + } + ] + } + ] + }, + "links": { + "title": "Item links", + "description": "Links to item relations", + "type": "array", + "items": { + "$ref": "#/definitions/link" + } + }, + "link": { + "allOf": [ + { + "type": "object", + "required": [ + "rel", + "href" + ], + "properties": { + "href": { + "title": "Link reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "rel": { + "title": "Link relation type", + "type": "string", + "minLength": 1 + }, + "type": { + "title": "Link type", + "type": "string" + }, + "title": { + "title": "Link title", + "type": "string" + }, + "method": { + "title": "Link method", + "type": "string", + "pattern": "^[A-Z]+$", + "default": "GET" + }, + "headers": { + "title": "Link headers", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "body": { + "title": "Link body", + "$comment": "Any type is allowed." + } + }, + "$comment": "Link with relationship `self` must be absolute URI", + "if": { + "properties": { + "rel": { + "const": "self" + } + } + }, + "then": { + "properties": { + "href": { + "format": "iri" + } + } + } + }, + { + "$ref": "common.json" + } + ] + }, + "assets": { + "title": "Asset links", + "description": "Links to assets", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/asset" + } + }, + "asset": { + "allOf": [ + { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "title": "Asset reference", + "type": "string", + "format": "iri-reference", + "minLength": 1 + }, + "title": { + "title": "Asset title", + "type": "string" + }, + "description": { + "title": "Asset description", + "type": "string" + }, + "type": { + "title": "Asset type", + "type": "string" + }, + "roles": { + "title": "Asset roles", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "$ref": "common.json" + } + ] + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json new file mode 100644 index 000000000..8f650712d --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/licensing.json", + "title": "Licensing Fields", + "type": "object", + "properties": { + "license": { + "type": "string", + "pattern": "^[\\w\\-\\.\\+]+$" + } + } +} \ No newline at end of file diff --git a/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json new file mode 100644 index 000000000..92a980406 --- /dev/null +++ b/src/pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/provider.json", + "title": "Provider Fields", + "type": "object", + "properties": { + "providers": { + "title": "Providers", + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "Organization name", + "type": "string", + "minLength": 1 + }, + "description": { + "title": "Organization description", + "type": "string" + }, + "roles": { + "title": "Organization roles", + "type": "array", + "items": { + "type": "string", + "enum": [ + "producer", + "licensor", + "processor", + "host" + ] + } + }, + "url": { + "title": "Organization homepage", + "type": "string", + "format": "iri" + } + } + } + } + } +} \ No newline at end of file diff --git a/pystac/validation/local_validator.py b/src/pystac/validation/local_validator.py similarity index 100% rename from pystac/validation/local_validator.py rename to src/pystac/validation/local_validator.py diff --git a/pystac/validation/schema_uri_map.py b/src/pystac/validation/schema_uri_map.py similarity index 100% rename from pystac/validation/schema_uri_map.py rename to src/pystac/validation/schema_uri_map.py diff --git a/pystac/validation/stac_validator.py b/src/pystac/validation/stac_validator.py similarity index 100% rename from pystac/validation/stac_validator.py rename to src/pystac/validation/stac_validator.py diff --git a/src/pystac/validator.py b/src/pystac/validator.py new file mode 100644 index 000000000..052a2d0e3 --- /dev/null +++ b/src/pystac/validator.py @@ -0,0 +1,11 @@ +from typing import Any, Protocol + +from .constants import STAC_OBJECT_TYPE + + +class Validator(Protocol): + def validate_core( + self, type: STAC_OBJECT_TYPE, version: str, data: dict[str, Any] + ) -> None: ... + + def validate_extension(self, extension: str, data: dict[str, Any]) -> None: ... diff --git a/pystac/version.py b/src/pystac/version.py similarity index 100% rename from pystac/version.py rename to src/pystac/version.py diff --git a/src/pystac/writer.py b/src/pystac/writer.py new file mode 100644 index 000000000..88d630367 --- /dev/null +++ b/src/pystac/writer.py @@ -0,0 +1,35 @@ +import json +import urllib.parse +from pathlib import Path +from typing import Any, Protocol + + +class Writer(Protocol): + def put_json(self, data: dict[str, Any], href: str | Path) -> None: ... + def delete(self, href: str | Path) -> None: ... + + +class StandardLibraryWriter: + def put_json(self, data: dict[str, Any], href: str | Path) -> None: + if isinstance(href, Path) or not urllib.parse.urlparse(href).scheme: + with open(href, "w") as f: + json.dump( + data, + f, + ) + else: + raise ValueError("StandardLibraryWriter cannot write to urls") + + def delete(self, href: str | Path) -> None: + if isinstance(href, Path) or not urllib.parse.urlparse(href).scheme: + Path(href).unlink() + else: + raise ValueError("StandardLibraryWriter cannot delete urls") + + +def set_default_writer(writer: Writer) -> None: + global DEFAULT_WRITER + DEFAULT_WRITER = writer # pyright: ignore[reportConstantRedefinition] + + +DEFAULT_WRITER = StandardLibraryWriter() diff --git a/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml b/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml new file mode 100644 index 000000000..cb75f6e95 --- /dev/null +++ b/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml b/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml new file mode 100644 index 000000000..6a841f075 --- /dev/null +++ b/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml @@ -0,0 +1,205 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml new file mode 100644 index 000000000..322c5dada --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path15].yaml b/tests/cassettes/test_examples/test_example_from_dict[path15].yaml new file mode 100644 index 000000000..61eaec859 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path15].yaml @@ -0,0 +1,478 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - proj.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://proj.org/schemas/v0.2/projjson.schema.json + response: + body: + string: '' + headers: + Location: + - https://proj.org/en/latest/schemas/v0.2/projjson.schema.json + status: + code: 302 + message: Found +- request: + body: null + headers: + Connection: + - close + Host: + - proj.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://proj.org/en/latest/schemas/v0.2/projjson.schema.json + response: + body: + string: "{\n \"$id\": \"https://proj.org/schemas/v0.2/projjson.schema.json\",\n + \ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"description\": + \"Schema for PROJJSON (v0.2.1)\",\n \"$comment\": \"This document is copyright + Even Rouault and PROJ contributors, 2019-2020, and subject to the MIT license. + This file exists both in data/ and in schemas/vXXX/. Keep both in sync. And + if changing the value of $id, change PROJJSON_CURRENT_VERSION accordingly + in io.cpp\",\n\n \"oneOf\": [\n { \"$ref\": \"#/definitions/crs\" },\n + \ { \"$ref\": \"#/definitions/datum\" },\n { \"$ref\": \"#/definitions/datum_ensemble\" + },\n { \"$ref\": \"#/definitions/ellipsoid\" },\n { \"$ref\": \"#/definitions/prime_meridian\" + },\n { \"$ref\": \"#/definitions/single_operation\" },\n { \"$ref\": + \"#/definitions/concatenated_operation\" }\n ],\n\n \"definitions\": {\n\n + \ \"abridged_transformation\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"AbridgedTransformation\"] },\n \"name\": { + \"type\": \"string\" },\n \"method\": { \"$ref\": \"#/definitions/method\" + },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"method\", \"parameters\" ],\n + \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"axis\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"Axis\"] },\n \"name\": { \"type\": \"string\" },\n \"abbreviation\": + { \"type\": \"string\" },\n \"direction\": { \"type\": \"string\",\n + \ \"enum\": [ \"north\",\n \"northNorthEast\",\n + \ \"northEast\",\n \"eastNorthEast\",\n + \ \"east\",\n \"eastSouthEast\",\n + \ \"southEast\",\n \"southSouthEast\",\n + \ \"south\",\n \"southSouthWest\",\n + \ \"southWest\",\n \"westSouthWest\",\n + \ \"west\",\n \"westNorthWest\",\n + \ \"northWest\",\n \"northNorthWest\",\n + \ \"up\",\n \"down\",\n + \ \"geocentricX\",\n \"geocentricY\",\n + \ \"geocentricZ\",\n \"columnPositive\",\n + \ \"columnNegative\",\n \"rowPositive\",\n + \ \"rowNegative\",\n \"displayRight\",\n + \ \"displayLeft\",\n \"displayUp\",\n + \ \"displayDown\",\n \"forward\",\n + \ \"aft\",\n \"port\",\n + \ \"starboard\",\n \"clockwise\",\n + \ \"counterClockwise\",\n \"towards\",\n + \ \"awayFrom\",\n \"future\",\n + \ \"past\",\n \"unspecified\" + ] },\n \"unit\": { \"$ref\": \"#/definitions/unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"abbreviation\", \"direction\" + ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"bbox\": + {\n \"type\": \"object\",\n \"properties\": {\n \"east_longitude\": + { \"type\": \"number\" },\n \"west_longitude\": { \"type\": \"number\" + },\n \"south_latitude\": { \"type\": \"number\" },\n \"north_latitude\": + { \"type\": \"number\" }\n },\n \"required\" : [ \"east_longitude\", + \"west_longitude\",\n \"south_latitude\", \"north_latitude\" + ],\n \"additionalProperties\": false\n },\n\n \"bound_crs\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"BoundCRS\"] },\n \"source_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"transformation\": + { \"$ref\": \"#/definitions/abridged_transformation\" }\n },\n \"required\" + : [ \"source_crs\", \"target_crs\", \"transformation\" ],\n \"additionalProperties\": + false\n },\n\n \"compound_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"CompoundCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"components\": {\n + \ \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/crs\" + }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"components\" ],\n \"additionalProperties\": false\n },\n\n \"concatenated_operation\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"ConcatenatedOperation\"] },\n \"name\": { \"type\": \"string\" },\n + \ \"source_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": + { \"$ref\": \"#/definitions/crs\" },\n \"steps\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/single_operation\" + }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"source_crs\", \"target_crs\", \"steps\" ],\n \"additionalProperties\": + false\n },\n\n \"conversion\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"Conversion\"] },\n \"name\": { \"type\": \"string\" + },\n \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", + \"method\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"coordinate_system\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"CoordinateSystem\"] },\n \"name\": { \"type\": \"string\" },\n \"subtype\": + { \"type\": \"string\",\n \"enum\": [\"Cartesian\",\n + \ \"spherical\",\n \"ellipsoidal\",\n + \ \"vertical\",\n \"ordinal\",\n + \ \"parametric\",\n \"TemporalDateTime\",\n + \ \"TemporalCount\",\n \"TemporalMeasure\"] + \ },\n \"axis\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/axis\" }\n },\n \"id\": { \"$ref\": + \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"subtype\", \"axis\" ],\n \"allOf\": + [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"crs\": {\n \"oneOf\": + [\n { \"$ref\": \"#/definitions/bound_crs\" },\n { \"$ref\": + \"#/definitions/compound_crs\" },\n { \"$ref\": \"#/definitions/derived_engineering_crs\" + },\n { \"$ref\": \"#/definitions/derived_geodetic_crs\" },\n { + \"$ref\": \"#/definitions/derived_parametric_crs\" },\n { \"$ref\": + \"#/definitions/derived_projected_crs\" },\n { \"$ref\": \"#/definitions/derived_temporal_crs\" + },\n { \"$ref\": \"#/definitions/derived_vertical_crs\" },\n { + \"$ref\": \"#/definitions/engineering_crs\" },\n { \"$ref\": \"#/definitions/geodetic_crs\" + },\n { \"$ref\": \"#/definitions/parametric_crs\" },\n { \"$ref\": + \"#/definitions/projected_crs\" },\n { \"$ref\": \"#/definitions/temporal_crs\" + },\n { \"$ref\": \"#/definitions/vertical_crs\" }\n ]\n },\n\n + \ \"datum\": {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/geodetic_reference_frame\" + },\n { \"$ref\": \"#/definitions/vertical_reference_frame\" },\n { + \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" },\n { + \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" },\n { + \"$ref\": \"#/definitions/temporal_datum\" },\n { \"$ref\": \"#/definitions/parametric_datum\" + },\n { \"$ref\": \"#/definitions/engineering_datum\" }\n ]\n },\n\n + \ \"datum_ensemble\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"DatumEnsemble\"] },\n \"name\": { \"type\": + \"string\" },\n \"members\": {\n \"type\": \"array\",\n + \ \"items\": {\n \"type\": \"object\",\n \"properties\": + {\n \"name\": { \"type\": \"string\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": + \"#/definitions/ids\" }\n },\n \"required\" + : [ \"name\" ],\n \"allOf\": [\n { \"$ref\": + \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": + false\n }\n },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" + },\n \"accuracy\": { \"type\": \"string\" },\n \"id\": { \"$ref\": + \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"members\", \"accuracy\" ],\n + \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"derived_engineering_crs\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\",\n + \ \"enum\": [\"DerivedEngineeringCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": \"#/definitions/engineering_crs\" + },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n + \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"derived_geodetic_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"DerivedGeodeticCRS\",\n \"DerivedGeographicCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"derived_parametric_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"DerivedParametricCRS\"] },\n \"name\": { \"type\": \"string\" },\n + \ \"base_crs\": { \"$ref\": \"#/definitions/parametric_crs\" },\n \"conversion\": + { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": + { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_projected_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedProjectedCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/projected_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"derived_temporal_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"DerivedTemporalCRS\"] },\n \"name\": { \"type\": \"string\" },\n + \ \"base_crs\": { \"$ref\": \"#/definitions/temporal_crs\" },\n \"conversion\": + { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": + { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_vertical_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedVerticalCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/vertical_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"dynamic_geodetic_reference_frame\": {\n \"type\": + \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/geodetic_reference_frame\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"DynamicGeodeticReferenceFrame\"] },\n \"name\": {},\n \"anchor\": + {},\n \"ellipsoid\": {},\n \"prime_meridian\": {},\n \"frame_reference_epoch\": + { \"type\": \"number\" },\n \"deformation_model\": { \"type\": \"string\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"ellipsoid\", \"frame_reference_epoch\" ],\n \"additionalProperties\": + false\n },\n\n \"dynamic_vertical_reference_frame\": {\n \"type\": + \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/vertical_reference_frame\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"DynamicVerticalReferenceFrame\"] },\n \"name\": {},\n \"anchor\": + {},\n \"frame_reference_epoch\": { \"type\": \"number\" },\n \"deformation_model\": + { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"frame_reference_epoch\" ],\n \"additionalProperties\": false\n },\n\n + \ \"ellipsoid\": {\n \"type\": \"object\",\n \"oneOf\":[\n {\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] + },\n \"name\": { \"type\": \"string\" },\n \"semi_major_axis\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"semi_minor_axis\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"semi_major_axis\", + \"semi_minor_axis\" ],\n \"additionalProperties\": false\n },\n + \ {\n \"properties\": {\n \"$schema\" : { \"type\": + \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] + },\n \"name\": { \"type\": \"string\" },\n \"semi_major_axis\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"inverse_flattening\": + { \"type\": \"number\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"required\" : [ \"name\", \"semi_major_axis\", \"inverse_flattening\" + ],\n \"additionalProperties\": false\n },\n {\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": + { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] },\n \"name\": + { \"type\": \"string\" },\n \"radius\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" + : [ \"name\", \"radius\" ],\n \"additionalProperties\": false\n }\n + \ ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ]\n },\n\n \"engineering_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": + \"#/definitions/engineering_datum\" },\n \"coordinate_system\": { \"$ref\": + \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": + {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n + \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" + : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n + \ \"engineering_datum\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringDatum\"] + },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": + \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" + ],\n \"additionalProperties\": false\n },\n\n \"geodetic_crs\": + {\n \"type\": \"object\",\n \"properties\": {\n \"type\": + { \"type\": \"string\", \"enum\": [\"GeodeticCRS\", \"GeographicCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"datum\": {\n \"oneOf\": + [\n { \"$ref\": \"#/definitions/geodetic_reference_frame\" + },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" + }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" + ],\n \"description\": \"One and only one of datum and datum_ensemble + must be provided\",\n \"allOf\": [\n { \"$ref\": \"#/definitions/object_usage\" + },\n { \"$ref\": \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"geodetic_reference_frame\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"GeodeticReferenceFrame\"] },\n \"name\": { \"type\": \"string\" + },\n \"anchor\": { \"type\": \"string\" },\n \"ellipsoid\": + { \"$ref\": \"#/definitions/ellipsoid\" },\n \"prime_meridian\": { + \"$ref\": \"#/definitions/prime_meridian\" },\n \"$schema\" : {},\n + \ \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"ellipsoid\" ],\n \"additionalProperties\": + false\n },\n\n \"id\": {\n \"type\": \"object\",\n \"properties\": + {\n \"authority\": { \"type\": \"string\" },\n \"code\": {\n + \ \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"integer\" } + ]\n }\n },\n \"required\" : [ \"authority\", \"code\" ],\n + \ \"additionalProperties\": false\n },\n\n \"ids\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/id\" }\n },\n\n + \ \"method\": {\n \"type\": \"object\",\n \"properties\": {\n + \ \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"OperationMethod\"]},\n \"name\": { \"type\": + \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\" + ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"id_ids_mutually_exclusive\": + {\n \"not\": {\n \"type\": \"object\",\n \"required\": + [ \"id\", \"ids\" ]\n }\n },\n\n \"one_and_only_one_of_datum_or_datum_ensemble\": + {\n \"allOf\": [\n {\n \"not\": {\n \"type\": + \"object\",\n \"required\": [ \"datum\", \"datum_ensemble\" + ]\n }\n },\n {\n \"oneOf\": [\n { + \"type\": \"object\", \"required\": [\"datum\"] },\n { \"type\": + \"object\", \"required\": [\"datum_ensemble\"] }\n ]\n }\n + \ ]\n },\n\n \"object_usage\": {\n \"anyOf\": [\n {\n + \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"scope\": { \"type\": \"string\" + },\n \"area\": { \"type\": \"string\" },\n \"bbox\": + { \"$ref\": \"#/definitions/bbox\" },\n \"remarks\": { \"type\": + \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"allOf\": [\n { + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n },\n + \ {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"usages\": { \"$ref\": \"#/definitions/usages\" + },\n \"remarks\": { \"type\": \"string\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ]\n }\n ]\n },\n\n \"parameter_value\": {\n \"type\": + \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"ParameterValue\"] + },\n \"name\": { \"type\": \"string\" },\n \"value\": {\n \"oneOf\": + [\n { \"type\": \"string\" },\n { \"type\": \"number\" + }\n ]\n },\n \"unit\": { \"$ref\": \"#/definitions/unit\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", + \"value\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"parametric_crs\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"ParametricCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"datum\": + { \"$ref\": \"#/definitions/parametric_datum\" },\n \"coordinate_system\": + { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": + false\n },\n\n \"parametric_datum\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"ParametricDatum\"] + },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": + \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" + ],\n \"additionalProperties\": false\n },\n\n \"prime_meridian\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"PrimeMeridian\"] },\n \"name\": { \"type\": \"string\" },\n \"longitude\": + { \"$ref\": \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\" ],\n \"allOf\": [\n { + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": + false\n },\n\n \"single_operation\": {\n \"oneOf\": [\n { + \"$ref\": \"#/definitions/conversion\" },\n { \"$ref\": \"#/definitions/transformation\" + }\n ]\n },\n\n \"projected_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"ProjectedCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": + { \"$ref\": \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": + \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": + \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": + {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n + \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" + : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"TemporalCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": + \"#/definitions/temporal_datum\" },\n \"coordinate_system\": { \"$ref\": + \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": + {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n + \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" + : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n + \ \"temporal_datum\": {\n \"type\": \"object\",\n \"allOf\": [{ + \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": + { \"type\": \"string\", \"enum\": [\"TemporalDatum\"] },\n \"name\": + { \"type\": \"string\" },\n \"calendar\": { \"type\": \"string\" },\n + \ \"time_origin\": { \"type\": \"string\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"calendar\" ],\n \"additionalProperties\": + false\n },\n\n \"transformation\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"Transformation\"] + },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": { + \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" },\n + \ \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"source_crs\", \"target_crs\", + \"method\", \"parameters\" ],\n \"additionalProperties\": false\n },\n\n + \ \"unit\": {\n \"oneOf\": [\n {\n \"type\": \"string\",\n + \ \"enum\": [\"metre\", \"degree\", \"unity\"]\n },\n {\n + \ \"type\": \"object\",\n \"properties\": {\n \"type\": + { \"type\": \"string\",\n \"enum\": [\"LinearUnit\", \"AngularUnit\", + \"ScaleUnit\",\n \"TimeUnit\", \"ParametricUnit\", + \"Unit\"] },\n \"name\": { \"type\": \"string\" },\n \"conversion_factor\": + { \"type\": \"number\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"required\" : [ \"type\", \"name\" ],\n \"allOf\": [\n { + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": + false\n }\n ]\n },\n\n \"usages\": {\n \"type\": \"array\",\n + \ \"items\": {\n \"type\": \"object\",\n \"properties\": + {\n \"scope\": { \"type\": \"string\" },\n \"area\": + { \"type\": \"string\" },\n \"bbox\": { \"$ref\": \"#/definitions/bbox\" + }\n },\n \"additionalProperties\": false\n }\n },\n\n + \ \"value_and_unit\": {\n \"type\": \"object\",\n \"properties\": + {\n \"value\": { \"type\": \"number\" },\n \"unit\": { \"$ref\": + \"#/definitions/unit\" }\n },\n \"required\" : [ \"value\", \"unit\" + ],\n \"additionalProperties\": false\n },\n\n \"value_in_degree_or_value_and_unit\": + {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": + \"#/definitions/value_and_unit\" }\n ]\n },\n\n \"value_in_metre_or_value_and_unit\": + {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": + \"#/definitions/value_and_unit\" }\n ]\n },\n\n \"vertical_crs\": + {\n \"type\": \"object\",\n \"properties\": {\n \"type\": + { \"type\": \"string\", \"enum\": [\"VerticalCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"datum\": {\n \"oneOf\": [\n + \ { \"$ref\": \"#/definitions/vertical_reference_frame\" },\n + \ { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" + }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"geoid_model\": {\n \"type\": \"object\",\n \"properties\": + {\n \"name\": { \"type\": \"string\" },\n \"interpolation_crs\": + { \"$ref\": \"#/definitions/crs\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + }\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": + false\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\"],\n + \ \"description\": \"One and only one of datum and datum_ensemble must + be provided\",\n \"allOf\": [\n { \"$ref\": \"#/definitions/object_usage\" + },\n { \"$ref\": \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"vertical_reference_frame\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"VerticalReferenceFrame\"] },\n \"name\": { \"type\": \"string\" + },\n \"anchor\": { \"type\": \"string\" },\n \"$schema\" : {},\n + \ \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\" ],\n \"additionalProperties\": false\n + \ }\n\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path19].yaml b/tests/cassettes/test_examples/test_example_from_dict[path19].yaml new file mode 100644 index 000000000..a43a639fb --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path19].yaml @@ -0,0 +1,559 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - proj.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://proj.org/schemas/v0.7/projjson.schema.json + response: + body: + string: '' + headers: + Location: + - https://proj.org/en/latest/schemas/v0.7/projjson.schema.json + status: + code: 302 + message: Found +- request: + body: null + headers: + Connection: + - close + Host: + - proj.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://proj.org/en/latest/schemas/v0.7/projjson.schema.json + response: + body: + string: "{\n \"$id\": \"https://proj.org/schemas/v0.7/projjson.schema.json\",\n + \ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"description\": + \"Schema for PROJJSON (v0.7)\",\n \"$comment\": \"This document is copyright + Even Rouault and PROJ contributors, 2019-2023, and subject to the MIT license. + This file exists both in data/ and in schemas/vXXX/. Keep both in sync. And + if changing the value of $id, change PROJJSON_DEFAULT_VERSION accordingly + in io.cpp\",\n\n \"oneOf\": [\n { \"$ref\": \"#/definitions/crs\" },\n + \ { \"$ref\": \"#/definitions/datum\" },\n { \"$ref\": \"#/definitions/datum_ensemble\" + },\n { \"$ref\": \"#/definitions/ellipsoid\" },\n { \"$ref\": \"#/definitions/prime_meridian\" + },\n { \"$ref\": \"#/definitions/single_operation\" },\n { \"$ref\": + \"#/definitions/concatenated_operation\" },\n { \"$ref\": \"#/definitions/coordinate_metadata\" + }\n ],\n\n \"definitions\": {\n\n \"abridged_transformation\": {\n \"type\": + \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"AbridgedTransformation\"] + },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": {\n + \ \"$ref\": \"#/definitions/crs\",\n \"$comment\": \"Only + present when the source_crs of the bound_crs does not match the source_crs + of the AbridgedTransformation. No equivalent in WKT\"\n },\n \"method\": + { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", + \"method\", \"parameters\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"axis\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"Axis\"] },\n \"name\": { \"type\": \"string\" },\n \"abbreviation\": + { \"type\": \"string\" },\n \"direction\": { \"type\": \"string\",\n + \ \"enum\": [ \"north\",\n \"northNorthEast\",\n + \ \"northEast\",\n \"eastNorthEast\",\n + \ \"east\",\n \"eastSouthEast\",\n + \ \"southEast\",\n \"southSouthEast\",\n + \ \"south\",\n \"southSouthWest\",\n + \ \"southWest\",\n \"westSouthWest\",\n + \ \"west\",\n \"westNorthWest\",\n + \ \"northWest\",\n \"northNorthWest\",\n + \ \"up\",\n \"down\",\n + \ \"geocentricX\",\n \"geocentricY\",\n + \ \"geocentricZ\",\n \"columnPositive\",\n + \ \"columnNegative\",\n \"rowPositive\",\n + \ \"rowNegative\",\n \"displayRight\",\n + \ \"displayLeft\",\n \"displayUp\",\n + \ \"displayDown\",\n \"forward\",\n + \ \"aft\",\n \"port\",\n + \ \"starboard\",\n \"clockwise\",\n + \ \"counterClockwise\",\n \"towards\",\n + \ \"awayFrom\",\n \"future\",\n + \ \"past\",\n \"unspecified\" + ] },\n \"meridian\": { \"$ref\": \"#/definitions/meridian\" },\n \"unit\": + { \"$ref\": \"#/definitions/unit\" },\n \"minimum_value\": { \"type\": + \"number\" },\n \"maximum_value\": { \"type\": \"number\" },\n \"range_meaning\": + { \"type\": \"string\", \"enum\": [ \"exact\", \"wraparound\"] },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"abbreviation\", \"direction\" + ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"bbox\": + {\n \"type\": \"object\",\n \"properties\": {\n \"east_longitude\": + { \"type\": \"number\" },\n \"west_longitude\": { \"type\": \"number\" + },\n \"south_latitude\": { \"type\": \"number\" },\n \"north_latitude\": + { \"type\": \"number\" }\n },\n \"required\" : [ \"east_longitude\", + \"west_longitude\",\n \"south_latitude\", \"north_latitude\" + ],\n \"additionalProperties\": false\n },\n\n \"bound_crs\": {\n + \ \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"BoundCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"source_crs\": { \"$ref\": + \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"transformation\": { \"$ref\": \"#/definitions/abridged_transformation\" + },\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"source_crs\", \"target_crs\", \"transformation\" ],\n + \ \"additionalProperties\": false\n },\n\n \"compound_crs\": {\n + \ \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"CompoundCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"components\": + \ {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/crs\" + }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"components\" ],\n + \ \"additionalProperties\": false\n },\n\n \"concatenated_operation\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"ConcatenatedOperation\"] },\n \"name\": { \"type\": \"string\" },\n + \ \"source_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": + { \"$ref\": \"#/definitions/crs\" },\n \"steps\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/single_operation\" + }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"source_crs\", \"target_crs\", \"steps\" + ],\n \"additionalProperties\": false\n },\n\n \"conversion\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"Conversion\"] },\n \"name\": { \"type\": \"string\" },\n \"method\": + { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", + \"method\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"coordinate_metadata\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"CoordinateMetadata\"] },\n \"crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"coordinateEpoch\": { \"type\": \"number\" }\n },\n \"required\" + : [ \"crs\" ],\n \"additionalProperties\": false\n },\n\n \"coordinate_system\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"CoordinateSystem\"] },\n \"name\": { \"type\": \"string\" },\n \"subtype\": + { \"type\": \"string\",\n \"enum\": [\"Cartesian\",\n + \ \"spherical\",\n \"ellipsoidal\",\n + \ \"vertical\",\n \"ordinal\",\n + \ \"parametric\",\n \"affine\",\n + \ \"TemporalDateTime\",\n \"TemporalCount\",\n + \ \"TemporalMeasure\"] },\n \"axis\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/axis\" + }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"subtype\", + \"axis\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"crs\": + {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/bound_crs\" },\n + \ { \"$ref\": \"#/definitions/compound_crs\" },\n { \"$ref\": + \"#/definitions/derived_engineering_crs\" },\n { \"$ref\": \"#/definitions/derived_geodetic_crs\" + },\n { \"$ref\": \"#/definitions/derived_parametric_crs\" },\n { + \"$ref\": \"#/definitions/derived_projected_crs\" },\n { \"$ref\": + \"#/definitions/derived_temporal_crs\" },\n { \"$ref\": \"#/definitions/derived_vertical_crs\" + },\n { \"$ref\": \"#/definitions/engineering_crs\" },\n { \"$ref\": + \"#/definitions/geodetic_crs\" },\n { \"$ref\": \"#/definitions/parametric_crs\" + },\n { \"$ref\": \"#/definitions/projected_crs\" },\n { \"$ref\": + \"#/definitions/temporal_crs\" },\n { \"$ref\": \"#/definitions/vertical_crs\" + }\n ]\n },\n\n \"datum\": {\n \"oneOf\": [\n { \"$ref\": + \"#/definitions/geodetic_reference_frame\" },\n { \"$ref\": \"#/definitions/vertical_reference_frame\" + },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" + },\n { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" + },\n { \"$ref\": \"#/definitions/temporal_datum\" },\n { \"$ref\": + \"#/definitions/parametric_datum\" },\n { \"$ref\": \"#/definitions/engineering_datum\" + }\n ]\n },\n\n \"datum_ensemble\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"DatumEnsemble\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"members\": {\n \"type\": + \"array\",\n \"items\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"name\": { \"type\": + \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"required\" : [ \"name\" ],\n \"allOf\": [\n + \ { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n }\n + \ },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" + },\n \"accuracy\": { \"type\": \"string\" },\n \"id\": { \"$ref\": + \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"members\", \"accuracy\" ],\n + \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"deformation_model\": + {\n \"description\": \"Association to a PointMotionOperation\",\n \"type\": + \"object\",\n \"properties\": {\n \"name\": { \"type\": \"string\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" }\n },\n \"required\" + : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"derived_engineering_crs\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\",\n + \ \"enum\": [\"DerivedEngineeringCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": \"#/definitions/engineering_crs\" + },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n + \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_geodetic_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedGeodeticCRS\",\n + \ \"DerivedGeographicCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": \"#/definitions/geodetic_crs\" + },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n + \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_parametric_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedParametricCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/parametric_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_projected_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedProjectedCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/projected_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedTemporalCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/temporal_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_vertical_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedVerticalCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/vertical_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"dynamic_geodetic_reference_frame\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"DynamicGeodeticReferenceFrame\"] + },\n \"name\": {},\n \"anchor\": {},\n \"anchor_epoch\": + {},\n \"ellipsoid\": {},\n \"prime_meridian\": {},\n \"frame_reference_epoch\": + { \"type\": \"number\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"ellipsoid\", \"frame_reference_epoch\" ],\n \"additionalProperties\": + false\n },\n\n \"dynamic_vertical_reference_frame\": {\n \"type\": + \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"DynamicVerticalReferenceFrame\"] },\n \"name\": {},\n \"anchor\": + {},\n \"anchor_epoch\": {},\n \"frame_reference_epoch\": { \"type\": + \"number\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"frame_reference_epoch\" + ],\n \"additionalProperties\": false\n },\n\n \"ellipsoid\": {\n + \ \"type\": \"object\",\n \"oneOf\":[\n {\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": + { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] },\n \"name\": + { \"type\": \"string\" },\n \"semi_major_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + },\n \"semi_minor_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" + : [ \"name\", \"semi_major_axis\", \"semi_minor_axis\" ],\n \"additionalProperties\": + false\n },\n {\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", + \"enum\": [\"Ellipsoid\"] },\n \"name\": { \"type\": \"string\" + },\n \"semi_major_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + },\n \"inverse_flattening\": { \"type\": \"number\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"semi_major_axis\", + \"inverse_flattening\" ],\n \"additionalProperties\": false\n },\n + \ {\n \"properties\": {\n \"$schema\" : { \"type\": + \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] + },\n \"name\": { \"type\": \"string\" },\n \"radius\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"radius\" ],\n \"additionalProperties\": + false\n }\n ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ]\n },\n\n \"engineering_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": + \"#/definitions/engineering_datum\" },\n \"coordinate_system\": { \"$ref\": + \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": + {},\n \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"datum\" ],\n \"additionalProperties\": false\n },\n\n \"engineering_datum\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"EngineeringDatum\"] },\n \"name\": { \"type\": \"string\" },\n \"anchor\": + { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" + ],\n \"additionalProperties\": false\n },\n\n \"geodetic_crs\": + {\n \"type\": \"object\",\n \"properties\": {\n \"type\": + { \"type\": \"string\", \"enum\": [\"GeodeticCRS\", \"GeographicCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"datum\": {\n \"oneOf\": + [\n { \"$ref\": \"#/definitions/geodetic_reference_frame\" + },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" + }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"deformation_models\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/deformation_model\" }\n },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\" ],\n \"description\": \"One and only + one of datum and datum_ensemble must be provided\",\n \"allOf\": [\n + \ { \"$ref\": \"#/definitions/object_usage\" },\n { \"$ref\": + \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"geodetic_reference_frame\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"GeodeticReferenceFrame\"] },\n \"name\": { \"type\": \"string\" + },\n \"anchor\": { \"type\": \"string\" },\n \"anchor_epoch\": + { \"type\": \"number\" },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" + },\n \"prime_meridian\": { \"$ref\": \"#/definitions/prime_meridian\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"ellipsoid\" ],\n + \ \"additionalProperties\": false\n },\n\n \"geoid_model\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"name\": { + \"type\": \"string\" },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" }\n },\n \"required\" + : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"id\": + {\n \"type\": \"object\",\n \"properties\": {\n \"authority\": + { \"type\": \"string\" },\n \"code\": {\n \"oneOf\": [ { \"type\": + \"string\" }, { \"type\": \"integer\" } ]\n },\n \"version\": + {\n \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"number\" + } ]\n },\n \"authority_citation\": { \"type\": \"string\" },\n + \ \"uri\": { \"type\": \"string\" }\n },\n \"required\" : + [ \"authority\", \"code\" ],\n \"additionalProperties\": false\n },\n\n + \ \"ids\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/id\" + }\n },\n\n \"method\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"OperationMethod\"]},\n \"name\": { \"type\": + \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\" + ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"id_ids_mutually_exclusive\": + {\n \"not\": {\n \"type\": \"object\",\n \"required\": + [ \"id\", \"ids\" ]\n }\n },\n\n \"one_and_only_one_of_datum_or_datum_ensemble\": + {\n \"allOf\": [\n {\n \"not\": {\n \"type\": + \"object\",\n \"required\": [ \"datum\", \"datum_ensemble\" + ]\n }\n },\n {\n \"oneOf\": [\n { + \"type\": \"object\", \"required\": [\"datum\"] },\n { \"type\": + \"object\", \"required\": [\"datum_ensemble\"] }\n ]\n }\n + \ ]\n },\n\n \"meridian\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"Meridian\"] },\n \"longitude\": { \"$ref\": + \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": { + \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"longitude\" ],\n \"allOf\": [\n + \ { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"object_usage\": {\n + \ \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"scope\": + { \"type\": \"string\" },\n \"area\": { \"type\": \"string\" },\n + \ \"bbox\": { \"$ref\": \"#/definitions/bbox\" },\n \"vertical_extent\": + { \"$ref\": \"#/definitions/vertical_extent\" },\n \"temporal_extent\": + { \"$ref\": \"#/definitions/temporal_extent\" },\n \"remarks\": + { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"usages\": + { \"$ref\": \"#/definitions/usages\" },\n \"remarks\": { \"type\": + \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"allOf\": [\n { + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n }\n + \ ]\n },\n\n \"parameter_value\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"ParameterValue\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"value\": {\n \"oneOf\": + [\n { \"type\": \"string\" },\n { \"type\": \"number\" + }\n ]\n },\n \"unit\": { \"$ref\": \"#/definitions/unit\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", + \"value\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"parametric_crs\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"ParametricCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"datum\": + { \"$ref\": \"#/definitions/parametric_datum\" },\n \"coordinate_system\": + { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": + false\n },\n\n \"parametric_datum\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"ParametricDatum\"] + },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": + \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": + false\n },\n\n \"point_motion_operation\": {\n \"$comment\": \"Not + implemented in PROJ (at least as of PROJ 9.1)\",\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"PointMotionOperation\"] + },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": { + \"$ref\": \"#/definitions/crs\" },\n \"method\": { \"$ref\": \"#/definitions/method\" + },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"accuracy\": + { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"source_crs\", \"method\", \"parameters\" ],\n \"additionalProperties\": + false\n },\n\n \"prime_meridian\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"PrimeMeridian\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"longitude\": { \"$ref\": + \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": { + \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\" ],\n \"allOf\": [\n { + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": + false\n },\n\n \"single_operation\": {\n \"oneOf\": [\n { + \"$ref\": \"#/definitions/conversion\" },\n { \"$ref\": \"#/definitions/transformation\" + },\n { \"$ref\": \"#/definitions/point_motion_operation\" }\n ]\n + \ },\n\n \"projected_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"ProjectedCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ + \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": + { \"type\": \"string\", \"enum\": [\"TemporalCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"datum\": { \"$ref\": \"#/definitions/temporal_datum\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": + false\n },\n\n \"temporal_datum\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"TemporalDatum\"] + },\n \"name\": { \"type\": \"string\" },\n \"calendar\": { \"type\": + \"string\" },\n \"time_origin\": { \"type\": \"string\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"calendar\" ],\n \"additionalProperties\": + false\n },\n\n \"temporal_extent\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"start\": { \"type\": \"string\" },\n \"end\": + { \"type\": \"string\" }\n },\n \"required\" : [ \"start\", \"end\" + ],\n \"additionalProperties\": false\n },\n\n \"transformation\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"Transformation\"] },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": + { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": + \"#/definitions/crs\" },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"source_crs\", \"target_crs\", \"method\", + \"parameters\" ],\n \"additionalProperties\": false\n },\n\n \"unit\": + {\n \"oneOf\": [\n {\n \"type\": \"string\",\n \"enum\": + [\"metre\", \"degree\", \"unity\"]\n },\n {\n \"type\": \"object\",\n + \ \"properties\": {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"LinearUnit\", \"AngularUnit\", \"ScaleUnit\",\n \"TimeUnit\", + \"ParametricUnit\", \"Unit\"] },\n \"name\": { \"type\": \"string\" + },\n \"conversion_factor\": { \"type\": \"number\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"type\", \"name\" ],\n \"allOf\": + [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n + \ ],\n \"additionalProperties\": false\n }\n ]\n + \ },\n\n \"usages\": {\n \"type\": \"array\",\n \"items\": + {\n \"type\": \"object\",\n \"properties\": {\n \"scope\": + { \"type\": \"string\" },\n \"area\": { \"type\": \"string\" },\n + \ \"bbox\": { \"$ref\": \"#/definitions/bbox\" },\n \"vertical_extent\": + { \"$ref\": \"#/definitions/vertical_extent\" },\n \"temporal_extent\": + { \"$ref\": \"#/definitions/temporal_extent\" }\n },\n \"additionalProperties\": + false\n }\n },\n\n \"value_and_unit\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"value\": { \"type\": \"number\" },\n \"unit\": + { \"$ref\": \"#/definitions/unit\" }\n },\n \"required\" : [ \"value\", + \"unit\" ],\n \"additionalProperties\": false\n },\n\n \"value_in_degree_or_value_and_unit\": + {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": + \"#/definitions/value_and_unit\" }\n ]\n },\n\n \"value_in_metre_or_value_and_unit\": + {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": + \"#/definitions/value_and_unit\" }\n ]\n },\n\n \"vertical_crs\": + {\n \"type\": \"object\",\n \"properties\": {\n \"type\": + { \"type\": \"string\", \"enum\": [\"VerticalCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"datum\": {\n \"oneOf\": [\n + \ { \"$ref\": \"#/definitions/vertical_reference_frame\" },\n + \ { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" + }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"geoid_model\": { \"$ref\": \"#/definitions/geoid_model\" },\n + \ \"geoid_models\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/geoid_model\" }\n },\n \"deformation_models\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/deformation_model\" + }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\"],\n \"description\": + \"One and only one of datum and datum_ensemble must be provided\",\n \"allOf\": + [\n { \"$ref\": \"#/definitions/object_usage\" },\n { \"$ref\": + \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" },\n {\n + \ \"not\": {\n \"type\": \"object\",\n \"required\": + [ \"geoid_model\", \"geoid_models\" ]\n }\n }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"vertical_extent\": + {\n \"type\": \"object\",\n \"properties\": {\n \"minimum\": + { \"type\": \"number\" },\n \"maximum\": { \"type\": \"number\" },\n + \ \"unit\": { \"$ref\": \"#/definitions/unit\" }\n },\n \"required\" + : [ \"minimum\", \"maximum\" ],\n \"additionalProperties\": false\n },\n\n + \ \"vertical_reference_frame\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"VerticalReferenceFrame\"] + },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": + \"string\" },\n \"anchor_epoch\": { \"type\": \"number\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\" ],\n \"additionalProperties\": false\n + \ }\n\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path3].yaml b/tests/cassettes/test_examples/test_example_from_dict[path3].yaml new file mode 100644 index 000000000..df9d3a935 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path3].yaml @@ -0,0 +1,165 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index 64a44ef7b..a8dd37378 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,115 +1,48 @@ -# TODO move all test case code to this file - -import json -import shutil -import uuid -from datetime import datetime from pathlib import Path -from typing import Any +from typing import Any, cast import pytest +from pytest import FixtureRequest -from pystac import Asset, Catalog, Collection, Item, ItemCollection, Link - -from .utils import ARBITRARY_BBOX, ARBITRARY_EXTENT, ARBITRARY_GEOM, TestCases - -HERE = Path(__file__).resolve().parent - - -@pytest.fixture -def catalog() -> Catalog: - return Catalog("test-catalog", "A test catalog") - - -@pytest.fixture -def collection() -> Catalog: - return Collection("test-collection", "A test collection", ARBITRARY_EXTENT) - - -@pytest.fixture -def multi_extent_collection() -> Collection: - # TODO this code is repeated many times; refactor to use this fixture - return Collection.from_file( - TestCases.get_path("data-files/collections/multi-extent.json") - ) - - -@pytest.fixture -def item() -> Item: - return Item("test-item", ARBITRARY_GEOM, ARBITRARY_BBOX, datetime.now(), {}) - +from pystac import Catalog, Item -@pytest.fixture -def asset(item: Item) -> Asset: - item.add_asset("foo", Asset("https://example.tif")) - return item.assets["foo"] +@pytest.fixture(scope="module") +def vcr_config() -> dict[str, Any]: + def scrub_headers(response: dict[str, Any]) -> dict[str, Any]: + retain = ["location"] + response["headers"] = { + key: value + for (key, value) in response["headers"].items() + if key.lower() in retain + } + return response -@pytest.fixture -def link(item: Item) -> Link: - item.add_link(Link(rel="child", target="https://example.tif")) - return item.links[0] + return {"before_record_response": scrub_headers} @pytest.fixture -def test_case_1_catalog() -> Catalog: - return TestCases.case_1() +def data_files_path() -> Path: + return Path(__file__).parent / "data-files" -@pytest.fixture -def test_case_8_collection() -> Collection: - return TestCases.case_8() +@pytest.fixture(params=["v1.0.0", "v1.1.0"]) +def examples_path(request: FixtureRequest, data_files_path: Path) -> Path: + return data_files_path / "examples" / cast(str, request.param) @pytest.fixture -def projection_landsat8_item() -> Item: - path = TestCases.get_path("data-files/projection/example-landsat8.json") - return Item.from_file(path) - - -def get_data_file(rel_path: str) -> str: - return str(HERE / "data-files" / rel_path) +def catalog(examples_path: Path) -> Catalog: + return Catalog.from_file(examples_path / "catalog.json") @pytest.fixture -def sample_item_dict() -> dict[str, Any]: - m = TestCases.get_path("data-files/item/sample-item.json") - with open(m) as f: - item_dict: dict[str, Any] = json.load(f) - return item_dict +def item(examples_path: Path) -> Item: + return Item.from_file(examples_path / "simple-item.json") @pytest.fixture -def sample_item() -> Item: - return Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - - -@pytest.fixture -def sample_item_collection() -> ItemCollection: - return ItemCollection.from_file( - TestCases.get_path("data-files/item-collection/sample-item-collection.json") +def proj_example(examples_path: Path) -> Item: + return Item.from_file( + examples_path / "extensions-collection" / "proj-example" / "proj-example.json" ) - - -@pytest.fixture -def sample_items(sample_item_collection: ItemCollection) -> list[Item]: - return list(sample_item_collection) - - -@pytest.fixture(scope="function") -def tmp_asset(tmp_path: Path) -> Asset: - """Copy the entirety of test-case-2 to tmp and""" - src = get_data_file("catalogs/test-case-2") - dst = str(tmp_path / str(uuid.uuid4())) - shutil.copytree(src, dst) - - catalog = Catalog.from_file(f"{dst}/catalog.json") - item = next(catalog.get_items(recursive=True)) - return next(v for v in item.assets.values()) - - -@pytest.fixture(autouse=True) -def clear_validator() -> None: - from pystac.validation import RegisteredValidator - - RegisteredValidator._validator = None diff --git a/tests/data-files/examples/v1.0.0/catalog.json b/tests/data-files/examples/v1.0.0/catalog.json new file mode 100644 index 000000000..b5910be3c --- /dev/null +++ b/tests/data-files/examples/v1.0.0/catalog.json @@ -0,0 +1,43 @@ +{ + "id": "examples", + "type": "Catalog", + "title": "Example Catalog", + "stac_version": "1.0.0", + "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.", + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "./extensions-collection/collection.json", + "type": "application/json", + "title": "Collection Demonstrating STAC Extensions" + }, + { + "rel": "child", + "href": "./collection-only/collection.json", + "type": "application/json", + "title": "Collection with no items (standalone)" + }, + { + "rel": "child", + "href": "./collection-only/collection-with-schemas.json", + "type": "application/json", + "title": "Collection with no items (standalone with JSON Schemas)" + }, + { + "rel": "item", + "href": "./collectionless-item.json", + "type": "application/json", + "title": "Collection with no items (standalone)" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/catalog.json", + "type": "application/json" + } + ] +} diff --git a/tests/data-files/examples/v1.0.0/collection-only/collection-with-schemas.json b/tests/data-files/examples/v1.0.0/collection-only/collection-with-schemas.json new file mode 100644 index 000000000..213e53fb6 --- /dev/null +++ b/tests/data-files/examples/v1.0.0/collection-only/collection-with-schemas.json @@ -0,0 +1,277 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "id": "sentinel-2", + "type": "Collection", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-2A", + "description": "The SENTINEL-2 mission is a land monitoring constellation of two satellites each equipped with a MSI (Multispectral Imager) instrument covering 13 spectral bands providing high resolution optical imagery (i.e., 10m, 20m, 60 m) every 10 days with one satellite and 5 days with two satellites", + "license": "proprietary", + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -82.852377834669, + 180.0, + 82.819463367711 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2017-04-12T02:57:21.459000Z", + "2021-04-22T11:30:12.767000Z" + ] + ] + } + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + }, + { + "name": "AWS", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/sentinel-2/" + }, + { + "name": "jeobrowser", + "roles": [ + "processor" + ], + "url": "https://github.com/jjrom/resto" + } + ], + "summaries": { + "datetime": { + "minimum": "2017-04-12T02:57:21Z", + "maximum": "2021-04-22T11:30:12Z" + }, + "instruments": { + "type": "string", + "const": "msi", + "title": "Multispectral Intrument", + "count": 6613431 + }, + "resto:landcover": { + "type": "string", + "oneOf": [ + { + "const": "cultivated", + "title": "Cultivated", + "count": 490750 + }, + { + "const": "desert", + "title": "Desert", + "count": 543120 + }, + { + "const": "flooded", + "title": "Flooded", + "count": 5187 + }, + { + "const": "forest", + "title": "Forest", + "count": 767807 + }, + { + "const": "herbaceous", + "title": "Herbaceous", + "count": 674281 + }, + { + "const": "ice", + "title": "Ice", + "count": 231285 + }, + { + "const": "urban", + "title": "Urban", + "count": 1219 + }, + { + "const": "water", + "title": "Water", + "count": 2303314 + } + ] + }, + "resto:location": { + "type": "string", + "oneOf": [ + { + "const": "tropical", + "title": "Tropical", + "count": 1807474 + }, + { + "const": "southern", + "title": "Southern", + "count": 1671685 + }, + { + "const": "northern", + "title": "Northern", + "count": 4876669 + }, + { + "const": "equatorial", + "title": "Equatorial", + "count": 27302 + }, + { + "const": "coastal", + "title": "Coastal", + "count": 1495516 + } + ] + }, + "platform": { + "type": "string", + "oneOf": [ + { + "const": "sentinel-2b", + "title": "Sentinel 2B", + "count": 3495597 + }, + { + "const": "sentinel-2a", + "title": "Sentinel 2A", + "count": 3117831 + } + ] + }, + "resto:season": { + "type": "integer", + "oneOf": [ + { + "const": 0, + "title": "Winter", + "count": 1621108 + }, + { + "const": 2, + "title": "Summer", + "count": 2279472 + }, + { + "const": 1, + "title": "Spring", + "count": 1577067 + }, + { + "const": 3, + "title": "Autumn", + "count": 1098015 + } + ] + }, + "eo:bands": [ + { + "title": "B1", + "common_name": "coastal", + "center_wavelength": 4.439, + "gsd": 60 + }, + { + "title": "B2", + "common_name": "blue", + "center_wavelength": 4.966, + "gsd": 10 + }, + { + "title": "B3", + "common_name": "green", + "center_wavelength": 5.6, + "gsd": 10 + }, + { + "title": "B4", + "common_name": "red", + "center_wavelength": 6.645, + "gsd": 10 + }, + { + "title": "B5", + "center_wavelength": 7.039, + "gsd": 20 + }, + { + "title": "B6", + "center_wavelength": 7.402, + "gsd": 20 + }, + { + "title": "B7", + "center_wavelength": 7.825, + "gsd": 20 + }, + { + "title": "B8", + "common_name": "nir", + "center_wavelength": 8.351, + "gsd": 10 + }, + { + "title": "B8A", + "center_wavelength": 8.648, + "gsd": 20 + }, + { + "title": "B9", + "center_wavelength": 9.45, + "gsd": 60 + }, + { + "title": "B10", + "center_wavelength": 1.3735, + "gsd": 60 + }, + { + "title": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137, + "gsd": 20 + }, + { + "title": "B12", + "common_name": "swir22", + "center_wavelength": 2.2024, + "gsd": 20 + } + ] + } +} diff --git a/tests/data-files/examples/v1.0.0/collection-only/collection.json b/tests/data-files/examples/v1.0.0/collection-only/collection.json new file mode 100644 index 000000000..64edf78dd --- /dev/null +++ b/tests/data-files/examples/v1.0.0/collection-only/collection.json @@ -0,0 +1,233 @@ +{ + "type": "Collection", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "id": "sentinel-2", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-1C", + "description": "Sentinel-2 is a wide-swath, high-resolution, multi-spectral\nimaging mission supporting Copernicus Land Monitoring studies,\nincluding the monitoring of vegetation, soil and water cover,\nas well as observation of inland waterways and coastal areas.\n\nThe Sentinel-2 data contain 13 UINT16 spectral bands representing\nTOA reflectance scaled by 10000. See the [Sentinel-2 User Handbook](https://sentinel.esa.int/documents/247904/685211/Sentinel-2_User_Handbook)\nfor details. In addition, three QA bands are present where one\n(QA60) is a bitmask band with cloud mask information. For more\ndetails, [see the full explanation of how cloud masks are computed.](https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-1c/cloud-masks)\n\nEach Sentinel-2 product (zip archive) may contain multiple\ngranules. Each granule becomes a separate Earth Engine asset.\nEE asset ids for Sentinel-2 assets have the following format:\nCOPERNICUS/S2/20151128T002653_20151128T102149_T56MNN. Here the\nfirst numeric part represents the sensing date and time, the\nsecond numeric part represents the product generation date and\ntime, and the final 6-character string is a unique granule identifier\nindicating its UTM grid reference (see [MGRS](https://en.wikipedia.org/wiki/Military_Grid_Reference_System)).\n\nFor more details on Sentinel-2 radiometric resoltuon, [see this page](https://earth.esa.int/web/sentinel/user-guides/sentinel-2-msi/resolutions/radiometric).\n", + "license": "proprietary", + "keywords": [ + "copernicus", + "esa", + "eu", + "msi", + "radiance", + "sentinel" + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -56.0, + 180.0, + 83.0 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2015-06-23T00:00:00Z", + null + ] + ] + } + }, + "assets": { + "metadata_iso_19139": { + "roles": [ + "metadata", + "iso-19139" + ], + "href": "https://storage.googleapis.com/open-cogs/stac-examples/sentinel-2-iso-19139.xml", + "title": "ISO 19139 metadata", + "type": "application/vnd.iso.19139+xml" + } + }, + "summaries": { + "datetime": { + "minimum": "2015-06-23T00:00:00Z", + "maximum": "2019-07-10T13:44:56Z" + }, + "platform": [ + "sentinel-2a", + "sentinel-2b" + ], + "constellation": [ + "sentinel-2" + ], + "instruments": [ + "msi" + ], + "view:off_nadir": { + "minimum": 0, + "maximum": 100 + }, + "view:sun_elevation": { + "minimum": 6.78, + "maximum": 89.9 + }, + "gsd": [ + 10, + 30, + 60 + ], + "proj:epsg": [ + 32601, + 32602, + 32603, + 32604, + 32605, + 32606, + 32607, + 32608, + 32609, + 32610, + 32611, + 32612, + 32613, + 32614, + 32615, + 32616, + 32617, + 32618, + 32619, + 32620, + 32621, + 32622, + 32623, + 32624, + 32625, + 32626, + 32627, + 32628, + 32629, + 32630, + 32631, + 32632, + 32633, + 32634, + 32635, + 32636, + 32637, + 32638, + 32639, + 32640, + 32641, + 32642, + 32643, + 32644, + 32645, + 32646, + 32647, + 32648, + 32649, + 32650, + 32651, + 32652, + 32653, + 32654, + 32655, + 32656, + 32657, + 32658, + 32659, + 32660 + ], + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 4.439 + }, + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 4.966 + }, + { + "name": "B3", + "common_name": "green", + "center_wavelength": 5.6 + }, + { + "name": "B4", + "common_name": "red", + "center_wavelength": 6.645 + }, + { + "name": "B5", + "center_wavelength": 7.039 + }, + { + "name": "B6", + "center_wavelength": 7.402 + }, + { + "name": "B7", + "center_wavelength": 7.825 + }, + { + "name": "B8", + "common_name": "nir", + "center_wavelength": 8.351 + }, + { + "name": "B8A", + "center_wavelength": 8.648 + }, + { + "name": "B9", + "center_wavelength": 9.45 + }, + { + "name": "B10", + "center_wavelength": 1.3735 + }, + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137 + }, + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.2024 + } + ] + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ] +} diff --git a/tests/data-files/examples/v1.0.0/collection.json b/tests/data-files/examples/v1.0.0/collection.json new file mode 100644 index 000000000..07643b600 --- /dev/null +++ b/tests/data-files/examples/v1.0.0/collection.json @@ -0,0 +1,112 @@ +{ + "id": "simple-collection", + "type": "Collection", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "stac_version": "1.0.0", + "description": "A simple collection demonstrating core catalog fields with links to a couple of items", + "title": "Simple Example Collection", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-12-11T22:38:32.125Z", + "2020-12-14T18:02:31.437Z" + ] + ] + } + }, + "license": "CC-BY-4.0", + "summaries": { + "platform": [ + "cool_sat1", + "cool_sat2" + ], + "constellation": [ + "ion" + ], + "instruments": [ + "cool_sensor_v1", + "cool_sensor_v2" + ], + "gsd": { + "minimum": 0.512, + "maximum": 0.66 + }, + "eo:cloud_cover": { + "minimum": 1.2, + "maximum": 1.2 + }, + "proj:epsg": { + "minimum": 32659, + "maximum": 32659 + }, + "view:sun_elevation": { + "minimum": 54.9, + "maximum": 54.9 + }, + "view:off_nadir": { + "minimum": 3.8, + "maximum": 3.8 + }, + "view:sun_azimuth": { + "minimum": 135.7, + "maximum": 135.7 + } + }, + "links": [ + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "item", + "href": "./simple-item.json", + "type": "application/geo+json", + "title": "Simple Item" + }, + { + "rel": "item", + "href": "./core-item.json", + "type": "application/geo+json", + "title": "Core Item" + }, + { + "rel": "item", + "href": "./extended-item.json", + "type": "application/geo+json", + "title": "Extended Item" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/collection.json", + "type": "application/json" + } + ] +} diff --git a/tests/data-files/examples/v1.0.0/collectionless-item.json b/tests/data-files/examples/v1.0.0/collectionless-item.json new file mode 100644 index 000000000..6e7beab93 --- /dev/null +++ b/tests/data-files/examples/v1.0.0/collectionless-item.json @@ -0,0 +1,144 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "CS3-20160503_132131_08", + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.2880486, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, + "properties": { + "title": "Full Item", + "description": "A sample STAC Item demonstrates an Item that does not have a collection, which is not recommended, but allowed by the spec.", + "datetime": null, + "start_datetime": "2016-05-03T13:22:30Z", + "end_datetime": "2016-05-03T13:27:30Z", + "created": "2016-05-04T00:00:01Z", + "updated": "2017-01-01T00:30:55Z", + "license": "various", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.it" + } + ], + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v1" + ], + "view:sun_elevation": 33.4, + "gsd": 0.512, + "cs:type": "scene", + "cs:anomalous_pixels": 0.14, + "cs:earth_sun_distance": 1.014156, + "cs:sat_id": "CS3", + "cs:product_level": "LV1B" + }, + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "parent", + "href": "./catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html", + "title": "HTML representation of this STAC Item" + }, + { + "rel": "license", + "type": "text/html", + "href": "http://remotedata.io/license.html", + "title": "Data License for Remote Data, Inc." + } + ], + "assets": { + "analytic": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", + "title": "4-Band Analytic", + "eo:bands": [ + { + "name": "band1" + }, + { + "name": "band1" + }, + { + "name": "band2" + }, + { + "name": "band3" + } + ] + }, + "thumbnail": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "udm": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/UDM.tif", + "title": "Unusable Data Mask" + }, + "json-metadata": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/S3-20160503_132130_04.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/v1.0.0/core-item.json b/tests/data-files/examples/v1.0.0/core-item.json new file mode 100644 index 000000000..d07cc8df6 --- /dev/null +++ b/tests/data-files/examples/v1.0.0/core-item.json @@ -0,0 +1,125 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Core Item", + "description": "A sample STAC Item that includes examples of all common metadata", + "datetime": null, + "start_datetime": "2020-12-11T22:38:32.125Z", + "end_datetime": "2020-12-11T22:38:32.327Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat1", + "instruments": [ + "cool_sensor_v1" + ], + "constellation": "ion", + "mission": "collection 5624", + "gsd": 0.512 + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/v1.0.0/extended-item.json b/tests/data-files/examples/v1.0.0/extended-item.json new file mode 100644 index 000000000..59f2de1f3 --- /dev/null +++ b/tests/data-files/examples/v1.0.0/extended-item.json @@ -0,0 +1,199 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/remote-data/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Extended Item", + "description": "A sample STAC Item that includes a variety of examples from the stable extensions", + "datetime": "2020-12-14T18:02:31.437000Z", + "created": "2020-12-15T01:48:13.725Z", + "updated": "2020-12-15T01:48:13.725Z", + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v2" + ], + "gsd": 0.66, + "eo:cloud_cover": 1.2, + "proj:epsg": 32659, + "proj:shape": [ + 5558, + 9559 + ], + "proj:transform": [ + 0.5, + 0, + 712710, + 0, + -0.5, + 151406, + 0, + 0, + 1 + ], + "view:sun_elevation": 54.9, + "view:off_nadir": 3.8, + "view:sun_azimuth": 135.7, + "rd:type": "scene", + "rd:anomalous_pixels": 0.14, + "rd:earth_sun_distance": 1.014156, + "rd:sat_id": "cool_sat2", + "rd:product_level": "LV3A", + "sci:doi": "10.5061/dryad.s2v81.2/27.2" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ], + "eo:bands": [ + { + "name": "band1", + "common_name": "blue", + "center_wavelength": 470, + "full_width_half_max": 70 + }, + { + "name": "band2", + "common_name": "green", + "center_wavelength": 560, + "full_width_half_max": 80 + }, + { + "name": "band3", + "common_name": "red", + "center_wavelength": 645, + "full_width_half_max": 90 + }, + { + "name": "band4", + "common_name": "nir", + "center_wavelength": 800, + "full_width_half_max": 152 + } + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ], + "eo:bands": [ + { + "name": "band3", + "common_name": "red", + "center_wavelength": 645, + "full_width_half_max": 90 + }, + { + "name": "band2", + "common_name": "green", + "center_wavelength": 560, + "full_width_half_max": 80 + }, + { + "name": "band1", + "common_name": "blue", + "center_wavelength": 470, + "full_width_half_max": 70 + } + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/v1.0.0/extensions-collection/collection.json b/tests/data-files/examples/v1.0.0/extensions-collection/collection.json new file mode 100644 index 000000000..2c6306acb --- /dev/null +++ b/tests/data-files/examples/v1.0.0/extensions-collection/collection.json @@ -0,0 +1,68 @@ +{ + "id": "extensions-collection", + "type": "Collection", + "stac_version": "1.0.0", + "description": "A heterogenous collection containing deeper examples of various extensions", + "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "item", + "href": "./proj-example/proj-example.json", + "title": "Proj extension example" + }, + { + "rel": "license", + "href": "https://remotedata.io/license.html", + "title": "Remote Data License Terms" + } + ], + "stac_extensions": [], + "title": "Collection of Extension Items", + "keywords": [ + "examples", + "sar", + "projection" + ], + "providers": [ + { + "name": "Remote Data, Inc.", + "roles": [ + "producer", + "licensor" + ], + "url": "https://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -56.0, + 180.0, + 83.0 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2009-05-20T02:40:01.042784Z", + "2018-11-03T23:59:55.112875Z" + ] + ] + } + }, + "license": "PDDL-1.0" +} diff --git a/tests/data-files/examples/v1.0.0/extensions-collection/proj-example/proj-example.json b/tests/data-files/examples/v1.0.0/extensions-collection/proj-example/proj-example.json new file mode 100644 index 000000000..09b7986da --- /dev/null +++ b/tests/data-files/examples/v1.0.0/extensions-collection/proj-example/proj-example.json @@ -0,0 +1,285 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "proj-example", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "proj:epsg": 32614, + "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "proj:projjson": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 14N", + "base_crs": { + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "conversion": { + "name": "UTM zone 14N", + "method": { + "name": "Transverse Mercator", + "id": { + "authority": "EPSG", + "code": 9807 + } + }, + "parameters": [ + { + "name": "Latitude of natural origin", + "value": 0, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8801 + } + }, + { + "name": "Longitude of natural origin", + "value": -99, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8802 + } + }, + { + "name": "Scale factor at natural origin", + "value": 0.9996, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8805 + } + }, + { + "name": "False easting", + "value": 500000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": 0, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + }, + "area": "World - N hemisphere - 102°W to 96°W - by country", + "bbox": { + "south_latitude": 0, + "west_longitude": -102, + "north_latitude": 84, + "east_longitude": -96 + }, + "id": { + "authority": "EPSG", + "code": 32614 + } + }, + "proj:geometry": { + "coordinates": [ + [ + [ + 169200, + 3712800 + ], + [ + 403200, + 3712800 + ], + [ + 403200, + 3951000 + ], + [ + 169200, + 3951000 + ], + [ + 169200, + 3712800 + ] + ] + ], + "type": "Polygon" + }, + "proj:bbox": [ + 169200, + 3712800, + 403200, + 3951000 + ], + "proj:centroid": { + "lat": 34.595302781575604, + "lon": -101.34448382627504 + }, + "proj:shape": [ + 8391, + 8311 + ], + "proj:transform": [ + 30, + 0, + 224985, + 0, + -30, + 6790215, + 0, + 0, + 1 + ] + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 152.52758, + 60.63437 + ], + [ + 149.1755, + 61.19016 + ], + [ + 148.13933, + 59.51584 + ], + [ + 151.33786, + 58.97792 + ], + [ + 152.52758, + 60.63437 + ] + ] + ] + }, + "links": [ + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json", + "title": "Collection of Extension Items" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json", + "title": "Collection of Extension Items" + } + ], + "assets": { + "B1": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + } + ] + }, + "B8": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 8 (panchromatic)", + "eo:bands": [ + { + "name": "B8", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + } + ], + "proj:shape": [ + 16781, + 16621 + ], + "proj:transform": [ + 15, + 0, + 224992.5, + 0, + -15, + 6790207.5, + 0, + 0, + 1 + ] + } + }, + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" + ], + "collection": "landsat-8-l1" +} diff --git a/tests/data-files/examples/v1.0.0/simple-item.json b/tests/data-files/examples/v1.0.0/simple-item.json new file mode 100644 index 000000000..1e413c43d --- /dev/null +++ b/tests/data-files/examples/v1.0.0/simple-item.json @@ -0,0 +1,81 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "datetime": "2020-12-11T22:38:32.125000Z" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + } + ], + "assets": { + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/jpeg", + "roles": [ + "thumbnail" + ] + } + } +} diff --git a/tests/data-files/examples/1.1.0/README.md b/tests/data-files/examples/v1.1.0/README.md similarity index 100% rename from tests/data-files/examples/1.1.0/README.md rename to tests/data-files/examples/v1.1.0/README.md diff --git a/tests/data-files/examples/1.1.0/catalog.json b/tests/data-files/examples/v1.1.0/catalog.json similarity index 100% rename from tests/data-files/examples/1.1.0/catalog.json rename to tests/data-files/examples/v1.1.0/catalog.json diff --git a/tests/data-files/examples/v1.1.0/collection-only/collection-with-schemas.json b/tests/data-files/examples/v1.1.0/collection-only/collection-with-schemas.json new file mode 100644 index 000000000..eeebb779e --- /dev/null +++ b/tests/data-files/examples/v1.1.0/collection-only/collection-with-schemas.json @@ -0,0 +1,311 @@ +{ + "stac_version": "1.1.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v2.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "id": "sentinel-2", + "type": "Collection", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-2A", + "description": "The SENTINEL-2 mission is a land monitoring constellation of two satellites each equipped with a MSI (Multispectral Imager) instrument covering 13 spectral bands providing high resolution optical imagery (i.e., 10m, 20m, 60 m) every 10 days with one satellite and 5 days with two satellites", + "license": "other", + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -82.852377834669, + 180, + 82.819463367711 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2017-04-12T02:57:21.459000Z", + "2021-04-22T11:30:12.767000Z" + ] + ] + } + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + }, + { + "name": "AWS", + "roles": [ + "host" + ], + "url": "https://registry.opendata.aws/sentinel-2/" + }, + { + "name": "jeobrowser", + "roles": [ + "processor" + ], + "url": "https://github.com/jjrom/resto" + } + ], + "summaries": { + "datetime": { + "minimum": "2017-04-12T02:57:21Z", + "maximum": "2021-04-22T11:30:12Z" + }, + "instruments": { + "type": "string", + "const": "msi", + "title": "Multispectral Instrument", + "count": 6613431 + }, + "resto:landcover": { + "type": "string", + "oneOf": [ + { + "const": "cultivated", + "title": "Cultivated", + "count": 490750 + }, + { + "const": "desert", + "title": "Desert", + "count": 543120 + }, + { + "const": "flooded", + "title": "Flooded", + "count": 5187 + }, + { + "const": "forest", + "title": "Forest", + "count": 767807 + }, + { + "const": "herbaceous", + "title": "Herbaceous", + "count": 674281 + }, + { + "const": "ice", + "title": "Ice", + "count": 231285 + }, + { + "const": "urban", + "title": "Urban", + "count": 1219 + }, + { + "const": "water", + "title": "Water", + "count": 2303314 + } + ] + }, + "resto:location": { + "type": "string", + "oneOf": [ + { + "const": "tropical", + "title": "Tropical", + "count": 1807474 + }, + { + "const": "southern", + "title": "Southern", + "count": 1671685 + }, + { + "const": "northern", + "title": "Northern", + "count": 4876669 + }, + { + "const": "equatorial", + "title": "Equatorial", + "count": 27302 + }, + { + "const": "coastal", + "title": "Coastal", + "count": 1495516 + } + ] + }, + "platform": { + "type": "string", + "oneOf": [ + { + "const": "sentinel-2b", + "title": "Sentinel 2B", + "count": 3495597 + }, + { + "const": "sentinel-2a", + "title": "Sentinel 2A", + "count": 3117831 + } + ] + }, + "resto:season": { + "type": "integer", + "oneOf": [ + { + "const": 0, + "title": "Winter", + "count": 1621108 + }, + { + "const": 2, + "title": "Summer", + "count": 2279472 + }, + { + "const": 1, + "title": "Spring", + "count": 1577067 + }, + { + "const": 3, + "title": "Autumn", + "count": 1098015 + } + ] + }, + "bands": [ + { + "title": "B1", + "eo:common_name": "coastal", + "eo:center_wavelength": 0.4439, + "gsd": 60 + }, + { + "title": "B2", + "eo:common_name": "blue", + "eo:center_wavelength": 0.4966, + "gsd": 10 + }, + { + "title": "B3", + "eo:common_name": "green", + "eo:center_wavelength": 0.56, + "gsd": 10 + }, + { + "title": "B4", + "eo:common_name": "red", + "eo:center_wavelength": 0.6645, + "gsd": 10 + }, + { + "title": "B5", + "eo:center_wavelength": 0.7039, + "gsd": 20 + }, + { + "title": "B6", + "eo:center_wavelength": 0.7402, + "gsd": 20 + }, + { + "title": "B7", + "eo:center_wavelength": 0.7825, + "gsd": 20 + }, + { + "title": "B8", + "eo:common_name": "nir", + "eo:center_wavelength": 0.8351, + "gsd": 10 + }, + { + "title": "B8A", + "eo:center_wavelength": 0.8648, + "gsd": 20 + }, + { + "title": "B9", + "eo:center_wavelength": 0.945, + "gsd": 60 + }, + { + "title": "B10", + "eo:center_wavelength": 0.13735, + "gsd": 60 + }, + { + "title": "B11", + "eo:common_name": "swir16", + "eo:center_wavelength": 0.16137, + "gsd": 20 + }, + { + "title": "B12", + "eo:common_name": "swir22", + "eo:center_wavelength": 0.22024, + "gsd": 20 + } + ] + }, + "item_assets": { + "B1": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff", + "roles": [ + "data", + "visual" + ] + }, + "B2": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff", + "roles": [ + "data", + "visual" + ] + }, + "B3": { + "title": "Band 3 (green)", + "type": "image/tiff; application=geotiff", + "roles": [ + "data", + "visual" + ] + }, + "B4": { + "title": "Band 4 (red)", + "type": "image/tiff; application=geotiff", + "roles": [ + "data", + "visual" + ] + } + } +} diff --git a/tests/data-files/examples/v1.1.0/collection-only/collection.json b/tests/data-files/examples/v1.1.0/collection-only/collection.json new file mode 100644 index 000000000..9fab89984 --- /dev/null +++ b/tests/data-files/examples/v1.1.0/collection-only/collection.json @@ -0,0 +1,233 @@ +{ + "type": "Collection", + "stac_version": "1.1.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v2.0.0/schema.json", + "https://stac-extensions.github.io/projection/v2.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "id": "sentinel-2", + "title": "Sentinel-2 MSI: MultiSpectral Instrument, Level-1C", + "description": "Sentinel-2 is a wide-swath, high-resolution, multi-spectral\nimaging mission supporting Copernicus Land Monitoring studies,\nincluding the monitoring of vegetation, soil and water cover,\nas well as observation of inland waterways and coastal areas.\n\nThe Sentinel-2 data contain 13 UINT16 spectral bands representing\nTOA reflectance scaled by 10000. See the [Sentinel-2 User Handbook](https://sentinel.esa.int/documents/247904/685211/Sentinel-2_User_Handbook)\nfor details. In addition, three QA bands are present where one\n(QA60) is a bitmask band with cloud mask information. For more\ndetails, [see the full explanation of how cloud masks are computed.](https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-1c/cloud-masks)\n\nEach Sentinel-2 product (zip archive) may contain multiple\ngranules. Each granule becomes a separate Earth Engine asset.\nEE asset ids for Sentinel-2 assets have the following format:\nCOPERNICUS/S2/20151128T002653_20151128T102149_T56MNN. Here the\nfirst numeric part represents the sensing date and time, the\nsecond numeric part represents the product generation date and\ntime, and the final 6-character string is a unique granule identifier\nindicating its UTM grid reference (see [MGRS](https://en.wikipedia.org/wiki/Military_Grid_Reference_System)).\n\nFor more details on Sentinel-2 radiometric resoltuon, [see this page](https://earth.esa.int/web/sentinel/user-guides/sentinel-2-msi/resolutions/radiometric).\n", + "license": "other", + "keywords": [ + "copernicus", + "esa", + "eu", + "msi", + "radiance", + "sentinel" + ], + "providers": [ + { + "name": "European Union/ESA/Copernicus", + "roles": [ + "producer", + "licensor" + ], + "url": "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -56, + 180, + 83 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2015-06-23T00:00:00Z", + null + ] + ] + } + }, + "assets": { + "metadata_iso_19139": { + "roles": [ + "metadata", + "iso-19139" + ], + "href": "https://storage.googleapis.com/open-cogs/stac-examples/sentinel-2-iso-19139.xml", + "title": "ISO 19139 metadata", + "type": "application/vnd.iso.19139+xml" + } + }, + "summaries": { + "datetime": { + "minimum": "2015-06-23T00:00:00Z", + "maximum": "2019-07-10T13:44:56Z" + }, + "platform": [ + "sentinel-2a", + "sentinel-2b" + ], + "constellation": [ + "sentinel-2" + ], + "instruments": [ + "msi" + ], + "view:off_nadir": { + "minimum": 0, + "maximum": 100 + }, + "view:sun_elevation": { + "minimum": 6.78, + "maximum": 89.9 + }, + "gsd": [ + 10, + 30, + 60 + ], + "proj:code": [ + "EPSG:32601", + "EPSG:32602", + "EPSG:32603", + "EPSG:32604", + "EPSG:32605", + "EPSG:32606", + "EPSG:32607", + "EPSG:32608", + "EPSG:32609", + "EPSG:32610", + "EPSG:32611", + "EPSG:32612", + "EPSG:32613", + "EPSG:32614", + "EPSG:32615", + "EPSG:32616", + "EPSG:32617", + "EPSG:32618", + "EPSG:32619", + "EPSG:32620", + "EPSG:32621", + "EPSG:32622", + "EPSG:32623", + "EPSG:32624", + "EPSG:32625", + "EPSG:32626", + "EPSG:32627", + "EPSG:32628", + "EPSG:32629", + "EPSG:32630", + "EPSG:32631", + "EPSG:32632", + "EPSG:32633", + "EPSG:32634", + "EPSG:32635", + "EPSG:32636", + "EPSG:32637", + "EPSG:32638", + "EPSG:32639", + "EPSG:32640", + "EPSG:32641", + "EPSG:32642", + "EPSG:32643", + "EPSG:32644", + "EPSG:32645", + "EPSG:32646", + "EPSG:32647", + "EPSG:32648", + "EPSG:32649", + "EPSG:32650", + "EPSG:32651", + "EPSG:32652", + "EPSG:32653", + "EPSG:32654", + "EPSG:32655", + "EPSG:32656", + "EPSG:32657", + "EPSG:32658", + "EPSG:32659", + "EPSG:32660" + ], + "bands": [ + { + "name": "B1", + "eo:common_name": "coastal", + "eo:center_wavelength": 0.4439 + }, + { + "name": "B2", + "eo:common_name": "blue", + "eo:center_wavelength": 0.4966 + }, + { + "name": "B3", + "eo:common_name": "green", + "eo:center_wavelength": 0.56 + }, + { + "name": "B4", + "eo:common_name": "red", + "eo:center_wavelength": 0.6645 + }, + { + "name": "B5", + "eo:center_wavelength": 0.7039 + }, + { + "name": "B6", + "eo:center_wavelength": 0.7402 + }, + { + "name": "B7", + "eo:center_wavelength": 0.7825 + }, + { + "name": "B8", + "eo:common_name": "nir", + "eo:center_wavelength": 0.8351 + }, + { + "name": "B8A", + "eo:center_wavelength": 0.8648 + }, + { + "name": "B9", + "eo:center_wavelength": 0.945 + }, + { + "name": "B10", + "eo:center_wavelength": 1.3735 + }, + { + "name": "B11", + "eo:common_name": "swir16", + "eo:center_wavelength": 1.6137 + }, + { + "name": "B12", + "eo:common_name": "swir22", + "eo:center_wavelength": 2.2024 + } + ] + }, + "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "license", + "href": "https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", + "title": "Legal notice on the use of Copernicus Sentinel Data and Service Information" + } + ] +} diff --git a/tests/data-files/examples/1.1.0/collection.json b/tests/data-files/examples/v1.1.0/collection.json similarity index 100% rename from tests/data-files/examples/1.1.0/collection.json rename to tests/data-files/examples/v1.1.0/collection.json diff --git a/tests/data-files/examples/1.1.0/collectionless-item.json b/tests/data-files/examples/v1.1.0/collectionless-item.json similarity index 100% rename from tests/data-files/examples/1.1.0/collectionless-item.json rename to tests/data-files/examples/v1.1.0/collectionless-item.json diff --git a/tests/data-files/examples/1.1.0/core-item.json b/tests/data-files/examples/v1.1.0/core-item.json similarity index 100% rename from tests/data-files/examples/1.1.0/core-item.json rename to tests/data-files/examples/v1.1.0/core-item.json diff --git a/tests/data-files/examples/v1.1.0/extended-item.json b/tests/data-files/examples/v1.1.0/extended-item.json new file mode 100644 index 000000000..c3e493200 --- /dev/null +++ b/tests/data-files/examples/v1.1.0/extended-item.json @@ -0,0 +1,210 @@ +{ + "stac_version": "1.1.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v2.0.0/schema.json", + "https://stac-extensions.github.io/projection/v2.0.0/schema.json", + "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/remote-data/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Extended Item", + "description": "A sample STAC Item that includes a variety of examples from the stable extensions", + "keywords": [ + "extended", + "example", + "item" + ], + "datetime": "2020-12-14T18:02:31.437000Z", + "created": "2020-12-15T01:48:13.725Z", + "updated": "2020-12-15T01:48:13.725Z", + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v2" + ], + "gsd": 0.66, + "eo:cloud_cover": 1.2, + "eo:snow_cover": 0, + "statistics": { + "vegetation": 12.57, + "water": 1.23, + "urban": 26.2 + }, + "proj:code": "EPSG:32659", + "proj:shape": [ + 5558, + 9559 + ], + "proj:transform": [ + 0.5, + 0, + 712710, + 0, + -0.5, + 151406, + 0, + 0, + 1 + ], + "view:sun_elevation": 54.9, + "view:off_nadir": 3.8, + "view:sun_azimuth": 135.7, + "rd:type": "scene", + "rd:anomalous_pixels": 0.14, + "rd:earth_sun_distance": 1.014156, + "rd:sat_id": "cool_sat2", + "rd:product_level": "LV3A", + "sci:doi": "10.5061/dryad.s2v81.2/27.2" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ], + "bands": [ + { + "name": "band1", + "eo:common_name": "blue", + "eo:center_wavelength": 0.47, + "eo:full_width_half_max": 70 + }, + { + "name": "band2", + "eo:common_name": "green", + "eo:center_wavelength": 0.56, + "eo:full_width_half_max": 80 + }, + { + "name": "band3", + "eo:common_name": "red", + "eo:center_wavelength": 0.645, + "eo:full_width_half_max": 90 + }, + { + "name": "band4", + "eo:common_name": "nir", + "eo:center_wavelength": 0.8, + "eo:full_width_half_max": 152 + } + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ], + "bands": [ + { + "name": "band3", + "eo:common_name": "red", + "eo:center_wavelength": 0.645, + "eo:full_width_half_max": 90 + }, + { + "name": "band2", + "eo:common_name": "green", + "eo:center_wavelength": 0.56, + "eo:full_width_half_max": 80 + }, + { + "name": "band1", + "eo:common_name": "blue", + "eo:center_wavelength": 0.47, + "eo:full_width_half_max": 70 + } + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/1.1.0/extensions-collection/collection.json b/tests/data-files/examples/v1.1.0/extensions-collection/collection.json similarity index 100% rename from tests/data-files/examples/1.1.0/extensions-collection/collection.json rename to tests/data-files/examples/v1.1.0/extensions-collection/collection.json diff --git a/tests/data-files/examples/1.1.0/extensions-collection/proj-example/proj-example.json b/tests/data-files/examples/v1.1.0/extensions-collection/proj-example/proj-example.json similarity index 100% rename from tests/data-files/examples/1.1.0/extensions-collection/proj-example/proj-example.json rename to tests/data-files/examples/v1.1.0/extensions-collection/proj-example/proj-example.json diff --git a/tests/data-files/examples/1.1.0/simple-item.json b/tests/data-files/examples/v1.1.0/simple-item.json similarity index 100% rename from tests/data-files/examples/1.1.0/simple-item.json rename to tests/data-files/examples/v1.1.0/simple-item.json diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 3f82b81ff..0c726a9be 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,2039 +1,17 @@ -from __future__ import annotations - import json -import os -import posixpath -import tempfile -from collections import defaultdict -from collections.abc import Iterator -from copy import deepcopy -from datetime import datetime, timezone from pathlib import Path -from typing import Any, cast - -import pytest - -import pystac -from pystac import ( - HIERARCHICAL_LINKS, - Asset, - Catalog, - CatalogType, - Collection, - Item, - Link, - MediaType, -) -from pystac.errors import STACError -from pystac.layout import ( - APILayoutStrategy, - BestPracticesLayoutStrategy, - HrefLayoutStrategy, - TemplateLayoutStrategy, -) -from pystac.utils import ( - is_absolute_href, - make_absolute_href, - make_posix_style, - make_relative_href, -) -from tests.utils import ( - ARBITRARY_BBOX, - ARBITRARY_EXTENT, - ARBITRARY_GEOM, - MockStacIO, - TestCases, -) - - -class TestCatalogType: - def test_determine_type_for_absolute_published(self) -> None: - cat = TestCases.case_1() - with tempfile.TemporaryDirectory() as tmp_dir: - cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat_json = pystac.StacIO.default().read_json( - os.path.join(tmp_dir, "catalog.json") - ) - - catalog_type = CatalogType.determine_type(cat_json) - assert catalog_type == CatalogType.ABSOLUTE_PUBLISHED - - def test_determine_type_for_relative_published(self) -> None: - cat = TestCases.case_2() - with tempfile.TemporaryDirectory() as tmp_dir: - cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.RELATIVE_PUBLISHED) - cat_json = pystac.StacIO.default().read_json( - os.path.join(tmp_dir, "catalog.json") - ) - - catalog_type = CatalogType.determine_type(cat_json) - assert catalog_type == CatalogType.RELATIVE_PUBLISHED - - def test_determine_type_for_self_contained(self) -> None: - cat_json = pystac.StacIO.default().read_json( - TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") - ) - catalog_type = CatalogType.determine_type(cat_json) - assert catalog_type == CatalogType.SELF_CONTAINED - - def test_determine_type_for_unknown(self) -> None: - catalog = Catalog(id="test", description="test desc") - subcat = Catalog(id="subcat", description="subcat desc") - catalog.add_child(subcat) - catalog.normalize_hrefs("http://example.com") - d = catalog.to_dict(include_self_link=False) - - assert CatalogType.determine_type(d) is None - - -class TestCatalog: - def test_create_and_read(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - cat_dir = os.path.join(tmp_dir, "catalog") - catalog = TestCases.case_1() - - catalog.normalize_and_save( - cat_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED - ) - - read_catalog = Catalog.from_file(f"{cat_dir}/catalog.json") - - collections = catalog.get_children() - assert len(list(collections)) == 2 - - items = read_catalog.get_items(recursive=True) - - assert len(list(items)) == 8 - - def test_from_dict_preserves_dict(self) -> None: - catalog_dict = TestCases.case_1().to_dict() - param_dict = deepcopy(catalog_dict) - - # test that the parameter is preserved - _ = Catalog.from_dict(param_dict) - assert param_dict == catalog_dict - - # assert that the parameter is not preserved with - # non-default parameter - _ = Catalog.from_dict(param_dict, preserve_dict=False, migrate=False) - assert param_dict != catalog_dict - - def test_from_file_bad_catalog(self) -> None: - with pytest.raises(pystac.errors.STACTypeError) as ctx: - _ = Catalog.from_file(TestCases.get_path(TestCases.bad_catalog_case)) - assert "(id = broken_cat) does not represent a STACObject" in ctx.value.args[0] - assert "is Catalog" in ctx.value.args[0] - - def test_from_dict_set_root(self) -> None: - path = TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") - with open(path) as f: - cat_dict = json.load(f) - root_cat = pystac.Catalog(id="test", description="test desc") - collection = Catalog.from_dict(cat_dict, root=root_cat) - assert collection.get_root() is root_cat - - @pytest.mark.vcr() - def test_read_remote(self) -> None: - catalog_url = ( - "https://raw.githubusercontent.com/stac-extensions/label/main/" - "examples/multidataset/catalog.json" - ) - cat = Catalog.from_file(catalog_url) - - zanzibar = cat.get_child("zanzibar-collection") - assert zanzibar is not None - - assert len(list(zanzibar.get_items())) == 2 - - def test_clear_items_removes_from_cache(self) -> None: - catalog = Catalog(id="test", description="test") - subcat = Catalog(id="subcat", description="test") - catalog.add_child(subcat) - item = Item( - id="test-item", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={"key": "one"}, - ) - subcat.add_item(item) - - items = list(catalog.get_items(recursive=True)) - assert len(items) == 1 - assert items[0].properties["key"] == "one" - - subcat.clear_items() - item = Item( - id="test-item", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={"key": "two"}, - ) - subcat.add_item(item) - - items = list(catalog.get_items(recursive=True)) - assert len(items) == 1 - assert items[0].properties["key"] == "two" - - subcat.remove_item("test-item") - item = Item( - id="test-item", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={"key": "three"}, - ) - subcat.add_item(item) - - items = list(catalog.get_items(recursive=True)) - assert len(items) == 1 - assert items[0].properties["key"] == "three" - - def test_clear_children_removes_from_cache(self) -> None: - catalog = Catalog(id="test", description="test") - subcat = Catalog(id="subcat", description="test") - catalog.add_child(subcat) - - children = list(catalog.get_children()) - assert len(children) == 1 - assert children[0].description == "test" - - catalog.clear_children() - subcat = Catalog(id="subcat", description="test2") - catalog.add_child(subcat) - - children = list(catalog.get_children()) - assert len(children) == 1 - assert children[0].description == "test2" - - catalog.remove_child("subcat") - subcat = Catalog(id="subcat", description="test3") - catalog.add_child(subcat) - - children = list(catalog.get_children()) - assert len(children) == 1 - assert children[0].description == "test3" - - def test_clear_children_sets_parent_and_root_to_None(self) -> None: - catalog = Catalog(id="test", description="test") - subcat1 = Catalog(id="subcat", description="test") - subcat2 = Catalog(id="subcat2", description="test2") - catalog.add_children([subcat1, subcat2]) - - assert subcat1.get_parent() is not None - assert subcat2.get_parent() is not None - assert subcat1.get_root() is not None - assert subcat2.get_root() is not None - - children = list(catalog.get_children()) - assert len(children) == 2 - - catalog.clear_children() - - assert subcat1.get_parent() is None - assert subcat2.get_parent() is None - assert subcat1.get_root() is None - assert subcat2.get_root() is None - - def test_add_child_throws_if_item(self) -> None: - cat = TestCases.case_1() - item = next(cat.get_items(recursive=True)) - with pytest.raises(pystac.STACError): - cat.add_child(item) # type:ignore - - def test_add_child_returns_link(self) -> None: - parent = Catalog(id="parent", description="test") - child = Catalog(id="child", description="test") - link = parent.add_child(child) - assert isinstance(link, pystac.Link) - link.extra_fields["foo"] = "bar" - link2 = parent.get_single_link("child") - assert link2 is not None - assert link2.extra_fields["foo"] == "bar" - - def test_add_children_returns_links(self) -> None: - parent = Catalog(id="parent", description="test") - child1 = Catalog(id="child", description="test") - child2 = Catalog(id="child2", description="test") - links = parent.add_children([child1, child2]) - assert isinstance(links, list) - assert len(links) == 2 - assert isinstance(links[0], pystac.Link) - assert isinstance(links[1], pystac.Link) - links2 = parent.get_links("child") - assert links[0] in links2 - assert links[1] in links2 - - def test_add_child_override_parent(self) -> None: - parent1 = Catalog(id="parent1", description="test1") - parent2 = Catalog(id="parent2", description="test2") - child = Catalog(id="child", description="test3") - assert child.get_parent() is None - - parent1.add_child(child) - assert child.get_parent() is parent1 - - parent2.add_child(child) - assert child.get_parent() is parent2 - - def test_add_child_set_parent_false(self) -> None: - parent1 = Catalog(id="parent1", description="test1") - parent2 = Catalog(id="parent2", description="test2") - child = Catalog(id="child", description="test3") - assert child.get_parent() is None - - parent1.add_child(child, set_parent=False) - assert child.get_parent() is not parent1 - - parent1.add_child(child) - parent2.add_child(child, set_parent=False) - assert child.get_parent() is parent1 - - def test_add_item_override_parent(self) -> None: - parent1 = Catalog(id="parent1", description="test1") - parent2 = Catalog(id="parent2", description="test2") - child = Item( - id="child", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - assert child.get_parent() is None - - parent1.add_item(child) - assert child.get_parent() is parent1 - - parent2.add_item(child) - assert child.get_parent() is parent2 - - def test_add_item_set_parent_false(self) -> None: - parent1 = Catalog(id="parent1", description="test1") - parent2 = Catalog(id="parent2", description="test2") - child = Item( - id="child", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - assert child.get_parent() is None - - parent1.add_item(child, set_parent=False) - assert child.get_parent() is not parent1 - - parent1.add_item(child) - parent2.add_item(child, set_parent=False) - assert child.get_parent() is parent1 - - def test_add_item_throws_if_child(self) -> None: - cat = TestCases.case_1() - child = next(iter(cat.get_children())) - with pytest.raises(pystac.STACError): - cat.add_item(child) # type:ignore - - def test_add_item_returns_link(self) -> None: - parent = Catalog(id="parent", description="test") - child = Item( - id="child", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - link = parent.add_item(child) - assert isinstance(link, pystac.Link) - link.extra_fields["foo"] = "bar" - link2 = parent.get_single_link("item") - assert link2 is not None - assert link2.extra_fields["foo"] == "bar" - - def test_add_items_returns_links(self) -> None: - parent = Catalog(id="parent", description="test") - child1 = Item( - id="child1", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - child2 = Item( - id="child2", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - links = parent.add_items([child1, child2]) - assert isinstance(links, list) - assert len(links) == 2 - assert isinstance(links[0], pystac.Link) - assert isinstance(links[1], pystac.Link) - links2 = parent.get_links("item") - assert links[0] in links2 - assert links[1] in links2 - - def test_get_child_returns_none_if_not_found(self) -> None: - cat = TestCases.case_1() - child = cat.get_child("thisshouldnotbeachildid", recursive=True) - assert child is None - - def test_get_item_is_deprecated_but_still_works(self) -> None: - cat = TestCases.case_1() - with pytest.warns(DeprecationWarning): - item = cat.get_item("area-2-1-imagery", recursive=True) - assert item is not None - - def test_get_item_returns_none_if_not_found(self) -> None: - cat = TestCases.case_1() - with pytest.warns(DeprecationWarning): - item = cat.get_item("thisshouldnotbeanitemid", recursive=True) - assert item is None - - def test_get_all_items_is_deprecated_but_still_works(self) -> None: - cat = TestCases.case_1() - with pytest.warns(DeprecationWarning): - all_items = cat.get_all_items() - items = cat.get_items(recursive=True) - assert all(a == i for a, i in zip(all_items, items)) - - def test_get_items_returns_empty_generator_if_not_found(self) -> None: - cat = TestCases.case_1() - items = cat.get_items("thisshouldnotbeanitemid", recursive=True) - assert next(items, None) is None - - def test_sets_catalog_type(self) -> None: - cat = TestCases.case_1() - - assert cat.catalog_type == CatalogType.SELF_CONTAINED - - @pytest.mark.parametrize("catalog", TestCases.all_test_catalogs()) - def test_walk_iterates_correctly(self, catalog: Catalog) -> None: - def test_catalog(cat: Catalog) -> None: - expected_catalog_iterations = 1 - actual_catalog_iterations = 0 - for root, children, items in cat.walk(): - actual_catalog_iterations += 1 - expected_catalog_iterations += len(list(root.get_children())) - - assert {c.id for c in root.get_children()} == { - c.id for c in children - }, "Children unequal" - - assert {c.id for c in root.get_items()} == {c.id for c in items}, ( - "Items unequal" - ) - - assert actual_catalog_iterations == expected_catalog_iterations - - test_catalog(catalog) - - @pytest.mark.parametrize("catalog", TestCases.all_test_catalogs()) - def test_clone_generates_correct_links(self, catalog: Catalog) -> None: - expected_link_types_to_counts: Any = {} - actual_link_types_to_counts: Any = {} - - for root, _, items in catalog.walk(): - expected_link_types_to_counts[root.id] = defaultdict(int) - actual_link_types_to_counts[root.id] = defaultdict(int) - - for link in root.get_links(): - expected_link_types_to_counts[root.id][link.rel] += 1 - - for link in root.clone().get_links(): - actual_link_types_to_counts[root.id][link.rel] += 1 - - for item in items: - expected_link_types_to_counts[item.id] = defaultdict(int) - actual_link_types_to_counts[item.id] = defaultdict(int) - for link in item.get_links(): - expected_link_types_to_counts[item.id][link.rel] += 1 - for link in item.get_links(): - actual_link_types_to_counts[item.id][link.rel] += 1 - - assert set(expected_link_types_to_counts.keys()) == set( - actual_link_types_to_counts.keys() - ) - - for obj_id in actual_link_types_to_counts: - expected_counts = expected_link_types_to_counts[obj_id] - actual_counts = actual_link_types_to_counts[obj_id] - assert set(expected_counts.keys()) == set(actual_counts.keys()) - for rel in expected_counts: - assert actual_counts[rel] == expected_counts[rel], ( - "Clone of {} has {} {} links, original has {}".format( - obj_id, actual_counts[rel], rel, expected_counts[rel] - ) - ) - - def test_save_uses_previous_catalog_type(self) -> None: - catalog = TestCases.case_1() - assert catalog.catalog_type == CatalogType.SELF_CONTAINED - with tempfile.TemporaryDirectory() as tmp_dir: - catalog.normalize_hrefs(tmp_dir) - href = catalog.self_href - catalog.save() - - cat2 = pystac.Catalog.from_file(href) - assert cat2.catalog_type == CatalogType.SELF_CONTAINED - - def test_save_to_provided_href(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_1() - href = "https://stac.test" - folder = os.path.join(tmp_dir, "cat") - catalog.normalize_hrefs(href) - catalog.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED, dest_href=folder) - - catalog_path = os.path.join(folder, "catalog.json") - assert os.path.exists(catalog_path) - result_cat = Catalog.from_file(catalog_path) - for link in result_cat.get_child_links(): - assert cast(str, link.target).startswith(href) - - def test_save_relative_published_no_self_links(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_1() - href = "https://stac.test" - folder = os.path.join(tmp_dir, "cat") - catalog.normalize_hrefs(href) - catalog.save(catalog_type=CatalogType.RELATIVE_PUBLISHED, dest_href=folder) - - catalog_path = os.path.join(folder, "catalog.json") - assert os.path.exists(catalog_path) - result_cat = Catalog.from_file(catalog_path) - - # Check that Items do not have a self link - # Since Item.from_dict automatically adds a self link, we need to look at - # the JSON files themselves. - stac_io = pystac.StacIO.default() - - for current_cat, _, __ in result_cat.walk(): - for item_link in current_cat.get_item_links(): - item_dict = stac_io.read_json(item_link) - self_link = next( - ( - link - for link in item_dict.get("links", []) - if link["rel"] == "self" - ), - None, - ) - assert self_link is None - - def test_save_with_different_stac_io(self) -> None: - catalog = Catalog.from_file( - TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") - ) - stac_io = MockStacIO() - with tempfile.TemporaryDirectory() as tmp_dir: - catalog.normalize_hrefs(tmp_dir) - catalog.save( - catalog_type=CatalogType.ABSOLUTE_PUBLISHED, - dest_href=tmp_dir, - stac_io=stac_io, - ) - - hrefs = [] - for root, _, items in catalog.walk(): - hrefs.append(root.get_self_href()) - for item in items: - hrefs.append(item.get_self_href()) - - assert len(hrefs) == stac_io.mock.write_text.call_count - for call_args_list in stac_io.mock.write_text.call_args_list: - assert call_args_list[0][0] in hrefs - - def test_subcatalogs_saved_to_correct_path(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_1() - href = "https://stac.test" - - catalog.normalize_hrefs(href) - catalog.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED, dest_href=tmp_dir) - - # Check the root catalog path - expected_root_catalog_path = os.path.join(tmp_dir, "catalog.json") - assert os.path.exists(expected_root_catalog_path), ( - f"{expected_root_catalog_path} does not exist." - ) - assert os.path.isfile(expected_root_catalog_path), ( - f"{expected_root_catalog_path} is not a file." - ) - - # Check each child catalog - for child_catalog in catalog.get_children(): - relative_path = make_relative_href( - child_catalog.self_href, catalog.self_href, start_is_dir=False - ) - expected_child_path = make_absolute_href( - relative_path, - expected_root_catalog_path, - start_is_dir=False, - ) - assert os.path.exists(expected_child_path), ( - f"{expected_child_path} does not exist." - ) - assert os.path.isfile(expected_child_path), ( - f"{expected_child_path} is not a file." - ) - - # Check each item - for item in catalog.get_items(recursive=True): - relative_path = make_relative_href( - item.self_href, catalog.self_href, start_is_dir=False - ) - expected_item_path = make_absolute_href( - relative_path, - expected_root_catalog_path, - start_is_dir=False, - ) - assert os.path.exists(expected_item_path), ( - f"{expected_item_path} does not exist." - ) - assert os.path.isfile(expected_item_path), ( - f"{expected_item_path} is not a file." - ) - - def test_clone_uses_previous_catalog_type(self) -> None: - catalog = TestCases.case_1() - assert catalog.catalog_type == CatalogType.SELF_CONTAINED - clone = catalog.clone() - assert clone.catalog_type == CatalogType.SELF_CONTAINED - - def test_normalize_hrefs_sets_all_hrefs(self) -> None: - catalog = TestCases.case_1() - catalog.normalize_hrefs("http://example.com") - for root, _, items in catalog.walk(): - self_href = root.get_self_href() - assert self_href is not None - assert self_href.startswith("http://example.com") - for link in root.links: - if link.is_resolved(): - target_href = cast(pystac.STACObject, link.target).self_href - else: - target_href = link.absolute_href - assert "http://example.com" in target_href, ( - '[{}] {} does not contain "{}"'.format( - link.rel, target_href, "http://example.com" - ) - ) - for item in items: - assert "http://example.com" in item.self_href - - def test_normalize_hrefs_makes_absolute_href(self) -> None: - catalog = TestCases.case_1() - catalog.normalize_hrefs("./relativepath") - abspath = make_posix_style(os.path.abspath("./relativepath")) - self_href = catalog.get_self_href() - assert self_href is not None - assert self_href.startswith(abspath) - - def test_normalize_hrefs_skip_unresolved(self) -> None: - catalog = TestCases.case_1() - catalog.normalize_hrefs("http://example.com", skip_unresolved=True) - assert catalog.self_href.startswith("http://example.com") - for link in catalog.links: - if link.rel == "child" or link.rel == "item": - assert not link.is_resolved() - item = Item( - "an-id", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - catalog.add_item(item, title="This is the test item") - catalog.normalize_hrefs("http://example.com", skip_unresolved=True) - for link in catalog.links: - if link.title == "This is the test item": - assert link.is_resolved() - assert isinstance(link.target, pystac.STACObject) - assert link.target.self_href.startswith("http://example.com") - elif link.rel == "child" or link.rel == "item": - assert not link.is_resolved() - - def test_save_unresolved(self) -> None: - catalog = Catalog("an-id", "a description") - item = Item( - "an-id", - geometry=None, - bbox=None, - datetime=datetime.now(), - properties={}, - ) - catalog.add_item(item) - catalog.add_link(pystac.Link("child", "/not/a/real/path/catalog.json")) - with tempfile.TemporaryDirectory() as temporary_directory: - catalog.set_self_href(os.path.join(temporary_directory, "catalog.json")) - item.set_self_href(os.path.join(temporary_directory, "item.json")) - catalog.save() - assert len(os.listdir(temporary_directory)) == 2 - - with tempfile.TemporaryDirectory() as temporary_directory: - catalog.normalize_and_save(temporary_directory, skip_unresolved=True) - assert len(os.listdir(temporary_directory)) == 2 - - with tempfile.TemporaryDirectory() as temporary_directory: - with pytest.raises(STACError, match="does not resolve to a STAC object"): - catalog.normalize_and_save(temporary_directory, skip_unresolved=False) - - def test_generate_subcatalogs_works_with_custom_properties(self) -> None: - catalog = TestCases.case_8() - defaults = {"pl:item_type": "PlanetScope"} - catalog.generate_subcatalogs( - "${year}/${month}/${pl:item_type}", defaults=defaults - ) - - month_cat = catalog.get_child("8", recursive=True) - assert month_cat is not None - type_cats = {cat.id for cat in month_cat.get_children()} - - assert type_cats == {"PSScene4Band", "SkySatScene", "PlanetScope"} - - def test_generate_subcatalogs_does_not_change_item_count(self) -> None: - catalog = TestCases.case_7() - - item_counts = { - cat.id: len(list(cat.get_items(recursive=True))) - for cat in catalog.get_children() - } - - catalog.generate_subcatalogs("${year}/${day}") - - with tempfile.TemporaryDirectory() as tmp_dir: - catalog.normalize_hrefs(tmp_dir) - catalog.save(pystac.CatalogType.SELF_CONTAINED) - - cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) - for child in cat2.get_children(): - actual = len(list(child.get_items(recursive=True))) - expected = item_counts[child.id] - assert actual == expected, f" for child '{child.id}'" - - def test_generate_subcatalogs_merge_template_elements(self) -> None: - catalog = Catalog(id="test", description="Test") - item_properties = [ - dict(property1=p1, property2=p2) for p1 in ("A", "B") for p2 in (1, 2) - ] - for ni, properties in enumerate(item_properties): - catalog.add_item( - Item( - id=f"item{ni}", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties=properties, - ) - ) - result = catalog.generate_subcatalogs("${property1}_${property2}") - - actual_subcats = {cat.id for cat in result} - expected_subcats = { - "{}_{}".format(d["property1"], d["property2"]) for d in item_properties - } - assert len(result) == len(expected_subcats) - assert actual_subcats == expected_subcats - - def test_generate_subcatalogs_can_be_applied_multiple_times(self) -> None: - catalog = TestCases.case_8() - - _ = catalog.generate_subcatalogs("${year}/${month}") - catalog.normalize_hrefs("/tmp") - expected_hrefs = { - item.id: item.get_self_href() for item in catalog.get_items(recursive=True) - } - - result = catalog.generate_subcatalogs("${year}/${month}") - assert len(result) == 0 - catalog.normalize_hrefs("/tmp") - for item in catalog.get_items(recursive=True): - assert item.get_self_href() == expected_hrefs[item.id], ( - f" for item '{item.id}'" - ) - - def test_generate_subcatalogs_works_after_adding_more_items(self) -> None: - catalog = Catalog(id="test", description="Test") - properties = dict(property1="A", property2=1) - catalog.add_item( - Item( - id="item1", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties=properties, - ) - ) - catalog.generate_subcatalogs("${property1}/${property2}") - catalog.add_item( - Item( - id="item2", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties=properties, - ) - ) - catalog.generate_subcatalogs("${property1}/${property2}") - - catalog.normalize_hrefs("/tmp") - item1 = next(catalog.get_items("item1", recursive=True)) - item1_parent = item1.get_parent() - assert item1_parent is not None - item2 = next(catalog.get_items("item2", recursive=True)) - item2_parent = item2.get_parent() - assert item2_parent is not None - assert item1_parent.get_self_href() == item2_parent.get_self_href() - - def test_generate_subcatalogs_works_for_branched_subcatalogs(self) -> None: - catalog = Catalog(id="test", description="Test") - item_properties = [ - dict(property1="A", property2=1, property3="i"), # add 3 subcats - dict(property1="A", property2=1, property3="j"), # add 1 more - dict(property1="A", property2=2, property3="i"), # add 2 more - dict(property1="B", property2=1, property3="i"), # add 3 more - ] - for ni, properties in enumerate(item_properties): - catalog.add_item( - Item( - id=f"item{ni}", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties=properties, - ) - ) - result = catalog.generate_subcatalogs("${property1}/${property2}/${property3}") - assert len(result) == 9 - - actual_subcats = {cat.id for cat in result} - expected_subcats = {"A", "B", "1", "2", "i", "j"} - assert actual_subcats == expected_subcats - - def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self) -> None: - catalog = Catalog(id="test", description="Test") - item_properties = [ - dict(property1=1, property2=1), # add 2 subcats - dict(property1=1, property2=2), # add 1 more - dict(property1=2, property2=1), # add 2 more - dict(property1=2, property2=2), # add 1 more - ] - for ni, properties in enumerate(item_properties): - catalog.add_item( - Item( - id=f"item{ni}", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties=properties, - ) - ) - - result = catalog.generate_subcatalogs("${property1}/${property2}") - assert len(result) == 6 - - catalog.normalize_hrefs("/") - - for item in catalog.get_items(recursive=True): - item_parent = item.get_parent() - assert item_parent is not None - parent_href = item_parent.self_href - path_to_parent, _ = os.path.split(parent_href) - subcats = list( - Path(path_to_parent).parts[1:] - ) # Skip drive letter if present (Windows) - - assert len(subcats) == 2, f" for item '{item.id}'" - - def test_map_items(self) -> None: - def item_mapper(item: pystac.Item) -> pystac.Item: - item.properties["ITEM_MAPPER"] = "YEP" - return item - - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_1() - - new_cat = catalog.map_items(item_mapper) - - new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) - new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) - - for item in result_cat.get_items(recursive=True): - assert "ITEM_MAPPER" in item.properties - - for item in catalog.get_items(recursive=True): - assert "ITEM_MAPPER" not in item.properties - - def test_map_items_multiple(self) -> None: - def item_mapper(item: pystac.Item) -> list[pystac.Item]: - item2 = item.clone() - item2.id = item2.id + "_2" - item.properties["ITEM_MAPPER_1"] = "YEP" - item2.properties["ITEM_MAPPER_2"] = "YEP" - return [item, item2] - - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_1() - catalog_items = list(catalog.get_items(recursive=True)) - - new_cat = catalog.map_items(item_mapper) - - new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) - new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) - result_items = list(result_cat.get_items(recursive=True)) - - assert len(catalog_items) * 2 == len(result_items) - - ones, twos = 0, 0 - for item in result_items: - assert ("ITEM_MAPPER_1" in item.properties) or ( - "ITEM_MAPPER_2" in item.properties - ) - if "ITEM_MAPPER_1" in item.properties: - ones += 1 - - if "ITEM_MAPPER_2" in item.properties: - twos += 1 - - assert ones == twos - - for item in catalog.get_items(recursive=True): - assert ("ITEM_MAPPER_1" not in item.properties) and ( - "ITEM_MAPPER_2" not in item.properties - ) - - def test_map_items_multiple_2(self) -> None: - catalog = Catalog(id="test-1", description="Test1") - item1 = Item( - id="item1", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={}, - ) - item1.add_asset("ortho", Asset(href="/some/ortho.tif")) - catalog.add_item(item1) - kitten = Catalog(id="test-kitten", description="A cuter version of catalog") - catalog.add_child(kitten) - item2 = Item( - id="item2", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={}, - ) - item2.add_asset("ortho", Asset(href="/some/other/ortho.tif")) - kitten.add_item(item2) - - def modify_item_title(item: pystac.Item) -> pystac.Item: - item.properties["title"] = "Some title" - return item - - def duplicate_item(item: pystac.Item) -> list[pystac.Item]: - duplicated_item = item.clone() - duplicated_item.id += "-duplicated" - return [item, duplicated_item] - - c = catalog.map_items(modify_item_title) - c = c.map_items(duplicate_item) - new_catalog = c - - items = new_catalog.get_items(recursive=True) - assert len(list(items)) == 4 - - def test_map_assets_single(self) -> None: - changed_asset = "d43bead8-e3f8-4c51-95d6-e24e750a402b" - - def asset_mapper(key: str, asset: pystac.Asset) -> pystac.Asset: - if key == changed_asset: - asset.title = "NEW TITLE" - - return asset - - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_2() - - new_cat = catalog.map_assets(asset_mapper) - - new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) - new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) - - found = False - for item in result_cat.get_items(recursive=True): - for key, asset in item.assets.items(): - if key == changed_asset: - found = True - assert asset.title == "NEW TITLE" - else: - assert asset.title != "NEW TITLE" - assert found - - def test_map_assets_tup(self) -> None: - changed_assets: list[str] = [] - - def asset_mapper( - key: str, asset: pystac.Asset - ) -> pystac.Asset | tuple[str, pystac.Asset]: - if asset.media_type and "geotiff" in asset.media_type: - asset.title = "NEW TITLE" - changed_assets.append(key) - return (f"{key}-modified", asset) - else: - return asset - - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_2() - - new_cat = catalog.map_assets(asset_mapper) - - new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) - new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) - - found = False - not_found = False - for item in result_cat.get_items(recursive=True): - for key, asset in item.assets.items(): - if key.replace("-modified", "") in changed_assets: - found = True - assert asset.title == "NEW TITLE" - else: - not_found = True - assert asset.title != "NEW TITLE" - - assert found - assert not_found - - def test_map_assets_multi(self) -> None: - changed_assets = [] - - def asset_mapper( - key: str, asset: pystac.Asset - ) -> pystac.Asset | dict[str, pystac.Asset]: - if asset.media_type and "geotiff" in asset.media_type: - changed_assets.append(key) - mod1 = asset.clone() - mod1.title = "NEW TITLE 1" - mod2 = asset.clone() - mod2.title = "NEW TITLE 2" - return {f"{key}-mod-1": mod1, f"{key}-mod-2": mod2} - else: - return asset - - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = TestCases.case_2() - - new_cat = catalog.map_assets(asset_mapper) - - new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) - new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) - - found1 = False - found2 = False - not_found = False - for item in result_cat.get_items(recursive=True): - for key, asset in item.assets.items(): - if key.replace("-mod-1", "") in changed_assets: - found1 = True - assert asset.title == "NEW TITLE 1" - elif key.replace("-mod-2", "") in changed_assets: - found2 = True - assert asset.title == "NEW TITLE 2" - else: - not_found = True - assert asset.title != "NEW TITLE" - - assert found1 - assert found2 - assert not_found - - def test_make_all_asset_hrefs_absolute(self) -> None: - cat = TestCases.case_2() - cat.make_all_asset_hrefs_absolute() - ID = "cf73ec1a-d790-4b59-b077-e101738571ed" - item = next(cat.get_items(ID, recursive=True)) - href = item.assets[ID].href - assert is_absolute_href(href) - - def test_make_all_asset_hrefs_relative(self) -> None: - cat = TestCases.case_2() - ID = "cf73ec1a-d790-4b59-b077-e101738571ed" - item = next(cat.get_items(ID, recursive=True)) - asset = item.assets[ID] - original_href = asset.href - cat.make_all_asset_hrefs_absolute() - - assert is_absolute_href(asset.href) - - cat.make_all_asset_hrefs_relative() - - assert not is_absolute_href(asset.href) - assert asset.href == original_href - - @pytest.mark.parametrize("catalog", TestCases.all_test_catalogs()) - def test_make_all_links_relative_or_absolute(self, catalog: Catalog) -> None: - def check_all_relative(cat: Catalog) -> None: - for root, catalogs, items in cat.walk(): - for link in root.links: - if link.rel in HIERARCHICAL_LINKS: - assert not is_absolute_href(link.href) - for item in items: - for link in item.links: - if link.rel in HIERARCHICAL_LINKS: - assert not is_absolute_href(link.href) - - def check_all_absolute(cat: Catalog) -> None: - for root, catalogs, items in cat.walk(): - for link in root.links: - assert is_absolute_href(link.href) - for item in items: - for link in item.links: - assert is_absolute_href(link.href) - - with tempfile.TemporaryDirectory() as tmp_dir: - c2 = catalog.full_copy() - c2.normalize_hrefs(tmp_dir) - c2.catalog_type = CatalogType.RELATIVE_PUBLISHED - check_all_relative(c2) - c2.catalog_type = CatalogType.ABSOLUTE_PUBLISHED - check_all_absolute(c2) - - @pytest.mark.block_network() - def test_self_contained_catalog_collection_item_links(self) -> None: - """See issue https://github.com/stac-utils/pystac/issues/657""" - with tempfile.TemporaryDirectory() as tmp_dir: - catalog = pystac.Catalog( - id="catalog-issue-657", description="catalog-issue-657" - ) - collection = pystac.Collection( - "collection-issue-657", - "collection-issue-657", - pystac.Extent( - spatial=pystac.SpatialExtent([[-180.0, -90.0, 180.0, 90.0]]), - temporal=pystac.TemporalExtent([[datetime(2021, 11, 1), None]]), - ), - license="other", - ) - - item = pystac.Item( - id="item-issue-657", - stac_extensions=[], - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime(2021, 11, 1), - properties={}, - ) - - collection.add_item(item) - catalog.add_child(collection) - - catalog.normalize_hrefs(tmp_dir) - catalog.validate_all() - - catalog.save(catalog_type=CatalogType.SELF_CONTAINED) - with open( - f"{tmp_dir}/collection-issue-657/item-issue-657/item-issue-657.json" - ) as f: - item_json = json.load(f) - - for link in item_json["links"]: - # self links are always absolute - if link["rel"] == "self": - continue - - href = link["href"] - assert not is_absolute_href(href), ( - f"Link with rel={link['rel']} is absolute!" - ) - - def test_full_copy_and_normalize_works_with_created_stac(self) -> None: - cat = TestCases.case_3() - cat_copy = cat.full_copy() - cat_copy.normalize_hrefs("http://example.com") - for root, catalogs, items in cat_copy.walk(): - for link in root.links: - if link.rel != "self": - assert link.target is not None - for item in items: - for link in item.links: - if link.rel != "self": - assert link.get_href() is not None - - def test_extra_fields(self) -> None: - catalog = TestCases.case_1() - - catalog.extra_fields["custom_field"] = "Special content" - - with tempfile.TemporaryDirectory() as tmp_dir: - p = os.path.join(tmp_dir, "catalog.json") - catalog.save_object(include_self_link=False, dest_href=p) - with open(p) as f: - cat_json = json.load(f) - assert "custom_field" in cat_json - assert cat_json["custom_field"] == "Special content" - - read_cat = pystac.Catalog.from_file(p) - assert "custom_field" in read_cat.extra_fields - assert read_cat.extra_fields["custom_field"] == "Special content" - - @pytest.mark.parametrize("cat", TestCases.all_test_catalogs()) - @pytest.mark.vcr() - def test_validate_all(self, cat: Catalog) -> None: - # If hrefs are not set, it will fail validation. - if cat.get_self_href() is None: - cat.normalize_hrefs("/tmp") - cat.validate_all() - - @pytest.mark.vcr() - def test_validate_all_invalid(self) -> None: - # Make one invalid, write it off, read it in, ensure it throws - cat = TestCases.case_1() - item = next(cat.get_items("area-1-1-labels", recursive=True)) - item.geometry = {"type": "INVALID", "coordinates": "NONE"} - with tempfile.TemporaryDirectory() as tmp_dir: - cat.normalize_hrefs(tmp_dir) - cat.save(catalog_type=pystac.CatalogType.SELF_CONTAINED) - - cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) - - with pytest.raises(pystac.STACValidationError): - cat2.validate_all() - - def test_set_hrefs_manually(self) -> None: - catalog = TestCases.case_1() - - # Modify the datetimes - year = 2004 - month = 2 - for item in catalog.get_items(recursive=True): - assert item.datetime is not None - item.datetime = item.datetime.replace(year=year, month=month) - year += 1 - month += 1 - - with tempfile.TemporaryDirectory() as tmp_dir: - for root, _, items in catalog.walk(): - # Set root's HREF based off the parent - parent = root.get_parent() - if parent is None: - root_dir = tmp_dir - else: - d = os.path.dirname(parent.self_href) - root_dir = os.path.join(d, root.id) - root_href = os.path.join(root_dir, root.DEFAULT_FILE_NAME) - root.set_self_href(root_href) - - # Set each item's HREF based on it's datetime - for item in items: - assert item.datetime is not None - item_href = "{}/{}-{}/{}.json".format( - root_dir, item.datetime.year, item.datetime.month, item.id - ) - item.set_self_href(item_href) - - catalog.save(catalog_type=CatalogType.SELF_CONTAINED) - - read_catalog = Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) - - for root, _, items in read_catalog.walk(): - parent = root.get_parent() - if parent is None: - assert root.get_self_href() == make_posix_style( - os.path.join(tmp_dir, "catalog.json") - ) - else: - d = os.path.dirname(parent.self_href) - assert root.get_self_href() == make_posix_style( - os.path.join(d, root.id, root.DEFAULT_FILE_NAME) - ) - for item in items: - assert item.datetime is not None - end = posixpath.join( - f"{item.datetime.year}-{item.datetime.month}", - f"{item.id}.json", - ) - self_href = item.get_self_href() - assert self_href is not None - assert self_href.endswith(end), "{} does not end with {}".format( - self_href, end - ) - - @pytest.mark.parametrize("cat", TestCases.all_test_catalogs()) - def test_collections_cache_correctly(self, cat: Catalog) -> None: - mock_io = MockStacIO() - cat._stac_io = mock_io - expected_collection_reads = set() - for root, _, items in cat.walk(): - if isinstance(root, Collection) and root != cat: - expected_collection_reads.add(root.get_self_href()) - - # Iterate over items to make sure they are read - assert list(items) is not None - - call_uris: list[Any] = [ - call[0][0] - for call in mock_io.mock.read_text.call_args_list - if call[0][0] in expected_collection_reads - ] - - for collection_uri in expected_collection_reads: - calls = len([x for x in call_uris if x == collection_uri]) - assert calls == 1, "{} was read {} times instead of once!".format( - collection_uri, calls - ) - - def test_reading_iterating_and_writing_works_as_expected(self) -> None: - """Test case to cover issue #88""" - stac_uri = TestCases.get_path("data-files/catalogs/test-case-6/catalog.json") - cat = Catalog.from_file(stac_uri) - - # Iterate over the items. This was causing failure in - # the later iterations as per issue #88 - for item in cat.get_items(recursive=True): - pass - - with tempfile.TemporaryDirectory() as tmp_dir: - new_stac_uri = os.path.join(tmp_dir, "test-case-6") - cat.normalize_hrefs(new_stac_uri) - cat.save(catalog_type=CatalogType.SELF_CONTAINED) - - # Open the local copy and iterate over it. - cat2 = Catalog.from_file(os.path.join(new_stac_uri, "catalog.json")) - - for item in cat2.get_items(recursive=True): - # Iterate again over the items. This would fail in #88 - pass - - def test_get_children_cbers(self) -> None: - cat = TestCases.case_6() - assert len(list(cat.get_children())) == 4 - - def test_resolve_planet(self) -> None: - """Test against a bug that caused infinite recursion during link resolution""" - cat = TestCases.case_8() - for root, _, items in cat.walk(): - for item in items: - item.resolve_links() - root.resolve_links() - - def test_handles_children_with_same_id(self) -> None: - # This catalog has the root and child collection share an ID. - cat = pystac.Catalog.from_file( - TestCases.get_path("data-files/invalid/shared-id/catalog.json") - ) - items = list(cat.get_items(recursive=True)) - - assert len(items) == 1 - - def test_catalog_with_href_caches_by_href(self) -> None: - cat = TestCases.case_1() - cache = cat._resolved_objects - - # Since all of our STAC objects have HREFs, everything should be - # cached only by HREF - assert len(cache.id_keys_to_objects) == 0 - - def test_from_invalid_dict_raises_exception(self) -> None: - stac_io = pystac.StacIO.default() - collection_dict = stac_io.read_json( - TestCases.get_path("data-files/collections/multi-extent.json") - ) - with pytest.raises(pystac.STACTypeError): - _ = pystac.Catalog.from_dict(collection_dict) - - def test_get_collections(self) -> None: - catalog = TestCases.case_2() - collections = list(catalog.get_collections()) - - assert len(collections) > 0 - assert all(isinstance(c, pystac.Collection) for c in collections) - - def test_get_all_collections(self) -> None: - catalog = TestCases.case_1() - all_collections = list(catalog.get_all_collections()) - - assert len(all_collections) == 4 - assert all(isinstance(c, pystac.Collection) for c in all_collections) - - def test_get_single_links_media_type(self) -> None: - catalog = TestCases.case_1() - - catalog.links.append( - pystac.Link(rel="search", target="./search.html", media_type="text/html") - ) - catalog.links.append( - pystac.Link( - rel="search", target="./search.json", media_type="application/geo+json" - ) - ) - - html_link = catalog.get_single_link(rel="search") - assert html_link is not None - assert html_link.href == "./search.html" - html_link = catalog.get_single_link(media_type="text/html") - assert html_link is not None - assert html_link.href == "./search.html" - json_link = catalog.get_single_link( - rel="search", media_type="application/geo+json" - ) - assert json_link is not None - assert json_link.href == "./search.json" - no_link = catalog.get_single_link(rel="via") - assert no_link is None - first_link = catalog.get_single_link() - assert first_link is not None - assert first_link.rel == "self" - - def test_get_links(self) -> None: - catalog = TestCases.case_1() - - catalog.links.append( - pystac.Link(rel="search", target="./search.html", media_type="text/html") - ) - catalog.links.append( - pystac.Link( - rel="search", target="./search.json", media_type="application/geo+json" - ) - ) - assert ( - len(catalog.get_links(rel="search", media_type="application/geo+json")) == 1 - ) - assert len(catalog.get_links(media_type="text/html")) == 1 - assert ( - len(catalog.get_links(media_type=["text/html", "application/geo+json"])) - == 2 - ) - assert len(catalog.get_links(rel="search")) == 2 - assert len(catalog.get_links(rel="via")) == 0 - assert len(catalog.get_links()) == 6 - - def test_to_dict_no_self_href(self) -> None: - catalog = Catalog(id="an-id", description="A test Catalog") - d = catalog.to_dict(include_self_link=False) - Catalog.from_dict(d) - - -class TestFullCopy: - def check_link(self, link: pystac.Link, tag: str) -> None: - if link.is_resolved(): - target_href: str = cast(pystac.STACObject, link.target).self_href - else: - target_href = str(link.target) - assert tag in target_href, '[{}] {} does not contain "{}"'.format( - link.rel, target_href, tag - ) - - def check_item(self, item: Item, tag: str) -> None: - for link in item.links: - self.check_link(link, tag) - - def check_catalog(self, c: Catalog, tag: str) -> None: - assert len(c.get_links("root")) == 1, f"Failure for catalog: {c}" - - for link in c.links: - self.check_link(link, tag) - - for child in c.get_children(): - self.check_catalog(child, tag) - - for item in c.get_items(): - self.check_item(item, tag) - - def test_full_copy_1(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - cat = Catalog(id="test", description="test catalog") - - item = Item( - id="test_item", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={}, - ) - - cat.add_item(item) - - cat.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-1-source")) - cat2 = cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-1-dest")) - - self.check_catalog(cat, "source") - self.check_catalog(cat2, "dest") - - def test_full_copy_2(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - cat = Catalog(id="test", description="test catalog") - image_item = Item( - id="Imagery", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={}, - ) - for key in ["ortho", "dsm"]: - image_item.add_asset( - key, - Asset(href=f"some/{key}.tif", media_type=MediaType.GEOTIFF), - ) - - label_item = Item( - id="Labels", - geometry=ARBITRARY_GEOM, - bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), - properties={}, - ) - cat.add_items([image_item, label_item]) - - cat.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-2-source")) - cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat2 = cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-2-dest")) - cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - self.check_catalog(cat, "source") - self.check_catalog(cat2, "dest") - - def test_full_copy_3(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - root_cat = TestCases.case_1() - root_cat.normalize_hrefs( - os.path.join(tmp_dir, "catalog-full-copy-3-source") - ) - root_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat2 = root_cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-3-dest")) - cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - self.check_catalog(root_cat, "source") - self.check_catalog(cat2, "dest") - - def test_full_copy_4(self) -> None: - with tempfile.TemporaryDirectory() as tmp_dir: - root_cat = TestCases.case_2() - root_cat.normalize_hrefs( - os.path.join(tmp_dir, "catalog-full-copy-4-source") - ) - root_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - cat2 = root_cat.full_copy() - cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-4-dest")) - cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) - - self.check_catalog(root_cat, "source") - self.check_catalog(cat2, "dest") - - # Check that the relative asset link was saved correctly in the copy. - ID = "cf73ec1a-d790-4b59-b077-e101738571ed" - item = next(cat2.get_items(ID, recursive=True)) - href = item.assets[ID].get_absolute_href() - assert href is not None - assert os.path.exists(href) - - -class TestCatalogSubClass: - """This tests cases related to creating classes inheriting from pystac.Catalog to - ensure that inheritance, class methods, etc. function as expected.""" - - case_1 = TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") - - class BasicCustomCatalog(pystac.Catalog): - def get_items(self) -> Iterator[Item]: # type: ignore - # This get_items does not have the `recursive` kwarg. This mimics - # the current state of pystac-client and is intended to test - # backwards compatibility of inherited classes - return super().get_items() - - def test_from_dict_returns_subclass(self) -> None: - self.stac_io = pystac.StacIO.default() - catalog_dict = self.stac_io.read_json(self.case_1) - custom_catalog = self.BasicCustomCatalog.from_dict(catalog_dict) - assert isinstance(custom_catalog, self.BasicCustomCatalog) - - def test_from_file_returns_subclass(self) -> None: - custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) - assert isinstance(custom_catalog, self.BasicCustomCatalog) - - def test_clone(self) -> None: - custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) - cloned_catalog = custom_catalog.clone() - assert isinstance(cloned_catalog, self.BasicCustomCatalog) - - def test_get_all_items_works(self) -> None: - custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) - cloned_catalog = custom_catalog.clone() - with pytest.warns(DeprecationWarning): - cloned_catalog.get_all_items() - - def test_get_item_works(self) -> None: - custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) - cloned_catalog = custom_catalog.clone() - with pytest.warns(DeprecationWarning): - cloned_catalog.get_item("area-1-1-imagery") - - -def test_custom_catalog_from_dict(catalog: Catalog) -> None: - # https://github.com/stac-utils/pystac/issues/862 - class CustomCatalog(Catalog): - @classmethod - def from_dict( - cls, - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> CustomCatalog: - return super().from_dict(d) - - _ = CustomCatalog.from_dict(catalog.to_dict()) - - -@pytest.mark.parametrize("add_canonical", (True, False)) -def test_remove_hierarchical_links( - test_case_1_catalog: Catalog, add_canonical: bool -) -> None: - test_case_1_catalog.remove_hierarchical_links(add_canonical=add_canonical) - for link in test_case_1_catalog.links: - assert not link.is_hierarchical() - assert bool(test_case_1_catalog.get_single_link("canonical")) == add_canonical - - -def test_fully_resolve(tmp_path: Path, test_case_1_catalog: Catalog) -> None: - test_case_1_catalog.save(dest_href=str(tmp_path / "before")) - assert len(list((tmp_path / "before").glob("**/*.json"))) == 1 - test_case_1_catalog.fully_resolve() - test_case_1_catalog.save(dest_href=str(tmp_path / "after")) - assert len(list((tmp_path / "after").glob("**/*.json"))) == 15 - - -def test_get_items_with_multiple_ids(test_case_1_catalog: Catalog) -> None: - cat = test_case_1_catalog - items = cat.get_items("area-2-1-imagery", "area-1-1-labels", recursive=True) - assert len(list(items)) == 2 - - -@pytest.mark.vcr() -def test_validate_all_with_max_n(test_case_1_catalog: Catalog) -> None: - cat = test_case_1_catalog - assert cat.validate_all() == 8 - assert cat.validate_all(max_items=6) == 6 - assert cat.validate_all(max_items=1) == 1 - - -@pytest.mark.vcr() -def test_validate_all_with_recusive_off(test_case_1_catalog: Catalog) -> None: - cat = test_case_1_catalog - assert cat.validate_all() == 8 - assert cat.validate_all(recursive=False) == 0 - - -@pytest.fixture -def nested_catalog() -> pystac.Catalog: - """ - Structure: - - ├── catalog.json - ├── products - │ ├── catalog.json - │ └── product_a - │ └── catalog.json - └── variables - ├── catalog.json - └── variable_a - └── catalog.json - └── variable_a_1 - └── collection.json - """ - root = pystac.Catalog("root", "root") - variables = pystac.Catalog("variables", "variables") - products = pystac.Catalog("products", "products") - - root.add_child(variables) - root.add_child(products) - - variable_a = pystac.Catalog("variable_a", "variable_a") - product_a = pystac.Catalog("product_a", "product_a") - - variables.add_child(variable_a) - products.add_child(product_a) - - variable_a_1 = pystac.Collection( - "variable_a_1", "variable_a_1", extent=ARBITRARY_EXTENT - ) - variable_a.add_child(variable_a_1) - - return root - - -def test_get_all_collections_deeply_nested(nested_catalog: pystac.Catalog) -> None: - catalog = nested_catalog - all_collections = list(catalog.get_all_collections()) - - assert len(all_collections) == 1 - assert all(isinstance(c, pystac.Collection) for c in all_collections) - - -def test_set_parent_false_stores_in_proper_place_on_normalize_and_save( - nested_catalog: pystac.Catalog, tmp_path: Path -) -> None: - root = nested_catalog - product_a = next(root.get_child("products").get_children()) # type: ignore - variable_a = next(root.get_child("variables").get_children()) # type: ignore - - variable_a.add_child(product_a, set_parent=False) - - root.normalize_and_save( - root_href=str(tmp_path), catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED - ) - assert (tmp_path / "products" / "product_a").exists() - assert not (tmp_path / "variables" / "variable_a" / "product_a").exists() - - -def test_set_parent_false_stores_in_proper_place_on_save( - nested_catalog: pystac.Catalog, tmp_path: Path -) -> None: - nested_catalog.normalize_and_save( - root_href=str(tmp_path), catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED - ) - root = pystac.Catalog.from_file(tmp_path / "catalog.json") - product_a = next(root.get_child("products").get_children()) # type: ignore - variable_a = next(root.get_child("variables").get_children()) # type: ignore - - variable_a.add_child(product_a, set_parent=False) - - root.save(pystac.CatalogType.SELF_CONTAINED, dest_href=str(tmp_path)) - - assert (tmp_path / "products" / "product_a").exists() - assert not (tmp_path / "variables" / "variable_a" / "product_a").exists() - - -BEST_PRACTICE_CATALOG_TEMPLATE = "{id}" -BEST_PRACTICE_ITEM_TEMPLATE = "{id}" -TEST_CATALOG_TEMPLATE = "cat/${id}/${description}" -TEST_ITEM_TEMPLATE = "cat/items/${id}" -STRATEGY = TemplateLayoutStrategy( - catalog_template=TEST_CATALOG_TEMPLATE, item_template=TEST_ITEM_TEMPLATE -) - - -@pytest.mark.parametrize( - "root_strategy,sub_strategy,provided_root_strategy,provided_sub_strategy,root_template,sub_template", - [ - ( - None, - None, - None, - None, - BEST_PRACTICE_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - ), - (STRATEGY, None, None, None, TEST_CATALOG_TEMPLATE, TEST_CATALOG_TEMPLATE), - ( - STRATEGY, - BestPracticesLayoutStrategy(), - None, - None, - TEST_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - ), - ( - STRATEGY, - None, - BestPracticesLayoutStrategy(), - None, - BEST_PRACTICE_CATALOG_TEMPLATE, - TEST_CATALOG_TEMPLATE, - ), - ( - STRATEGY, - None, - None, - BestPracticesLayoutStrategy(), - TEST_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - ), - ], -) -def test_add_child_layout_strategy( - root_strategy: HrefLayoutStrategy, - sub_strategy: HrefLayoutStrategy, - provided_root_strategy: HrefLayoutStrategy, - provided_sub_strategy: HrefLayoutStrategy, - root_template: str, - sub_template: str, -) -> None: - """Test for layout strategy when adding a child. - - If no layout strategy is specified, children HREF should - always follow BestPracticesLayoutStrategy. - If only root strategy is set, all children HREFs should - follow than strategy. - If root and child strategy are set, root and child children - may follow different strategies. - Strategy provided to `add_child` always overrides other settings. - """ - - base_url = "http://example.com" - catalog = Catalog( - id="test", - description="test desc", - href=f"{base_url}/catalog.json", - strategy=root_strategy, - ) - subcat = Catalog(id="subcat", description="subcat desc", strategy=sub_strategy) - subsubcat = Catalog(id="subsubcat", description="subsubcat desc") - - catalog.add_child(subcat, strategy=provided_root_strategy) - subcat.add_child(subsubcat, strategy=provided_sub_strategy) - - root_template = root_template.format( - id=subcat.id, description=subcat.description - ).replace("$", "") - sub_template = sub_template.format( - id=subsubcat.id, description=subsubcat.description - ).replace("$", "") - - assert subcat.self_href == f"{base_url}/{root_template}/catalog.json" - assert ( - subsubcat.self_href == f"{base_url}/{root_template}/{sub_template}/catalog.json" - ) - - -@pytest.mark.parametrize( - "root_strategy,sub_strategy,provided_root_strategy," - "root_template,sub_template,norm_template", - [ - ( - None, - None, - None, - BEST_PRACTICE_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - ), - ( - STRATEGY, - None, - None, - TEST_CATALOG_TEMPLATE, - TEST_CATALOG_TEMPLATE, - TEST_CATALOG_TEMPLATE, - ), - ( - STRATEGY, - BestPracticesLayoutStrategy(), - None, - TEST_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - TEST_CATALOG_TEMPLATE, - ), - ( - STRATEGY, - None, - BestPracticesLayoutStrategy(), - TEST_CATALOG_TEMPLATE, - TEST_CATALOG_TEMPLATE, - BEST_PRACTICE_CATALOG_TEMPLATE, - ), - ], -) -def test_add_child_layout_strategy_normalize( - root_strategy: HrefLayoutStrategy, - sub_strategy: HrefLayoutStrategy, - provided_root_strategy: HrefLayoutStrategy, - root_template: str, - sub_template: str, - norm_template: str, -) -> None: - """Test for layout strategy when adding a child and normalizing HREFs. - - If no layout strategy is specified, children HREF - should always follow BestPracticesLayoutStrategy. - If only root strategy is set, all children HREFs - should follow than strategy. - If root and child strategy are set, root strategy - overrides child strategy. - Strategy provided to `normalize_href` always - overrides other settings. - """ - - base_url = "http://example.com" - catalog = Catalog( - id="test", - description="test desc", - href=f"{base_url}/catalog.json", - strategy=root_strategy, - ) - subcat = Catalog(id="subcat", description="subcat desc", strategy=sub_strategy) - subsubcat = Catalog(id="subsubcat", description="subsubcat desc") - catalog.add_child(subcat) - subcat.add_child(subsubcat) - - _root_template = root_template.format( - id=subcat.id, description=subcat.description - ).replace("$", "") - _sub_template = sub_template.format( - id=subsubcat.id, description=subsubcat.description - ).replace("$", "") - - assert subcat.self_href == f"{base_url}/{_root_template}/catalog.json" - assert ( - subsubcat.self_href - == f"{base_url}/{_root_template}/{_sub_template}/catalog.json" - ) - - catalog.normalize_hrefs(base_url, strategy=provided_root_strategy) - - _root_template = norm_template.format( - id=subcat.id, description=subcat.description - ).replace("$", "") - _sub_template = norm_template.format( - id=subsubcat.id, description=subsubcat.description - ).replace("$", "") - - assert subcat.self_href == f"{base_url}/{_root_template}/catalog.json" - assert ( - subsubcat.self_href - == f"{base_url}/{_root_template}/{_sub_template}/catalog.json" - ) - - -@pytest.mark.parametrize( - "root_strategy,provided_strategy,template", - [ - (None, None, BEST_PRACTICE_ITEM_TEMPLATE), - (STRATEGY, None, TEST_ITEM_TEMPLATE), - (STRATEGY, BestPracticesLayoutStrategy(), BEST_PRACTICE_ITEM_TEMPLATE), - ], -) -def test_add_item_layout_strategy( - root_strategy: HrefLayoutStrategy, - provided_strategy: HrefLayoutStrategy, - template: str, -) -> None: - """Test for layout strategy when adding an item. - - If no layout strategy is specified, item HREF - should always follow BestPracticesLayoutStrategy. - If only root strategy is set, all item HREFs - should follow than strategy. - Strategy provided to `add_item` always overrides other settings. - """ - - base_url = "http://example.com" - item_id = "item_id" - catalog = Catalog( - id="test", - description="test desc", - href=f"{base_url}/catalog.json", - strategy=root_strategy, - ) - item = Item( - id=item_id, - geometry={ - "type": "Polygon", - "coordinates": [ - [ - [180.0, -90.0], - [180.0, 90.0], - [-180.0, 90.0], - [-180.0, -90.0], - [180.0, -90.0], - ] - ], - }, - bbox=[-180, -90, 180, 90], - datetime=datetime(2023, 1, 1), - properties={}, - assets={ - "data": Asset( - href="http://example.com/assets/data.tif", - roles=["data"], - title="DATA", - ) - }, - ) - - catalog.add_item(item, strategy=provided_strategy) - - template = template.format(id=item.id).replace("$", "") - - assert item.self_href == f"{base_url}/{template}/{item_id}.json" - - -def test_APILayoutStrategy_requires_root_to_be_url( - catalog: Catalog, collection: Collection, item: Item -) -> None: - collection.add_item(item) - catalog.add_child(collection) - with pytest.raises( - pystac.errors.STACError, - match="When using APILayoutStrategy the root_href must be a URL", - ): - catalog.normalize_hrefs(root_href="issues-1486", strategy=APILayoutStrategy()) - - -def test_get_child_links_cares_about_media_type(catalog: pystac.Catalog) -> None: - catalog.links.extend( - [ - pystac.Link( - rel="child", target="./child-1.json", media_type="application/json" - ), - pystac.Link( - rel="child", target="./child-2.json", media_type="application/geo+json" - ), - pystac.Link(rel="child", target="./child-3.json"), - # this one won't get counted since it's the wrong media_type - pystac.Link(rel="child", target="./child.html", media_type="text/html"), - ] - ) - - assert len(catalog.get_child_links()) == 3 - - -def test_get_item_links_cares_about_media_type(catalog: pystac.Catalog) -> None: - catalog.links.extend( - [ - pystac.Link( - rel="item", target="./item-1.json", media_type="application/json" - ), - pystac.Link( - rel="item", target="./item-2.json", media_type="application/geo+json" - ), - pystac.Link(rel="item", target="./item-3.json"), - # this one won't get counted since it's the wrong media_type - pystac.Link(rel="item", target="./item.html", media_type="text/html"), - ] - ) - - assert len(catalog.get_item_links()) == 3 - -def test_get_root_link_cares_about_media_type(catalog: pystac.Catalog) -> None: - catalog.links.insert( - 0, pystac.Link(rel="root", target="./self.json", media_type="text/html") - ) - root_link = catalog.get_root_link() - assert root_link and root_link.target != "./self.json" +from pystac import Catalog -def test_clone_extra_fields(catalog: Catalog) -> None: - catalog.extra_fields["foo"] = "bar" - cloned = catalog.clone() - assert cloned.extra_fields["foo"] == "bar" +def test_catalog() -> None: + catalog = Catalog(id="an-id", description="a description") + catalog.validate() -def test_warn_if_next_link_present(catalog: Catalog) -> None: - catalog.links.append(Link(rel="next", target="./next.json")) - with pytest.warns(UserWarning): - _ = list(catalog.get_children()) +def test_catalog_from_dict(examples_path: Path) -> None: + with open(examples_path / "catalog.json") as f: + data = json.load(f) + catalog = Catalog.from_dict(data) + catalog.validate() + assert catalog.to_dict() == data diff --git a/tests/test_collection.py b/tests/test_collection.py index d44f82f57..bc9f653eb 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -1,844 +1,20 @@ -from __future__ import annotations - import json -import os -import tempfile -from collections.abc import Iterator -from copy import deepcopy -from datetime import datetime -from typing import Any +from pathlib import Path import pytest -from dateutil import tz - -import pystac -from pystac import ( - Asset, - Catalog, - CatalogType, - Collection, - Extent, - Item, - ItemCollection, - Provider, - SpatialExtent, - TemporalExtent, -) -from pystac.extensions.eo import EOExtension -from pystac.utils import datetime_to_str, get_required, str_to_datetime -from pystac.validation import validate_dict -from tests.utils import ARBITRARY_BBOX, ARBITRARY_GEOM, TestCases - -TEST_DATETIME = datetime(2020, 3, 14, 16, 32) - - -def test_provider_to_from_dict() -> None: - provider_dict = { - "name": "Remote Data, Inc", - "description": "Producers of awesome spatiotemporal assets", - "roles": ["producer", "processor"], - "url": "http://remotedata.io", - "extension:field": "some value", - } - expected_extra_fields = {"extension:field": provider_dict["extension:field"]} - - provider = Provider.from_dict(provider_dict) - - assert ( - provider_dict["name"], - provider_dict["description"], - provider_dict["roles"], - provider_dict["url"], - expected_extra_fields, - provider_dict, - ) == ( - provider.name, - provider.description, - provider.roles, - provider.url, - provider.extra_fields, - provider.to_dict(), - ) - - -def test_spatial_extent_from_coordinates() -> None: - extent = SpatialExtent.from_coordinates(ARBITRARY_GEOM["coordinates"]) - - assert len(extent.bboxes) == 1 - bbox = extent.bboxes[0] - assert len(bbox) == 4 - for x in bbox: - assert isinstance(x, float) - - -def test_read_eo_items_are_heritable() -> None: - cat = TestCases.case_5() - item = next(cat.get_items(recursive=True)) - - assert EOExtension.has_extension(item) - - -def test_save_uses_previous_catalog_type() -> None: - collection = TestCases.case_8() - assert collection.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION - assert collection.catalog_type == CatalogType.SELF_CONTAINED - with tempfile.TemporaryDirectory() as tmp_dir: - collection.normalize_hrefs(tmp_dir) - href = collection.self_href - collection.save() - - collection2 = pystac.Collection.from_file(href) - assert collection2.catalog_type == CatalogType.SELF_CONTAINED - - -def test_clone_uses_previous_catalog_type() -> None: - catalog = TestCases.case_8() - assert catalog.catalog_type == CatalogType.SELF_CONTAINED - clone = catalog.clone() - assert clone.catalog_type == CatalogType.SELF_CONTAINED - - -def test_clone_cant_mutate_original() -> None: - collection = TestCases.case_8() - assert collection.keywords == ["disaster", "open"] - clone = collection.clone() - clone.extra_fields["test"] = "extra" - assert "test" not in collection.extra_fields - assert clone.keywords is not None - clone.keywords.append("clone") - assert clone.keywords == ["disaster", "open", "clone"] - assert collection.keywords == ["disaster", "open"] - assert id(collection.summaries) != id(clone.summaries) - - -def test_multiple_extents() -> None: - cat1 = TestCases.case_1() - country = cat1.get_child("country-1") - assert country is not None - col1 = country.get_child("area-1-1") - assert col1 is not None - col1.validate() - assert isinstance(col1, Collection) - validate_dict(col1.to_dict(), pystac.STACObjectType.COLLECTION) - - multi_ext_uri = TestCases.get_path("data-files/collections/multi-extent.json") - with open(multi_ext_uri) as f: - multi_ext_dict = json.load(f) - validate_dict(multi_ext_dict, pystac.STACObjectType.COLLECTION) - assert isinstance(Collection.from_dict(multi_ext_dict), Collection) - - multi_ext_col = Collection.from_file(multi_ext_uri) - multi_ext_col.validate() - ext = multi_ext_col.extent - extent_dict = multi_ext_dict["extent"] - assert isinstance(ext, Extent) - assert isinstance(ext.spatial.bboxes[0], list) - assert len(ext.spatial.bboxes) == 3 - assert ext.to_dict() == extent_dict - - cloned_ext = ext.clone() - assert cloned_ext.to_dict() == multi_ext_dict["extent"] - - -def test_extra_fields() -> None: - catalog = TestCases.case_2() - collection = catalog.get_child("1a8c1632-fa91-4a62-b33e-3a87c2ebdf16") - assert collection is not None - - collection.extra_fields["test"] = "extra" - - with tempfile.TemporaryDirectory() as tmp_dir: - p = os.path.join(tmp_dir, "collection.json") - collection.save_object(include_self_link=False, dest_href=p) - with open(p) as f: - col_json = json.load(f) - assert "test" in col_json - assert col_json["test"] == "extra" - - read_col = pystac.Collection.from_file(p) - assert "test" in read_col.extra_fields - assert read_col.extra_fields["test"] == "extra" - - -def test_update_extents() -> None: - catalog = TestCases.case_2() - base_collection = catalog.get_child("1a8c1632-fa91-4a62-b33e-3a87c2ebdf16") - assert isinstance(base_collection, Collection) - base_extent = base_collection.extent - collection = base_collection.clone() - - item1 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-180, -90, 180, 90], - datetime=TEST_DATETIME, - properties={"key": "one"}, - stac_extensions=["eo", "commons"], - ) - - item2 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-180, -90, 180, 90], - datetime=None, - properties={ - "start_datetime": datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0)), - "end_datetime": datetime_to_str(datetime(2000, 2, 1, 12, 0, 0, 0)), - }, - stac_extensions=["eo", "commons"], - ) - - collection.add_item(item1) - - collection.update_extent_from_items() - assert [[-180, -90, 180, 90]] == collection.extent.spatial.bboxes - assert len(base_extent.spatial.bboxes[0]) == len( - collection.extent.spatial.bboxes[0] - ) - assert base_extent.temporal.intervals != collection.extent.temporal.intervals - - collection.remove_item("test-item-1") - collection.update_extent_from_items() - assert [[-180, -90, 180, 90]] != collection.extent.spatial.bboxes - collection.add_item(item2) - - collection.update_extent_from_items() - - assert [ - [ - item2.common_metadata.start_datetime, - base_extent.temporal.intervals[0][1], - ] - ] == collection.extent.temporal.intervals - - -def test_supplying_href_in_init_does_not_fail() -> None: - test_href = "http://example.com/collection.json" - spatial_extent = SpatialExtent(bboxes=[ARBITRARY_BBOX]) - temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) - - collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) - collection = Collection( - id="test", description="test desc", extent=collection_extent, href=test_href - ) - - assert collection.get_self_href() == test_href - -def test_collection_with_href_caches_by_href() -> None: - collection = pystac.Collection.from_file( - TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") - ) - cache = collection._resolved_objects +from pystac import Collection - # Since all of our STAC objects have HREFs, everything should be - # cached only by HREF - assert len(cache.id_keys_to_objects) == 0 - -@pytest.mark.block_network -def test_assets() -> None: - path = TestCases.get_path("data-files/collections/with-assets.json") - with open(path) as f: - data = json.load(f) - collection = pystac.Collection.from_dict(data) +def test_collection() -> None: + collection = Collection(id="an-id", description="a-description") collection.validate() -def test_get_assets() -> None: - collection = pystac.Collection.from_file( - TestCases.get_path("data-files/collections/with-assets.json") - ) - - media_type_filter = collection.get_assets(media_type=pystac.MediaType.PNG) - assert list(media_type_filter.keys()) == ["thumbnail"] - role_filter = collection.get_assets(role="thumbnail") - assert list(role_filter.keys()) == ["thumbnail"] - multi_filter = collection.get_assets( - media_type=pystac.MediaType.PNG, role="thumbnail" - ) - assert list(multi_filter.keys()) == ["thumbnail"] - - no_filter = collection.get_assets() - assert no_filter is not collection.assets - assert list(no_filter.keys()) == ["thumbnail"] - no_filter["thumbnail"].description = "foo" - assert collection.assets["thumbnail"].description != "foo" - - no_assets = collection.get_assets(media_type=pystac.MediaType.HDF) - assert no_assets == {} - - -def test_removing_optional_attributes() -> None: - path = TestCases.get_path("data-files/collections/with-assets.json") - with open(path) as file: - data = json.load(file) - data["title"] = "dummy title" - data["stac_extensions"] = ["dummy extension"] - data["keywords"] = ["key", "word"] - data["providers"] = [{"name": "pystac"}] - collection = pystac.Collection.from_dict(data) - - # Assert we have everything set - assert collection.title - assert collection.stac_extensions - assert collection.keywords - assert collection.providers - assert collection.summaries - assert collection.assets - - # Remove all of the optional stuff - collection.title = None - collection.stac_extensions = [] - collection.keywords = [] - collection.providers = [] - collection.summaries = pystac.Summaries({}) - collection.assets = {} - - collection_as_dict = collection.to_dict() - for key in ( - "title", - "stac_extensions", - "keywords", - "providers", - "summaries", - "assets", - ): - assert key not in collection_as_dict - - -def test_from_dict_preserves_dict() -> None: - path = TestCases.get_path("data-files/collections/with-assets.json") - with open(path) as f: - collection_dict = json.load(f) - param_dict = deepcopy(collection_dict) - - # test that the parameter is preserved - _ = Collection.from_dict(param_dict) - assert param_dict == collection_dict - - # assert that the parameter is not preserved with - # non-default parameter - _ = Collection.from_dict(param_dict, preserve_dict=False, migrate=False) - assert param_dict != collection_dict - - -def test_from_dict_set_root() -> None: - path = TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") - with open(path) as f: - collection_dict = json.load(f) - catalog = pystac.Catalog(id="test", description="test desc") - collection = Collection.from_dict(collection_dict, root=catalog) - assert collection.get_root() is catalog - - -def test_schema_summary() -> None: - collection = pystac.Collection.from_file( - TestCases.get_path( - "data-files/examples/1.0.0/collection-only/collection-with-schemas.json" - ) - ) - instruments_schema = get_required( - collection.summaries.get_schema("instruments"), - collection.summaries, - "instruments", - ) - - assert isinstance(instruments_schema, dict) - - -def test_from_invalid_dict_raises_exception() -> None: - stac_io = pystac.StacIO.default() - catalog_dict = stac_io.read_json( - TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") - ) - with pytest.raises(pystac.STACTypeError): - _ = pystac.Collection.from_dict(catalog_dict) - - -def test_clone_preserves_assets() -> None: - path = TestCases.get_path("data-files/collections/with-assets.json") - original_collection = Collection.from_file(path) - assert len(original_collection.assets) > 0 - assert all( - asset.owner is original_collection - for asset in original_collection.assets.values() - ) - - cloned_collection = original_collection.clone() - - for key in original_collection.assets: - assert key in cloned_collection.assets, f"Failed to Preserve {key} asset" - cloned_asset = cloned_collection.assets.get(key) - if cloned_asset is not None: - assert cloned_asset.owner is cloned_collection, ( - f"Failed to set owner for {key}" - ) - - -def test_to_dict_no_self_href() -> None: - temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) - spatial_extent = SpatialExtent(bboxes=ARBITRARY_BBOX) - extent = Extent(spatial=spatial_extent, temporal=temporal_extent) - collection = Collection(id="an-id", description="A test Collection", extent=extent) - d = collection.to_dict(include_self_link=False) - Collection.from_dict(d) - - -def test_temporal_extent_init_typing() -> None: - # This test exists purely to test the typing of the intervals argument to - # TemporalExtent - start_datetime = str_to_datetime("2022-01-01T00:00:00Z") - end_datetime = str_to_datetime("2022-01-31T23:59:59Z") - - _ = TemporalExtent([[start_datetime, end_datetime]]) - - -@pytest.mark.block_network() -def test_temporal_extent_allows_single_interval() -> None: - start_datetime = str_to_datetime("2022-01-01T00:00:00Z") - end_datetime = str_to_datetime("2022-01-31T23:59:59Z") - - interval = [start_datetime, end_datetime] - temporal_extent = TemporalExtent(intervals=interval) - - assert temporal_extent.intervals == [interval] - - -@pytest.mark.block_network() -def test_temporal_extent_allows_single_interval_open_start() -> None: - end_datetime = str_to_datetime("2022-01-31T23:59:59Z") - - interval = [None, end_datetime] - temporal_extent = TemporalExtent(intervals=interval) - - assert temporal_extent.intervals == [interval] - - -@pytest.mark.block_network() -def test_temporal_extent_non_list_intervals_fails() -> None: - with pytest.raises(TypeError): - # Pass in non-list intervals - _ = TemporalExtent(intervals=1) # type: ignore - - -@pytest.mark.block_network() -def test_spatial_allows_single_bbox() -> None: - temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) - - # Pass in a single BBOX - spatial_extent = SpatialExtent(bboxes=ARBITRARY_BBOX) - - collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) - - collection = Collection( - id="test", description="test desc", extent=collection_extent - ) - - # HREF required by validation - collection.set_self_href("https://example.com/collection.json") - +@pytest.mark.vcr +def test_collection_from_dict(examples_path: Path) -> None: + with open(examples_path / "collection.json") as f: + data = json.load(f) + collection = Collection.from_dict(data) collection.validate() - - -@pytest.mark.block_network() -def test_spatial_extent_non_list_bboxes_fails() -> None: - with pytest.raises(TypeError): - # Pass in non-list bboxes - _ = SpatialExtent(bboxes=1) # type: ignore - - -def test_extent_from_items() -> None: - item1 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-10, -20, 0, -10], - datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - properties={}, - ) - - item2 = Item( - id="test-item-2", - geometry=ARBITRARY_GEOM, - bbox=[0, -9, 10, 1], - datetime=None, - properties={ - "start_datetime": datetime_to_str( - datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) - ), - "end_datetime": datetime_to_str( - datetime(2000, 7, 1, 12, 0, 0, 0, tzinfo=tz.UTC) - ), - }, - ) - - item3 = Item( - id="test-item-2", - geometry=ARBITRARY_GEOM, - bbox=[-5, -20, 5, 0], - datetime=None, - properties={ - "start_datetime": datetime_to_str( - datetime(2000, 12, 1, 12, 0, 0, 0, tzinfo=tz.UTC) - ), - "end_datetime": datetime_to_str( - datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - ), - }, - ) - - extent = Extent.from_items([item1, item2, item3]) - assert len(extent.spatial.bboxes) == 1 - assert extent.spatial.bboxes[0] == [-10, -20, 10, 1] - assert len(extent.temporal.intervals) == 1 - - interval = extent.temporal.intervals[0] - assert interval[0] == datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) - assert interval[1] == datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) - - -def test_extent_to_from_dict() -> None: - spatial_dict = { - "bbox": [ - [ - 172.91173669923782, - 1.3438851951615003, - 172.95469614953714, - 1.3690476620161975, - ] - ], - "extension:field": "spatial value", - } - temporal_dict = { - "interval": [["2020-12-11T22:38:32.125000Z", "2020-12-14T18:02:31.437000Z"]], - "extension:field": "temporal value", - } - extent_dict = { - "spatial": spatial_dict, - "temporal": temporal_dict, - "extension:field": "extent value", - } - expected_extent_extra_fields = { - "extension:field": extent_dict["extension:field"], - } - expected_spatial_extra_fields = { - "extension:field": spatial_dict["extension:field"], - } - expected_temporal_extra_fields = { - "extension:field": temporal_dict["extension:field"], - } - - extent = Extent.from_dict(extent_dict) - - assert expected_extent_extra_fields == extent.extra_fields - assert expected_spatial_extra_fields == extent.spatial.extra_fields - assert expected_temporal_extra_fields == extent.temporal.extra_fields - - assert extent_dict == extent.to_dict() - - -class TestCollectionSubClass: - """This tests cases related to creating classes inheriting from pystac.Catalog to - ensure that inheritance, class methods, etc. function as expected.""" - - MULTI_EXTENT = TestCases.get_path("data-files/collections/multi-extent.json") - - class BasicCustomCollection(pystac.Collection): - def get_items(self) -> Iterator[Item]: # type: ignore - # This get_items does not have the `recursive` kwarg. This mimics - # the current state of pystac-client and is intended to test - # backwards compatibility of inherited classes - return super().get_items() - - def test_from_dict_returns_subclass(self) -> None: - stac_io = pystac.StacIO.default() - collection_dict = stac_io.read_json(self.MULTI_EXTENT) - custom_collection = self.BasicCustomCollection.from_dict(collection_dict) - - assert isinstance(custom_collection, self.BasicCustomCollection) - - def test_from_file_returns_subclass(self) -> None: - custom_collection = self.BasicCustomCollection.from_file(self.MULTI_EXTENT) - - assert isinstance(custom_collection, self.BasicCustomCollection) - - def test_clone(self) -> None: - custom_collection = self.BasicCustomCollection.from_file(self.MULTI_EXTENT) - cloned_collection = custom_collection.clone() - - assert isinstance(cloned_collection, self.BasicCustomCollection) - - def test_collection_get_item_works(self) -> None: - path = TestCases.get_path( - "data-files/catalogs/test-case-1/country-1/area-1-1/collection.json" - ) - custom_collection = self.BasicCustomCollection.from_file(path) - collection = custom_collection.clone() - with pytest.warns(DeprecationWarning): - collection.get_item("area-1-1-imagery") - - -def test_collection_get_item_raises_type_error() -> None: - class BasicCustomCollection(pystac.Collection): - def get_items( # type: ignore - self, *, recursive: bool = False - ) -> Iterator[Item]: - # This get_items does not allow ids as args. - return super().get_items(recursive=recursive) - - path = TestCases.get_path( - "data-files/catalogs/test-case-1/country-1/area-1-1/collection.json" - ) - custom_collection = BasicCustomCollection.from_file(path) - collection = custom_collection.clone() - with pytest.raises(TypeError, match="takes 1 positional argument"): - collection.get_item("area-1-1-imagery") - - -def test_custom_collection_from_dict(collection: Collection) -> None: - # https://github.com/stac-utils/pystac/issues/862 - class CustomCollection(Collection): - @classmethod - def from_dict( - cls, - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> CustomCollection: - return super().from_dict(d) - - _ = CustomCollection.from_dict(collection.to_dict()) - - -@pytest.mark.parametrize("add_canonical", (True, False)) -def test_remove_hierarchical_links( - test_case_1_catalog: Catalog, add_canonical: bool -) -> None: - collection = list(test_case_1_catalog.get_all_collections())[0] - collection.remove_hierarchical_links(add_canonical=add_canonical) - for link in collection.links: - assert not link.is_hierarchical() - assert bool(collection.get_single_link("canonical")) == add_canonical - - -@pytest.mark.parametrize("child", ["country-1", "country-2"]) -def test_get_child_checks_links_where_hrefs_contains_id_first( - test_case_1_catalog: Catalog, child: str -) -> None: - cat1 = test_case_1_catalog - country = cat1.get_child(child) - assert country is not None - child_links = [link for link in cat1.links if link.rel == pystac.RelType.CHILD] - for link in child_links: - if country.id not in link.href: - assert not link.is_resolved() - - -def test_get_child_sort_links_by_id_is_configurable( - test_case_1_catalog: Catalog, -) -> None: - cat1 = test_case_1_catalog - country = cat1.get_child("country-2", sort_links_by_id=False) - assert country is not None - child_links = [link for link in cat1.links if link.rel == pystac.RelType.CHILD] - for link in child_links: - assert link.is_resolved() - - -def test_get_item_returns_none_if_not_found( - test_case_8_collection: Collection, -) -> None: - col8 = test_case_8_collection - item = col8.get_item("notarealitem") - assert item is None - - -def test_get_item_is_not_recursive_by_default( - test_case_8_collection: Collection, -) -> None: - col8 = test_case_8_collection - item = col8.get_item("20170831_162740_ssc1d1") - assert item is None - - item = col8.get_item("20170831_162740_ssc1d1", recursive=True) - assert item is not None - - -def test_delete_asset(tmp_asset: Asset, collection: Collection) -> None: - asset = tmp_asset - href = asset.get_absolute_href() - item = asset.owner - name = "foo" - - assert href is not None - assert item is not None - - collection.add_asset(name, asset) - - # steal the href from the owner and use it as the collection's - collection.set_self_href(item.get_self_href()) - - collection.delete_asset(name) - assert name not in collection.assets - assert not os.path.exists(href) - - -def test_delete_asset_relative_no_self_link_fails( - tmp_asset: Asset, collection: Collection -) -> None: - asset = tmp_asset - href = asset.get_absolute_href() - name = "foo" - - assert href is not None - - collection.add_asset(name, asset) - - with pytest.raises(ValueError, match="Cannot delete file") as e: - collection.delete_asset(name) - - assert asset.href in str(e.value) - assert name in collection.assets - assert os.path.exists(href) - - -def test_permissive_temporal_extent_deserialization(collection: Collection) -> None: - # https://github.com/stac-utils/pystac/issues/1221 - collection_dict = collection.to_dict(include_self_link=False, transform_hrefs=False) - collection_dict["extent"]["temporal"]["interval"] = collection_dict["extent"][ - "temporal" - ]["interval"][0] - with pytest.warns(UserWarning): - Collection.from_dict(collection_dict) - - -@pytest.mark.parametrize("fixture_name", ("sample_item_collection", "sample_items")) -def test_from_items(fixture_name: str, request: pytest.FixtureRequest) -> None: - items = request.getfixturevalue(fixture_name) - collection = Collection.from_items(items) - - for item in items: - assert collection.id == item.collection_id - assert collection.extent.spatial.bboxes[0][0] <= item.bbox[0] - assert collection.extent.spatial.bboxes[0][1] <= item.bbox[1] - assert collection.extent.spatial.bboxes[0][2] >= item.bbox[2] - assert collection.extent.spatial.bboxes[0][3] >= item.bbox[3] - - start = collection.extent.temporal.intervals[0][0] - end = collection.extent.temporal.intervals[0][1] - assert start and start <= str_to_datetime(item.properties["start_datetime"]) - assert end and end >= str_to_datetime(item.properties["end_datetime"]) - - if isinstance(items, ItemCollection): - expected = {(link["rel"], link["href"]) for link in items.extra_fields["links"]} - actual = {(link.rel, link.href) for link in collection.links} - assert expected.issubset(actual) - - -def test_from_items_pulls_from_properties() -> None: - item1 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-10, -20, 0, -10], - datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - collection="test-collection-1", - properties={"title": "Test Item", "description": "Extra words describing"}, - ) - collection = Collection.from_items([item1]) - assert collection.id == item1.collection_id - assert collection.title == item1.properties["title"] - assert collection.description == item1.properties["description"] - - -def test_from_items_without_collection_id() -> None: - item1 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-10, -20, 0, -10], - datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - properties={}, - ) - with pytest.raises(ValueError, match="Collection id must be defined."): - Collection.from_items([item1]) - - collection = Collection.from_items([item1], id="test-collection") - assert collection.id == "test-collection" - - -def test_from_items_with_collection_ids() -> None: - item1 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-10, -20, 0, -10], - datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - collection="test-collection-1", - properties={}, - ) - item2 = Item( - id="test-item-2", - geometry=ARBITRARY_GEOM, - bbox=[-15, -20, 0, -10], - datetime=datetime(2000, 2, 1, 13, 0, 0, 0, tzinfo=tz.UTC), - collection="test-collection-2", - properties={}, - ) - - with pytest.raises(ValueError, match="Collection id must be defined."): - Collection.from_items([item1, item2]) - - collection = Collection.from_items([item1, item2], id="test-collection") - assert collection.id == "test-collection" - - -def test_from_items_with_different_values() -> None: - item1 = Item( - id="test-item-1", - geometry=ARBITRARY_GEOM, - bbox=[-10, -20, 0, -10], - datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), - properties={"title": "Test Item 1"}, - ) - item2 = Item( - id="test-item-2", - geometry=ARBITRARY_GEOM, - bbox=[-15, -20, 0, -10], - datetime=datetime(2000, 2, 1, 13, 0, 0, 0, tzinfo=tz.UTC), - properties={"title": "Test Item 2"}, - ) - - collection = Collection.from_items([item1, item2], id="test_collection") - assert collection.title is None - - -def test_from_items_with_providers(sample_item_collection: ItemCollection) -> None: - sample_item_collection.extra_fields["providers"] = [{"name": "pystac"}] - - collection = Collection.from_items(sample_item_collection) - assert collection.providers and len(collection.providers) == 1 - - provider = collection.providers[0] - assert provider and provider.name == "pystac" - - -def test_from_dict_null_extent(collection: Collection) -> None: - # https://github.com/stac-utils/pystac/issues/1558 - # https://github.com/EOPF-Sample-Service/eopf-stac/issues/18 - d = collection.to_dict() - d["extent"] = None - with pytest.warns(UserWarning): - c = Collection.from_dict(d) - - assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] - assert c.extent.temporal.to_dict()["interval"] == [[None, None]] - - -def test_from_dict_missing_extent(collection: Collection) -> None: - d = collection.to_dict() - del d["extent"] - with pytest.warns(UserWarning): - c = Collection.from_dict(d) - - assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] - assert c.extent.temporal.to_dict()["interval"] == [[None, None]] + assert collection.to_dict() == data diff --git a/tests/test_examples.py b/tests/test_examples.py new file mode 100644 index 000000000..96f7ab040 --- /dev/null +++ b/tests/test_examples.py @@ -0,0 +1,27 @@ +import json +from pathlib import Path + +import pytest + +import pystac +from pystac.reader import StandardLibraryReader + +EXAMPLE_FILES = list( + (Path(__file__).parent / "data-files" / "examples").glob("**/*.json") +) + + +@pytest.mark.vcr +@pytest.mark.parametrize("path", EXAMPLE_FILES) +def test_example_from_dict(path: Path): + with open(path) as f: + data = json.load(f) + stac_object = pystac.from_dict(data) + stac_object.validate() + + +@pytest.mark.vcr +@pytest.mark.parametrize("path", EXAMPLE_FILES) +def test_example_read_file(path: Path): + stac_object = pystac.read_file(path, reader=StandardLibraryReader()) + stac_object.validate() diff --git a/tests/test_item.py b/tests/test_item.py index 6dee920c3..74d56c9dd 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -1,694 +1,31 @@ -from __future__ import annotations - -import copy +import datetime import json -import os -import pickle -import tempfile -from copy import deepcopy from pathlib import Path -from typing import Any, cast -import dateutil.relativedelta import pytest -import pystac -import pystac.serialization.common_properties -from pystac import Asset, Catalog, Collection, Item, Link, STACValidationError -from pystac.utils import ( - datetime_to_str, - get_opt, - is_absolute_href, - make_posix_style, - str_to_datetime, -) -from pystac.validation import validate_dict -from tests.utils import TestCases, assert_to_from_dict - - -def test_to_from_dict(sample_item_dict: dict[str, Any]) -> None: - param_dict = deepcopy(sample_item_dict) - - assert_to_from_dict(Item, param_dict) - item = Item.from_dict(param_dict) - assert item.id == "CS3-20160503_132131_05" - - # test asset creation additional field(s) - assert ( - item.assets["analytic"].extra_fields["product"] - == "http://cool-sat.com/catalog/products/analytic.json" - ) - assert len(item.assets["thumbnail"].extra_fields) == 0 - - # test that the parameter is preserved - assert param_dict == sample_item_dict - - # assert that the parameter is preserved regardless of preserve_dict - Item.from_dict(param_dict, preserve_dict=False) - assert param_dict == sample_item_dict - - -def test_from_dict_set_root(sample_item_dict: dict[str, Any]) -> None: - catalog = pystac.Catalog(id="test", description="test desc") - item = Item.from_dict(sample_item_dict, root=catalog) - assert item.get_root() is catalog - - -def test_set_self_href_does_not_break_asset_hrefs() -> None: - cat = TestCases.case_2() - for item in cat.get_items(recursive=True): - for asset in item.assets.values(): - if is_absolute_href(asset.href): - asset.href = f"./{os.path.basename(asset.href)}" - item.set_self_href("http://example.com/item.json") - for asset in item.assets.values(): - assert is_absolute_href(asset.href) - - -def test_set_self_href_none_ignores_relative_asset_hrefs() -> None: - cat = TestCases.case_2() - for item in cat.get_items(recursive=True): - for asset in item.assets.values(): - if is_absolute_href(asset.href): - asset.href = f"./{os.path.basename(asset.href)}" - item.set_self_href(None) - for asset in item.assets.values(): - assert not is_absolute_href(asset.href) - - -def test_asset_absolute_href(sample_item: Item) -> None: - item_path = TestCases.get_path("data-files/item/sample-item.json") - sample_item.set_self_href(item_path) - rel_asset = Asset("./data.geojson") - rel_asset.set_owner(sample_item) - expected_href = make_posix_style( - os.path.abspath(os.path.join(os.path.dirname(item_path), "./data.geojson")) - ) - actual_href = rel_asset.get_absolute_href() - assert expected_href == actual_href - - -def test_asset_absolute_href_no_item_self(sample_item_dict: dict[str, Any]) -> None: - item = Item.from_dict(sample_item_dict) - assert item.get_self_href() is None - - rel_asset = Asset("./data.geojson") - rel_asset.set_owner(item) - actual_href = rel_asset.get_absolute_href() - assert actual_href is None - - -def test_item_field_order() -> None: - item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - item_dict = item.to_dict(include_self_link=False) - expected_order = [ - "type", - "stac_version", - "stac_extensions", - "id", - "geometry", - "bbox", - "properties", - "links", - "assets", - "collection", - ] - actual_order = list(item_dict.keys()) - assert actual_order == expected_order - - -def test_extra_fields() -> None: - item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - - item.extra_fields["test"] = "extra" - - with tempfile.TemporaryDirectory() as tmp_dir: - p = os.path.join(tmp_dir, "item.json") - item.save_object(include_self_link=False, dest_href=p) - with open(p) as f: - item_json = json.load(f) - assert "test" in item_json - assert item_json["test"] == "extra" - - read_item = pystac.Item.from_file(p) - assert "test" in read_item.extra_fields - assert read_item.extra_fields["test"] == "extra" - - -def test_clearing_collection() -> None: - collection = TestCases.case_4().get_child("acc") - assert isinstance(collection, pystac.Collection) - item = next(collection.get_items(recursive=True)) - assert item.collection_id == collection.id - item.set_collection(None) - assert item.collection_id is None - assert item.get_collection() is None - item.set_collection(collection) - assert item.collection_id == collection.id - assert item.get_collection() is collection - - -def test_datetime_ISO8601_format(sample_item: Item) -> None: - formatted_time = sample_item.to_dict()["properties"]["datetime"] - assert "2016-05-03T13:22:30.040000Z" == formatted_time - - -@pytest.mark.vcr() -def test_null_datetime() -> None: - item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - - with pytest.raises(pystac.STACError): - Item( - "test", - geometry=item.geometry, - bbox=item.bbox, - datetime=None, - properties={}, - ) - - null_dt_item = Item( - "test", - geometry=item.geometry, - bbox=item.bbox, - datetime=None, - properties={ - "start_datetime": datetime_to_str(get_opt(item.datetime)), - "end_datetime": datetime_to_str(get_opt(item.datetime)), - }, - ) - - null_dt_item.validate() - - -def test_get_assets() -> None: - item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - - media_type_filter = item.get_assets(media_type=pystac.MediaType.COG) - assert set(media_type_filter.keys()) == {"analytic"} - role_filter = item.get_assets(role="data") - assert set(role_filter.keys()) == {"analytic"} - multi_filter = item.get_assets(media_type=pystac.MediaType.PNG, role="thumbnail") - assert set(multi_filter.keys()) == {"thumbnail"} - multi_filter["thumbnail"].description = "foo" - assert item.assets["thumbnail"].description != "foo" - - no_filter = item.get_assets() - assert set(no_filter.keys()) == {"analytic", "thumbnail"} - no_assets = item.get_assets(media_type=pystac.MediaType.HDF) - assert no_assets == {} - - -@pytest.mark.vcr() -def test_null_datetime_constructor() -> None: - item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - with pytest.raises(pystac.STACError): - Item( - "test", - geometry=item.geometry, - bbox=item.bbox, - datetime=None, - end_datetime=item.datetime, - properties={}, - ) - with pytest.raises(pystac.STACError): - Item( - "test", - geometry=item.geometry, - bbox=item.bbox, - datetime=None, - start_datetime=item.datetime, - properties={}, - ) - assert item.datetime - null_dt_item = Item( - "test", - geometry=item.geometry, - bbox=item.bbox, - datetime=None, - start_datetime=item.datetime, - end_datetime=item.datetime + dateutil.relativedelta.relativedelta(days=1), - properties={}, - ) - null_dt_item.validate() - - -def test_get_set_asset_datetime() -> None: - item = pystac.Item.from_file( - TestCases.get_path("data-files/item/sample-item-asset-properties.json") - ) - item_datetime = item.datetime - - # No property on asset - assert item.get_datetime(item.assets["thumbnail"]) == item.datetime - - # Property on asset - assert item.get_datetime(item.assets["analytic"]) != item.datetime - assert item.get_datetime(item.assets["analytic"]) == str_to_datetime( - "2017-05-03T13:22:30.040Z" - ) - - item.set_datetime( - str_to_datetime("2018-05-03T13:22:30.040Z"), item.assets["thumbnail"] - ) - assert item.get_datetime() == item_datetime - assert item.get_datetime(item.assets["thumbnail"]) == str_to_datetime( - "2018-05-03T13:22:30.040Z" - ) +from pystac import Item, utils -def test_read_eo_item_owns_asset() -> None: - item = next(TestCases.case_1().get_items(recursive=True)) - assert len(item.assets) > 0 - for asset_key in item.assets: - assert item.assets[asset_key].owner == item - - -@pytest.mark.vcr() -def test_null_geometry() -> None: - m = TestCases.get_path( - "data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json" - ) - with open(m) as f: - item_dict = json.load(f) - - validate_dict(item_dict, pystac.STACObjectType.ITEM) - - item = Item.from_dict(item_dict) - assert isinstance(item, Item) +def test_item_init() -> None: + item = Item(id="an-id") item.validate() - item_dict = item.to_dict() - assert item_dict["geometry"] is None - assert "bbox" not in item_dict - - -def test_0_9_item_with_no_extensions_does_not_read_collection_data() -> None: - item_json = pystac.StacIO.default().read_json( - TestCases.get_path("data-files/examples/hand-0.9.0/010100/010100.json") - ) - assert item_json.get("stac_extensions") is None - assert item_json.get("stac_version") == "0.9.0" - - did_merge = pystac.serialization.common_properties.merge_common_properties( - item_json - ) - assert not did_merge - - -def test_clone_preserves_assets() -> None: - cat = TestCases.case_2() - original_item = next(cat.get_items(recursive=True)) - assert len(original_item.assets) > 0 - assert all(asset.owner is original_item for asset in original_item.assets.values()) - - cloned_item = original_item.clone() - - for key in original_item.assets: - assert key in cloned_item.assets, f"Failed to preserve asset {key}" - cloned_asset = cloned_item.assets.get(key) - if cloned_asset is not None: - assert cloned_asset.owner is cloned_item, f"Failed set owner for {key}" - - -def test_make_asset_href_relative_is_noop_on_relative_hrefs() -> None: - cat = TestCases.case_2() - item = next(cat.get_items(recursive=True)) - asset = list(item.assets.values())[0] - assert not is_absolute_href(asset.href) - original_href = asset.get_absolute_href() - - item.make_asset_hrefs_relative() - assert asset.get_absolute_href() == original_href +def test_item_from_dict(examples_path: Path) -> None: + with open(examples_path / "simple-item.json") as f: + data = json.load(f) -def test_from_invalid_dict_raises_exception() -> None: - stac_io = pystac.StacIO.default() - catalog_dict = stac_io.read_json( - TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + # Normalize datetime string to get rid of spurious zeros + data["properties"]["datetime"] = utils.datetime_to_str( + datetime.datetime.fromisoformat(data["properties"]["datetime"]) ) - with pytest.raises(pystac.STACTypeError): - _ = pystac.Item.from_dict(catalog_dict) - -@pytest.mark.vcr() -def test_relative_extension_path() -> None: - item = pystac.Item.from_file( - TestCases.get_path( - "data-files/item/sample-item-with-relative-extension-path.json" - ) - ) + item = Item.from_dict(data) item.validate() + assert item.to_dict() == data -class TestItemSubClass: - """This tests cases related to creating classes inheriting from pystac.Catalog to - ensure that inheritance, class methods, etc. function as expected.""" - - SAMPLE_ITEM = TestCases.get_path("data-files/item/sample-item.json") - - class BasicCustomItem(pystac.Item): - pass - - def test_from_dict_returns_subclass(self) -> None: - stac_io = pystac.StacIO.default() - item_dict = stac_io.read_json(self.SAMPLE_ITEM) - custom_item = self.BasicCustomItem.from_dict(item_dict) - - assert isinstance(custom_item, self.BasicCustomItem) - - def test_from_file_returns_subclass(self) -> None: - custom_item = self.BasicCustomItem.from_file(self.SAMPLE_ITEM) - - assert isinstance(custom_item, self.BasicCustomItem) - - def test_clone(self) -> None: - custom_item = self.BasicCustomItem.from_file(self.SAMPLE_ITEM) - cloned_item = custom_item.clone() - - assert isinstance(cloned_item, self.BasicCustomItem) - - -def test_asset_clone() -> None: - with open(TestCases.get_path("data-files/item/sample-item.json")) as src: - item_dict = json.load(src) - asset_dict = item_dict["assets"]["analytic"] - original_asset = Asset.from_dict(asset_dict) - - cloned_asset = original_asset.clone() - - assert original_asset.to_dict() == asset_dict - assert cloned_asset.to_dict() == asset_dict - - # Changes to original asset should not affect cloned Asset - original_asset.description = "Some new description" - original_asset.href = "/path/to/new/href" - original_asset.title = "New Title" - original_asset.roles = ["new role"] - original_asset.roles.append("new role") - original_asset.extra_fields["new_field"] = "new_value" - assert cloned_asset.to_dict() == asset_dict - - -class TestAssetSubClass: - class CustomAsset(Asset): - pass - - AssetDict = dict[str, str | list[str]] - - @pytest.fixture - def asset_dict(self) -> AssetDict: - with open(TestCases.get_path("data-files/item/sample-item.json")) as src: - item_dict = json.load(src) - return cast(TestAssetSubClass.AssetDict, item_dict["assets"]["analytic"]) - - def test_from_dict(self, asset_dict: AssetDict) -> None: - asset = self.CustomAsset.from_dict(asset_dict) - assert isinstance(asset, self.CustomAsset) - - def test_clone(self, asset_dict: AssetDict) -> None: - asset = self.CustomAsset.from_dict(asset_dict) - cloned_asset = asset.clone() - assert isinstance(cloned_asset, self.CustomAsset) - - -def test_custom_item_from_dict(item: Item) -> None: - # https://github.com/stac-utils/pystac/issues/862 - class CustomItem(Item): - @classmethod - def from_dict( - cls, - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - migrate: bool = True, - preserve_dict: bool = True, - ) -> CustomItem: - return super().from_dict(d) - - _ = CustomItem.from_dict(item.to_dict()) - - -def test_item_from_dict_raises_useful_error() -> None: - item_dict = {"type": "Feature", "stac_version": "1.1.0", "id": "lalalalala"} - with pytest.raises(pystac.STACError, match="Invalid Item: "): - Item.from_dict(item_dict) - - -def test_item_from_dict_with_missing_stac_version_raises_useful_error() -> None: - item_dict = {"type": "Feature", "id": "lalalalala"} - with pytest.raises(pystac.STACTypeError, match="'stac_version' is missing"): - Item.from_dict(item_dict, migrate=False) - - -def test_item_from_dict_with_missing_type_raises_useful_error() -> None: - item_dict = {"stac_version": "0.8.0", "id": "lalalalala"} - with pytest.raises(pystac.STACTypeError, match="'type' is missing"): - Item.from_dict(item_dict, migrate=False) - - -@pytest.mark.parametrize("add_canonical", (True, False)) -def test_remove_hierarchical_links( - test_case_1_catalog: Catalog, add_canonical: bool -) -> None: - item = next(test_case_1_catalog.get_items(recursive=True)) - item.remove_hierarchical_links(add_canonical=add_canonical) - for link in item.links: - assert not link.is_hierarchical() - assert bool(item.get_single_link("canonical")) == add_canonical - - -def test_geo_interface() -> None: - item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) - assert ( - item.to_dict(include_self_link=False, transform_hrefs=False) - == item.__geo_interface__ - ) - - -def test_duplicate_self_links(tmp_path: Path, sample_item: pystac.Item) -> None: - # https://github.com/stac-utils/pystac/issues/1102 - assert len(sample_item.get_links(rel="self")) == 1 - path = tmp_path / "item.json" - sample_item.save_object(include_self_link=True, dest_href=str(path)) - sample_item = Item.from_file(path) - assert len(sample_item.get_links(rel="self")) == 1 - - -def test_get_derived_from_when_none_exists(test_case_1_catalog: Catalog) -> None: - item = next(test_case_1_catalog.get_items(recursive=True)) - assert item.get_derived_from() == [] - for link in item.links: - assert link.rel != pystac.RelType.DERIVED_FROM - assert item.get_single_link(pystac.RelType.DERIVED_FROM) is None - - -def test_add_derived_from(test_case_1_catalog: Catalog) -> None: - items = list(test_case_1_catalog.get_items(recursive=True)) - item_0 = items[0] - item_1 = items[1] - item_2 = items[2] - item_0.add_derived_from(item_1, item_2.self_href) - derived_from = item_0.get_derived_from() - assert len(derived_from) == 2 - assert derived_from[0].id == item_1.id - assert derived_from[1].id == item_2.id - filtered = [ - link for link in item_0.links if link.rel == pystac.RelType.DERIVED_FROM - ] - assert len(filtered) == 2 - assert filtered[0].to_dict(transform_href=False)["href"] == item_1.self_href - assert filtered[1].to_dict(transform_href=False)["href"] == item_2.self_href - - -def test_get_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: - item = next(test_case_1_catalog.get_items(recursive=True)) - item.add_derived_from("foo") - with pytest.raises( - pystac.STACError, match="Link failed to resolve. Use get_links instead" - ): - item.get_derived_from() - - links = item.get_links(pystac.RelType.DERIVED_FROM) - assert len(links) == 1 - - -def test_remove_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: - item = next(test_case_1_catalog.get_items(recursive=True)) - item.add_derived_from("foo") - with pytest.raises( - pystac.STACError, match="Link failed to resolve. Use remove_links instead" - ): - item.remove_derived_from("foo") - - item.remove_links(pystac.RelType.DERIVED_FROM) - assert item.get_derived_from() == [] - - -def test_remove_derived_from(test_case_1_catalog: Catalog) -> None: - items = list(test_case_1_catalog.get_items(recursive=True)) - item_0 = items[0] - item_1 = items[1] - item_0.add_derived_from(item_1) - item_0.remove_derived_from(item_1.id) - assert item_0.get_derived_from() == [] - for link in item_0.links: - assert link.rel != pystac.RelType.DERIVED_FROM - assert item_0.get_single_link(pystac.RelType.DERIVED_FROM) is None - - -def test_delete_asset(tmp_asset: Asset) -> None: - asset = tmp_asset - href = asset.get_absolute_href() - item = asset.owner - - assert href is not None - assert item is not None - - name = next(k for k in item.assets.keys() if item.assets[k] == asset) - item.delete_asset(name) - - assert name not in item.assets - assert not os.path.exists(href) - - -def test_delete_asset_relative_no_self_link_fails(tmp_asset: pystac.Asset) -> None: - asset = tmp_asset - href = asset.get_absolute_href() - item = asset.owner - - assert href is not None - assert item is not None - assert isinstance(item, pystac.Item) - - item.set_self_href(None) - - name = next(k for k in item.assets.keys() if item.assets[k] == asset) - with pytest.raises(ValueError, match="Cannot delete file") as e: - item.delete_asset(name) - - assert asset.href in str(e.value) - assert name in item.assets - assert os.path.exists(href) - - -def test_resolve_collection_with_root( - tmp_path: Path, item: Item, collection: Collection -) -> None: - # Motivated by https://github.com/stac-utils/pystac-client/issues/548 - catalog = Catalog("root", "the description") - item.set_root(catalog) - - collection_path = str(tmp_path / "collection.json") - collection.save_object( - include_self_link=False, - dest_href=collection_path, - ) - item.add_link(Link(rel="collection", target=collection_path)) - - read_collection = item.get_collection() - assert read_collection - root = read_collection.get_root() - assert root - assert root.id == "root" - - -@pytest.mark.vcr() -def test_non_hierarchical_relative_link() -> None: - root = pystac.Catalog("root", "root") - a = pystac.Catalog("a", "a") - b = pystac.Catalog("b", "b") - - root.add_child(a) - root.add_child(b) - a.add_link(pystac.Link("related", b)) - b.add_link( - pystac.Link("item", TestCases.get_path("data-files/item/sample-item.json")) - ) - - root.catalog_type = pystac.catalog.CatalogType.SELF_CONTAINED - root.normalize_hrefs("test_output") - related_href = [link for link in a.links if link.rel == "related"][0].get_href() - - assert related_href is not None and not is_absolute_href(related_href) - assert a.target_in_hierarchy(b) - assert root.target_in_hierarchy(next(b.get_items())) - assert root.target_in_hierarchy(root) - - -def test_pathlib() -> None: - # This works, but breaks mypy until we fix - # https://github.com/stac-utils/pystac/issues/1216 - Item.from_file(Path(TestCases.get_path("data-files/item/sample-item.json"))) - - -def test_invalid_error_message(item: Item) -> None: - item.extra_fields["collection"] = "can't have a collection" - with pytest.raises(STACValidationError) as error: - item.validate() - assert "can't have a collection" in str(error.value) - - -def test_pickle_with_no_links(item: Item) -> None: - roundtripped = pickle.loads(pickle.dumps(item)) - for attr in ["id", "geometry", "bbox", "datetime", "links"]: - assert getattr(roundtripped, attr) == getattr(item, attr) - - -def test_pickle_with_hrefless_links(item: Item) -> None: - root = pystac.Catalog("root", "root") - a = pystac.Catalog("a", "a") - - item.add_link(pystac.Link("related", a)) - item.add_link( - pystac.Link("item", TestCases.get_path("data-files/item/sample-item.json")) - ) - item.set_root(root) - - roundtripped = pickle.loads(pickle.dumps(item)) - for original, new in zip(item.links, roundtripped.links): - assert original.rel == new.rel - assert original.media_type == new.media_type - assert str(original.owner) == str(new.owner) - assert str(original.target) == str(new.target) - - -def test_pickle_with_only_href_links(item: Item) -> None: - item.add_link( - pystac.Link("item", TestCases.get_path("data-files/item/sample-item.json")) - ) - - roundtripped = pickle.loads(pickle.dumps(item)) - for original, new in zip(item.links, roundtripped.links): - assert original.rel == new.rel - assert original.media_type == new.media_type - assert str(original.owner) == str(new.owner) - assert str(original.target) == str(new.target) - - -def test_copy_with_unresolveable_root(item: Item) -> None: - item.add_link( - pystac.Link( - "root", "s3://naip-visualization/this-is-a-non-existent-catalog.json" - ) - ) - copy.deepcopy(item) - - -def test_no_collection(item: Item) -> None: - # https://github.com/stac-utils/stac-api-validator/issues/527 - assert item.collection is None - - -def test_migrate_by_default() -> None: - with open( - TestCases.get_path("data-files/projection/example-with-version-1.1.json") - ) as f: - data = json.load(f) - item = pystac.Item.from_dict(data) # default used to be migrate=False - assert item.ext.proj.code == "EPSG:32614" - - -def test_clone_extra_fields(item: Item) -> None: - item.extra_fields["foo"] = "bar" - cloned = item.clone() - assert cloned.extra_fields["foo"] == "bar" +@pytest.mark.vcr +def test_validate_extension(proj_example: Item) -> None: + proj_example.validate() diff --git a/tests/test_obstore.py b/tests/test_obstore.py new file mode 100644 index 000000000..eab3ef6b8 --- /dev/null +++ b/tests/test_obstore.py @@ -0,0 +1,20 @@ +from pathlib import Path + +import pytest +from pytest import Subtests + +import pystac + +obstore = pytest.importorskip("obstore") + + +def test_examples_read_file(examples_path: Path, subtests: Subtests) -> None: + from pystac.obstore import ObstoreReader + + store = obstore.store.LocalStore() + reader = ObstoreReader(store) + + for path in examples_path.glob("**/*.json"): + with subtests.test(path=path): + stac_object = pystac.read_file(path, reader=reader) + stac_object.validate() diff --git a/tests/__init__.py b/tests/v1/__init__.py similarity index 100% rename from tests/__init__.py rename to tests/v1/__init__.py diff --git a/tests/cassettes/test_catalog/TestCatalog.test_read_remote.yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_read_remote.yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_read_remote.yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_read_remote.yaml diff --git a/tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat0].yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat0].yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat0].yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat0].yaml diff --git a/tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat1].yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat1].yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat1].yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat1].yaml diff --git a/tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat3].yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat3].yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat3].yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat3].yaml diff --git a/tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat4].yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat4].yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat4].yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat4].yaml diff --git a/tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat5].yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat5].yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat5].yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat5].yaml diff --git a/tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat6].yaml b/tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat6].yaml similarity index 100% rename from tests/cassettes/test_catalog/TestCatalog.test_validate_all[cat6].yaml rename to tests/v1/cassettes/test_catalog/TestCatalog.test_validate_all[cat6].yaml diff --git a/tests/cassettes/test_catalog/test_validate_all_with_max_n.yaml b/tests/v1/cassettes/test_catalog/test_validate_all_with_max_n.yaml similarity index 100% rename from tests/cassettes/test_catalog/test_validate_all_with_max_n.yaml rename to tests/v1/cassettes/test_catalog/test_validate_all_with_max_n.yaml diff --git a/tests/cassettes/test_catalog/test_validate_all_with_recusive_off.yaml b/tests/v1/cassettes/test_catalog/test_validate_all_with_recusive_off.yaml similarity index 100% rename from tests/cassettes/test_catalog/test_validate_all_with_recusive_off.yaml rename to tests/v1/cassettes/test_catalog/test_validate_all_with_recusive_off.yaml diff --git a/tests/cassettes/test_item/test_non_hierarchical_relative_link.yaml b/tests/v1/cassettes/test_item/test_non_hierarchical_relative_link.yaml similarity index 100% rename from tests/cassettes/test_item/test_non_hierarchical_relative_link.yaml rename to tests/v1/cassettes/test_item/test_non_hierarchical_relative_link.yaml diff --git a/tests/cassettes/test_item/test_null_geometry.yaml b/tests/v1/cassettes/test_item/test_null_geometry.yaml similarity index 100% rename from tests/cassettes/test_item/test_null_geometry.yaml rename to tests/v1/cassettes/test_item/test_null_geometry.yaml diff --git a/tests/cassettes/test_stac_io/test_proj_json_schema_is_readable.yaml b/tests/v1/cassettes/test_stac_io/test_proj_json_schema_is_readable.yaml similarity index 100% rename from tests/cassettes/test_stac_io/test_proj_json_schema_is_readable.yaml rename to tests/v1/cassettes/test_stac_io/test_proj_json_schema_is_readable.yaml diff --git a/tests/cassettes/test_stac_io/test_retry_stac_io.yaml b/tests/v1/cassettes/test_stac_io/test_retry_stac_io.yaml similarity index 100% rename from tests/cassettes/test_stac_io/test_retry_stac_io.yaml rename to tests/v1/cassettes/test_stac_io/test_retry_stac_io.yaml diff --git a/tests/cassettes/test_stac_io/test_retry_stac_io_404.yaml b/tests/v1/cassettes/test_stac_io/test_retry_stac_io_404.yaml similarity index 100% rename from tests/cassettes/test_stac_io/test_retry_stac_io_404.yaml rename to tests/v1/cassettes/test_stac_io/test_retry_stac_io_404.yaml diff --git a/tests/cassettes/test_stac_io/test_urls_with_non_ascii_characters.yaml b/tests/v1/cassettes/test_stac_io/test_urls_with_non_ascii_characters.yaml similarity index 100% rename from tests/cassettes/test_stac_io/test_urls_with_non_ascii_characters.yaml rename to tests/v1/cassettes/test_stac_io/test_urls_with_non_ascii_characters.yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog0].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog0].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog0].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog0].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog1].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog1].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog1].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog1].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog3].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog3].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog3].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog3].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog4].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog4].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog4].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog4].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog5].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog5].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog5].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog5].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog6].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog6].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog6].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[ABSOLUTE_PUBLISHED-catalog6].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog0].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog0].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog0].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog0].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog1].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog1].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog1].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog1].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog3].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog3].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog3].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog3].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog4].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog4].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog4].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog4].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog5].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog5].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog5].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog5].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog6].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog6].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog6].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[RELATIVE_PUBLISHED-catalog6].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog0].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog0].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog0].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog0].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog1].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog1].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog1].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog1].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog3].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog3].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog3].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog3].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog4].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog4].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog4].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog4].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog5].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog5].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog5].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog5].yaml diff --git a/tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog6].yaml b/tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog6].yaml similarity index 100% rename from tests/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog6].yaml rename to tests/v1/cassettes/test_writing/TestSTACWriting.test_testcases[SELF_CONTAINED-catalog6].yaml diff --git a/tests/v1/conftest.py b/tests/v1/conftest.py new file mode 100644 index 000000000..64a44ef7b --- /dev/null +++ b/tests/v1/conftest.py @@ -0,0 +1,115 @@ +# TODO move all test case code to this file + +import json +import shutil +import uuid +from datetime import datetime +from pathlib import Path +from typing import Any + +import pytest + +from pystac import Asset, Catalog, Collection, Item, ItemCollection, Link + +from .utils import ARBITRARY_BBOX, ARBITRARY_EXTENT, ARBITRARY_GEOM, TestCases + +HERE = Path(__file__).resolve().parent + + +@pytest.fixture +def catalog() -> Catalog: + return Catalog("test-catalog", "A test catalog") + + +@pytest.fixture +def collection() -> Catalog: + return Collection("test-collection", "A test collection", ARBITRARY_EXTENT) + + +@pytest.fixture +def multi_extent_collection() -> Collection: + # TODO this code is repeated many times; refactor to use this fixture + return Collection.from_file( + TestCases.get_path("data-files/collections/multi-extent.json") + ) + + +@pytest.fixture +def item() -> Item: + return Item("test-item", ARBITRARY_GEOM, ARBITRARY_BBOX, datetime.now(), {}) + + +@pytest.fixture +def asset(item: Item) -> Asset: + item.add_asset("foo", Asset("https://example.tif")) + return item.assets["foo"] + + +@pytest.fixture +def link(item: Item) -> Link: + item.add_link(Link(rel="child", target="https://example.tif")) + return item.links[0] + + +@pytest.fixture +def test_case_1_catalog() -> Catalog: + return TestCases.case_1() + + +@pytest.fixture +def test_case_8_collection() -> Collection: + return TestCases.case_8() + + +@pytest.fixture +def projection_landsat8_item() -> Item: + path = TestCases.get_path("data-files/projection/example-landsat8.json") + return Item.from_file(path) + + +def get_data_file(rel_path: str) -> str: + return str(HERE / "data-files" / rel_path) + + +@pytest.fixture +def sample_item_dict() -> dict[str, Any]: + m = TestCases.get_path("data-files/item/sample-item.json") + with open(m) as f: + item_dict: dict[str, Any] = json.load(f) + return item_dict + + +@pytest.fixture +def sample_item() -> Item: + return Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + + +@pytest.fixture +def sample_item_collection() -> ItemCollection: + return ItemCollection.from_file( + TestCases.get_path("data-files/item-collection/sample-item-collection.json") + ) + + +@pytest.fixture +def sample_items(sample_item_collection: ItemCollection) -> list[Item]: + return list(sample_item_collection) + + +@pytest.fixture(scope="function") +def tmp_asset(tmp_path: Path) -> Asset: + """Copy the entirety of test-case-2 to tmp and""" + src = get_data_file("catalogs/test-case-2") + dst = str(tmp_path / str(uuid.uuid4())) + shutil.copytree(src, dst) + + catalog = Catalog.from_file(f"{dst}/catalog.json") + item = next(catalog.get_items(recursive=True)) + return next(v for v in item.assets.values()) + + +@pytest.fixture(autouse=True) +def clear_validator() -> None: + from pystac.validation import RegisteredValidator + + RegisteredValidator._validator = None diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json b/tests/v1/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json similarity index 100% rename from tests/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json rename to tests/v1/data-files/catalogs/cbers-partial/CBERS4AWFI/collection.json diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json b/tests/v1/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json similarity index 100% rename from tests/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json rename to tests/v1/data-files/catalogs/cbers-partial/CBERS4MUX/collection.json diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json b/tests/v1/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json similarity index 100% rename from tests/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json rename to tests/v1/data-files/catalogs/cbers-partial/CBERS4PAN10M/collection.json diff --git a/tests/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json b/tests/v1/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json similarity index 100% rename from tests/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json rename to tests/v1/data-files/catalogs/cbers-partial/CBERS4PAN5M/collection.json diff --git a/tests/data-files/catalogs/cbers-partial/catalog.json b/tests/v1/data-files/catalogs/cbers-partial/catalog.json similarity index 100% rename from tests/data-files/catalogs/cbers-partial/catalog.json rename to tests/v1/data-files/catalogs/cbers-partial/catalog.json diff --git a/tests/data-files/catalogs/invalid-catalog/catalog.json b/tests/v1/data-files/catalogs/invalid-catalog/catalog.json similarity index 100% rename from tests/data-files/catalogs/invalid-catalog/catalog.json rename to tests/v1/data-files/catalogs/invalid-catalog/catalog.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/665946-labels/665946-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/665946/665946.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/a42435-labels/a42435-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/a42435/a42435.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a-labels/ca041a-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/ca041a/ca041a.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81-labels/d41d81-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/acc/d41d81/d41d81.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/catalog.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/catalog.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/catalog.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/catalog.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40-labels/0a4c40-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/0a4c40/0a4c40.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/353093-labels/353093-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/353093/353093.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/42f235-labels/42f235-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/42f235/42f235.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9-labels/a017f9-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/a017f9/a017f9.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce-labels/b15fce-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/b15fce/b15fce.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0-labels/f883a0-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/dar/f883a0/f883a0.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f-labels/4e7c7f-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/kam/4e7c7f/4e7c7f.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/kam/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7-labels/207cc7-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/207cc7/207cc7.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/401175-labels/401175-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/401175/401175.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/493701-labels/493701-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/493701/493701.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/f15272-labels/f15272-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/mon/f15272/f15272.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/nia/825a50-labels/825a50-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/nia/825a50/825a50.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/nia/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3-labels/abe1a3-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/abe1a3/abe1a3.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31-labels/f49f31-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/ptn/f49f31/f49f31.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/06f252-labels/06f252-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/06f252/06f252.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/076995-labels/076995-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/076995/076995.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6-labels/33cae6-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/33cae6/33cae6.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4-labels/3b20d4-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3b20d4/3b20d4.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360-labels/3f8360-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/3f8360/3f8360.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/425403-labels/425403-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/425403/425403.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa-labels/75cdfa-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/75cdfa/75cdfa.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638-labels/9b8638-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/9b8638/9b8638.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd-labels/aee7fd-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/aee7fd/aee7fd.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1-labels/bc32f1-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bc32f1/bc32f1.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14-labels/bd5c14-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/bd5c14/bd5c14.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c-labels/c7415c-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/c7415c/c7415c.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/collection.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/e52478-labels/e52478-labels.json diff --git a/tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json b/tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json similarity index 100% rename from tests/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json rename to tests/v1/data-files/catalogs/label_catalog-v0.8.1/znz/e52478/e52478.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/collection.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/catalog.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_162740_ssc1d1/20170831_162740_ssc1d1.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_172754_101c/20170831_172754_101c.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/20170831_195425_SS02/20170831_195425_SS02.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/2017831_195552_SS02/2017831_195552_SS02.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/Houston-East-20170831-103f-100d-0f4f-RGB/Houston-East-20170831-103f-100d-0f4f-RGB.json diff --git a/tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json b/tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json similarity index 100% rename from tests/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json rename to tests/v1/data-files/catalogs/planet-example-v1.0.0-beta.2/hurricane-harvey/hurricane-harvey-0831/catalog.json diff --git a/tests/data-files/catalogs/test-case-1/catalog.json b/tests/v1/data-files/catalogs/test-case-1/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/catalog.json rename to tests/v1/data-files/catalogs/test-case-1/catalog.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json b/tests/v1/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-imagery/area-1-1-imagery.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json b/tests/v1/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/area-1-1/area-1-1-labels/area-1-1-labels.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json b/tests/v1/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/area-1-1/collection.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json b/tests/v1/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-imagery/area-1-2-imagery.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json b/tests/v1/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/area-1-2/area-1-2-labels/area-1-2-labels.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json b/tests/v1/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/area-1-2/collection.json diff --git a/tests/data-files/catalogs/test-case-1/country-1/catalog.json b/tests/v1/data-files/catalogs/test-case-1/country-1/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-1/catalog.json rename to tests/v1/data-files/catalogs/test-case-1/country-1/catalog.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json b/tests/v1/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-imagery/area-2-1-imagery.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json b/tests/v1/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/area-2-1/area-2-1-labels/area-2-1-labels.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json b/tests/v1/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/area-2-1/collection.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json b/tests/v1/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-imagery/area-2-2-imagery.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json b/tests/v1/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/area-2-2/area-2-2-labels/area-2-2-labels.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json b/tests/v1/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/area-2-2/collection.json diff --git a/tests/data-files/catalogs/test-case-1/country-2/catalog.json b/tests/v1/data-files/catalogs/test-case-1/country-2/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-1/country-2/catalog.json rename to tests/v1/data-files/catalogs/test-case-1/country-2/catalog.json diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json b/tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json similarity index 100% rename from tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json rename to tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/cf73ec1a-d790-4b59-b077-e101738571ed.json diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/data.geojson b/tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/data.geojson similarity index 100% rename from tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/data.geojson rename to tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/cf73ec1a-d790-4b59-b077-e101738571ed/data.geojson diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json b/tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json rename to tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/5b922d42-9a77-4f79-a672-86096f7f849e/collection.json diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json b/tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json rename to tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/collection.json diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json b/tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json rename to tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/collection.json diff --git a/tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json b/tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json similarity index 100% rename from tests/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json rename to tests/v1/data-files/catalogs/test-case-2/1a8c1632-fa91-4a62-b33e-3a87c2ebdf16/f433578c-f879-414d-8101-83142a0a13c3/d43bead8-e3f8-4c51-95d6-e24e750a402b/d43bead8-e3f8-4c51-95d6-e24e750a402b.json diff --git a/tests/data-files/catalogs/test-case-2/catalog.json b/tests/v1/data-files/catalogs/test-case-2/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-2/catalog.json rename to tests/v1/data-files/catalogs/test-case-2/catalog.json diff --git a/tests/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json b/tests/v1/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json rename to tests/v1/data-files/catalogs/test-case-4/acc/665946-labels/665946-labels.json diff --git a/tests/data-files/catalogs/test-case-4/acc/665946/665946.json b/tests/v1/data-files/catalogs/test-case-4/acc/665946/665946.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/665946/665946.json rename to tests/v1/data-files/catalogs/test-case-4/acc/665946/665946.json diff --git a/tests/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json b/tests/v1/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json rename to tests/v1/data-files/catalogs/test-case-4/acc/a42435-labels/a42435-labels.json diff --git a/tests/data-files/catalogs/test-case-4/acc/a42435/a42435.json b/tests/v1/data-files/catalogs/test-case-4/acc/a42435/a42435.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/a42435/a42435.json rename to tests/v1/data-files/catalogs/test-case-4/acc/a42435/a42435.json diff --git a/tests/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json b/tests/v1/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json rename to tests/v1/data-files/catalogs/test-case-4/acc/ca041a-labels/ca041a-labels.json diff --git a/tests/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json b/tests/v1/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json rename to tests/v1/data-files/catalogs/test-case-4/acc/ca041a/ca041a.json diff --git a/tests/data-files/catalogs/test-case-4/acc/collection.json b/tests/v1/data-files/catalogs/test-case-4/acc/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/collection.json rename to tests/v1/data-files/catalogs/test-case-4/acc/collection.json diff --git a/tests/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json b/tests/v1/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json rename to tests/v1/data-files/catalogs/test-case-4/acc/d41d81-labels/d41d81-labels.json diff --git a/tests/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json b/tests/v1/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json rename to tests/v1/data-files/catalogs/test-case-4/acc/d41d81/d41d81.json diff --git a/tests/data-files/catalogs/test-case-4/catalog.json b/tests/v1/data-files/catalogs/test-case-4/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/catalog.json rename to tests/v1/data-files/catalogs/test-case-4/catalog.json diff --git a/tests/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json b/tests/v1/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json rename to tests/v1/data-files/catalogs/test-case-4/dar/0a4c40-labels/0a4c40-labels.json diff --git a/tests/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json b/tests/v1/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json rename to tests/v1/data-files/catalogs/test-case-4/dar/0a4c40/0a4c40.json diff --git a/tests/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json b/tests/v1/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json rename to tests/v1/data-files/catalogs/test-case-4/dar/353093-labels/353093-labels.json diff --git a/tests/data-files/catalogs/test-case-4/dar/353093/353093.json b/tests/v1/data-files/catalogs/test-case-4/dar/353093/353093.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/353093/353093.json rename to tests/v1/data-files/catalogs/test-case-4/dar/353093/353093.json diff --git a/tests/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json b/tests/v1/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json rename to tests/v1/data-files/catalogs/test-case-4/dar/42f235-labels/42f235-labels.json diff --git a/tests/data-files/catalogs/test-case-4/dar/42f235/42f235.json b/tests/v1/data-files/catalogs/test-case-4/dar/42f235/42f235.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/42f235/42f235.json rename to tests/v1/data-files/catalogs/test-case-4/dar/42f235/42f235.json diff --git a/tests/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json b/tests/v1/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json rename to tests/v1/data-files/catalogs/test-case-4/dar/a017f9-labels/a017f9-labels.json diff --git a/tests/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json b/tests/v1/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json rename to tests/v1/data-files/catalogs/test-case-4/dar/a017f9/a017f9.json diff --git a/tests/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json b/tests/v1/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json rename to tests/v1/data-files/catalogs/test-case-4/dar/b15fce-labels/b15fce-labels.json diff --git a/tests/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json b/tests/v1/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json rename to tests/v1/data-files/catalogs/test-case-4/dar/b15fce/b15fce.json diff --git a/tests/data-files/catalogs/test-case-4/dar/collection.json b/tests/v1/data-files/catalogs/test-case-4/dar/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/collection.json rename to tests/v1/data-files/catalogs/test-case-4/dar/collection.json diff --git a/tests/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json b/tests/v1/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json rename to tests/v1/data-files/catalogs/test-case-4/dar/f883a0-labels/f883a0-labels.json diff --git a/tests/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json b/tests/v1/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json rename to tests/v1/data-files/catalogs/test-case-4/dar/f883a0/f883a0.json diff --git a/tests/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json b/tests/v1/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json rename to tests/v1/data-files/catalogs/test-case-4/kam/4e7c7f-labels/4e7c7f-labels.json diff --git a/tests/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json b/tests/v1/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json rename to tests/v1/data-files/catalogs/test-case-4/kam/4e7c7f/4e7c7f.json diff --git a/tests/data-files/catalogs/test-case-4/kam/collection.json b/tests/v1/data-files/catalogs/test-case-4/kam/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/kam/collection.json rename to tests/v1/data-files/catalogs/test-case-4/kam/collection.json diff --git a/tests/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json b/tests/v1/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json rename to tests/v1/data-files/catalogs/test-case-4/mon/207cc7-labels/207cc7-labels.json diff --git a/tests/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json b/tests/v1/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json rename to tests/v1/data-files/catalogs/test-case-4/mon/207cc7/207cc7.json diff --git a/tests/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json b/tests/v1/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json rename to tests/v1/data-files/catalogs/test-case-4/mon/401175-labels/401175-labels.json diff --git a/tests/data-files/catalogs/test-case-4/mon/401175/401175.json b/tests/v1/data-files/catalogs/test-case-4/mon/401175/401175.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/401175/401175.json rename to tests/v1/data-files/catalogs/test-case-4/mon/401175/401175.json diff --git a/tests/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json b/tests/v1/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json rename to tests/v1/data-files/catalogs/test-case-4/mon/493701-labels/493701-labels.json diff --git a/tests/data-files/catalogs/test-case-4/mon/493701/493701.json b/tests/v1/data-files/catalogs/test-case-4/mon/493701/493701.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/493701/493701.json rename to tests/v1/data-files/catalogs/test-case-4/mon/493701/493701.json diff --git a/tests/data-files/catalogs/test-case-4/mon/collection.json b/tests/v1/data-files/catalogs/test-case-4/mon/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/collection.json rename to tests/v1/data-files/catalogs/test-case-4/mon/collection.json diff --git a/tests/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json b/tests/v1/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json rename to tests/v1/data-files/catalogs/test-case-4/mon/f15272-labels/f15272-labels.json diff --git a/tests/data-files/catalogs/test-case-4/mon/f15272/f15272.json b/tests/v1/data-files/catalogs/test-case-4/mon/f15272/f15272.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/mon/f15272/f15272.json rename to tests/v1/data-files/catalogs/test-case-4/mon/f15272/f15272.json diff --git a/tests/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json b/tests/v1/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json rename to tests/v1/data-files/catalogs/test-case-4/nia/825a50-labels/825a50-labels.json diff --git a/tests/data-files/catalogs/test-case-4/nia/825a50/825a50.json b/tests/v1/data-files/catalogs/test-case-4/nia/825a50/825a50.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/nia/825a50/825a50.json rename to tests/v1/data-files/catalogs/test-case-4/nia/825a50/825a50.json diff --git a/tests/data-files/catalogs/test-case-4/nia/collection.json b/tests/v1/data-files/catalogs/test-case-4/nia/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/nia/collection.json rename to tests/v1/data-files/catalogs/test-case-4/nia/collection.json diff --git a/tests/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json b/tests/v1/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json rename to tests/v1/data-files/catalogs/test-case-4/ptn/abe1a3-labels/abe1a3-labels.json diff --git a/tests/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json b/tests/v1/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json rename to tests/v1/data-files/catalogs/test-case-4/ptn/abe1a3/abe1a3.json diff --git a/tests/data-files/catalogs/test-case-4/ptn/collection.json b/tests/v1/data-files/catalogs/test-case-4/ptn/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/ptn/collection.json rename to tests/v1/data-files/catalogs/test-case-4/ptn/collection.json diff --git a/tests/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json b/tests/v1/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json rename to tests/v1/data-files/catalogs/test-case-4/ptn/f49f31-labels/f49f31-labels.json diff --git a/tests/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json b/tests/v1/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json rename to tests/v1/data-files/catalogs/test-case-4/ptn/f49f31/f49f31.json diff --git a/tests/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/06f252-labels/06f252-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/06f252/06f252.json b/tests/v1/data-files/catalogs/test-case-4/znz/06f252/06f252.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/06f252/06f252.json rename to tests/v1/data-files/catalogs/test-case-4/znz/06f252/06f252.json diff --git a/tests/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/076995-labels/076995-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/076995/076995.json b/tests/v1/data-files/catalogs/test-case-4/znz/076995/076995.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/076995/076995.json rename to tests/v1/data-files/catalogs/test-case-4/znz/076995/076995.json diff --git a/tests/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/33cae6-labels/33cae6-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json b/tests/v1/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json rename to tests/v1/data-files/catalogs/test-case-4/znz/33cae6/33cae6.json diff --git a/tests/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/3b20d4-labels/3b20d4-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json b/tests/v1/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json rename to tests/v1/data-files/catalogs/test-case-4/znz/3b20d4/3b20d4.json diff --git a/tests/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/3f8360-labels/3f8360-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json b/tests/v1/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json rename to tests/v1/data-files/catalogs/test-case-4/znz/3f8360/3f8360.json diff --git a/tests/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/425403-labels/425403-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/425403/425403.json b/tests/v1/data-files/catalogs/test-case-4/znz/425403/425403.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/425403/425403.json rename to tests/v1/data-files/catalogs/test-case-4/znz/425403/425403.json diff --git a/tests/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/75cdfa-labels/75cdfa-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json b/tests/v1/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json rename to tests/v1/data-files/catalogs/test-case-4/znz/75cdfa/75cdfa.json diff --git a/tests/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/9b8638-labels/9b8638-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json b/tests/v1/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json rename to tests/v1/data-files/catalogs/test-case-4/znz/9b8638/9b8638.json diff --git a/tests/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/aee7fd-labels/aee7fd-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json b/tests/v1/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json rename to tests/v1/data-files/catalogs/test-case-4/znz/aee7fd/aee7fd.json diff --git a/tests/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/bc32f1-labels/bc32f1-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json b/tests/v1/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json rename to tests/v1/data-files/catalogs/test-case-4/znz/bc32f1/bc32f1.json diff --git a/tests/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/bd5c14-labels/bd5c14-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json b/tests/v1/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json rename to tests/v1/data-files/catalogs/test-case-4/znz/bd5c14/bd5c14.json diff --git a/tests/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/c7415c-labels/c7415c-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json b/tests/v1/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json rename to tests/v1/data-files/catalogs/test-case-4/znz/c7415c/c7415c.json diff --git a/tests/data-files/catalogs/test-case-4/znz/collection.json b/tests/v1/data-files/catalogs/test-case-4/znz/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/collection.json rename to tests/v1/data-files/catalogs/test-case-4/znz/collection.json diff --git a/tests/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json b/tests/v1/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json rename to tests/v1/data-files/catalogs/test-case-4/znz/e52478-labels/e52478-labels.json diff --git a/tests/data-files/catalogs/test-case-4/znz/e52478/e52478.json b/tests/v1/data-files/catalogs/test-case-4/znz/e52478/e52478.json similarity index 100% rename from tests/data-files/catalogs/test-case-4/znz/e52478/e52478.json rename to tests/v1/data-files/catalogs/test-case-4/znz/e52478/e52478.json diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json b/tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json rename to tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/CBERS_4_MUX_20190510_027_069_L2/CBERS_4_MUX_20190510_027_069_L2.json diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json b/tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json rename to tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/069/catalog.json diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json b/tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json rename to tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/CBERS4-MUX-027/collection.json diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json b/tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json rename to tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/CBERS4-MUX-027/catalog.json diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json b/tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json rename to tests/v1/data-files/catalogs/test-case-5/CBERS4/CBERS4MUX/collection.json diff --git a/tests/data-files/catalogs/test-case-5/CBERS4/catalog.json b/tests/v1/data-files/catalogs/test-case-5/CBERS4/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/CBERS4/catalog.json rename to tests/v1/data-files/catalogs/test-case-5/CBERS4/catalog.json diff --git a/tests/data-files/catalogs/test-case-5/catalog.json b/tests/v1/data-files/catalogs/test-case-5/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-5/catalog.json rename to tests/v1/data-files/catalogs/test-case-5/catalog.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/3c67b59c-2e6f-47fb-ba3c-0dd106941096.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/collection.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/data.geojson b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/data.geojson similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/data.geojson rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/400c22e3-5b54-438b-b600-5e9bd6d0a498/data.geojson diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/3bfb5bd3-5740-4ed0-92c9-968cc532dbc5.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/41c57cea-50ba-495c-9ee7-17ddba837380.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/4514bbc6-cd17-4c14-816d-1709dfc61079.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/52072b95-0275-4255-be5e-c567006f80cb.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/84fe42c0-9d23-404a-bd0f-1406112cca8c.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/884a939d-1d73-4351-9601-f9ee89a980b0.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/a6bd2ff9-3805-40ab-96fa-b7f112b18973.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/collection.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/e40bb30d-0295-42ed-ba04-6cf563b057f9.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/9420e0fd-3731-40e8-93ff-5b6ad0067048/eb1124cc-3b45-4487-bc2c-4855ac877ad9.json diff --git a/tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json b/tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json rename to tests/v1/data-files/catalogs/test-case-6/12cb17a8-ae08-469c-a2be-4d0619240014/collection.json diff --git a/tests/data-files/catalogs/test-case-6/catalog.json b/tests/v1/data-files/catalogs/test-case-6/catalog.json similarity index 100% rename from tests/data-files/catalogs/test-case-6/catalog.json rename to tests/v1/data-files/catalogs/test-case-6/catalog.json diff --git a/tests/data-files/change_stac_version.py b/tests/v1/data-files/change_stac_version.py similarity index 91% rename from tests/data-files/change_stac_version.py rename to tests/v1/data-files/change_stac_version.py index 5a5346657..d2971b393 100644 --- a/tests/data-files/change_stac_version.py +++ b/tests/v1/data-files/change_stac_version.py @@ -24,11 +24,7 @@ def migrate(path: str) -> None: if "stac_version" in stac_json: cur_ver = stac_json["stac_version"] if not cur_ver == TARGET_VERSION: - print( - " - Migrating {} from {} to {}...".format( - path, cur_ver, TARGET_VERSION - ) - ) + print(f" - Migrating {path} from {cur_ver} to {TARGET_VERSION}...") obj = pystac.read_dict(stac_json, href=path) migrated = obj.to_dict(include_self_link=False) with open(path, "w") as f: diff --git a/tests/data-files/classification/classification_landsat_example.json b/tests/v1/data-files/classification/classification_landsat_example.json similarity index 100% rename from tests/data-files/classification/classification_landsat_example.json rename to tests/v1/data-files/classification/classification_landsat_example.json diff --git a/tests/data-files/classification/collection-item-assets-raster-bands.json b/tests/v1/data-files/classification/collection-item-assets-raster-bands.json similarity index 100% rename from tests/data-files/classification/collection-item-assets-raster-bands.json rename to tests/v1/data-files/classification/collection-item-assets-raster-bands.json diff --git a/tests/data-files/collections/multi-extent.json b/tests/v1/data-files/collections/multi-extent.json similarity index 100% rename from tests/data-files/collections/multi-extent.json rename to tests/v1/data-files/collections/multi-extent.json diff --git a/tests/data-files/collections/with-assets.json b/tests/v1/data-files/collections/with-assets.json similarity index 100% rename from tests/data-files/collections/with-assets.json rename to tests/v1/data-files/collections/with-assets.json diff --git a/tests/data-files/datacube/item.json b/tests/v1/data-files/datacube/item.json similarity index 100% rename from tests/data-files/datacube/item.json rename to tests/v1/data-files/datacube/item.json diff --git a/tests/data-files/eo/eo-collection.json b/tests/v1/data-files/eo/eo-collection.json similarity index 100% rename from tests/data-files/eo/eo-collection.json rename to tests/v1/data-files/eo/eo-collection.json diff --git a/tests/data-files/eo/eo-landsat-example.json b/tests/v1/data-files/eo/eo-landsat-example.json similarity index 100% rename from tests/data-files/eo/eo-landsat-example.json rename to tests/v1/data-files/eo/eo-landsat-example.json diff --git a/tests/data-files/eo/eo-sentinel2-item.json b/tests/v1/data-files/eo/eo-sentinel2-item.json similarity index 100% rename from tests/data-files/eo/eo-sentinel2-item.json rename to tests/v1/data-files/eo/eo-sentinel2-item.json diff --git a/tests/data-files/eo/sample-bands-in-item-properties.json b/tests/v1/data-files/eo/sample-bands-in-item-properties.json similarity index 100% rename from tests/data-files/eo/sample-bands-in-item-properties.json rename to tests/v1/data-files/eo/sample-bands-in-item-properties.json diff --git a/tests/data-files/examples/0.8.1/catalog-spec/examples/catalog.json b/tests/v1/data-files/examples/0.8.1/catalog-spec/examples/catalog.json similarity index 100% rename from tests/data-files/examples/0.8.1/catalog-spec/examples/catalog.json rename to tests/v1/data-files/examples/0.8.1/catalog-spec/examples/catalog.json diff --git a/tests/data-files/examples/0.8.1/catalog-spec/examples/summaries-s2.json b/tests/v1/data-files/examples/0.8.1/catalog-spec/examples/summaries-s2.json similarity index 100% rename from tests/data-files/examples/0.8.1/catalog-spec/examples/summaries-s2.json rename to tests/v1/data-files/examples/0.8.1/catalog-spec/examples/summaries-s2.json diff --git a/tests/data-files/examples/0.8.1/collection-spec/examples/landsat-collection.json b/tests/v1/data-files/examples/0.8.1/collection-spec/examples/landsat-collection.json similarity index 100% rename from tests/data-files/examples/0.8.1/collection-spec/examples/landsat-collection.json rename to tests/v1/data-files/examples/0.8.1/collection-spec/examples/landsat-collection.json diff --git a/tests/data-files/examples/0.8.1/collection-spec/examples/landsat-item.json b/tests/v1/data-files/examples/0.8.1/collection-spec/examples/landsat-item.json similarity index 100% rename from tests/data-files/examples/0.8.1/collection-spec/examples/landsat-item.json rename to tests/v1/data-files/examples/0.8.1/collection-spec/examples/landsat-item.json diff --git a/tests/data-files/examples/0.8.1/collection-spec/examples/sentinel2.json b/tests/v1/data-files/examples/0.8.1/collection-spec/examples/sentinel2.json similarity index 100% rename from tests/data-files/examples/0.8.1/collection-spec/examples/sentinel2.json rename to tests/v1/data-files/examples/0.8.1/collection-spec/examples/sentinel2.json diff --git a/tests/data-files/examples/0.8.1/extensions/asset/examples/example-landsat8.json b/tests/v1/data-files/examples/0.8.1/extensions/asset/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/asset/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.8.1/extensions/asset/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.8.1/extensions/checksum/examples/example-sentinel1.json b/tests/v1/data-files/examples/0.8.1/extensions/checksum/examples/example-sentinel1.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/checksum/examples/example-sentinel1.json rename to tests/v1/data-files/examples/0.8.1/extensions/checksum/examples/example-sentinel1.json diff --git a/tests/data-files/examples/0.8.1/extensions/datacube/examples/example.json b/tests/v1/data-files/examples/0.8.1/extensions/datacube/examples/example.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/datacube/examples/example.json rename to tests/v1/data-files/examples/0.8.1/extensions/datacube/examples/example.json diff --git a/tests/data-files/examples/0.8.1/extensions/datetime-range/examples/example-video.json b/tests/v1/data-files/examples/0.8.1/extensions/datetime-range/examples/example-video.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/datetime-range/examples/example-video.json rename to tests/v1/data-files/examples/0.8.1/extensions/datetime-range/examples/example-video.json diff --git a/tests/data-files/examples/0.8.1/extensions/eo/examples/example-landsat8.json b/tests/v1/data-files/examples/0.8.1/extensions/eo/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/eo/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.8.1/extensions/eo/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/catalog.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/catalog.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/catalog.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/catalog.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/spacenet-buildings/collection.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/collection.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz001.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/multidataset/zanzibar/znz029.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_collection.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_item.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_item.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_item.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_item.json diff --git a/tests/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_source.json b/tests/v1/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_source.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_source.json rename to tests/v1/data-files/examples/0.8.1/extensions/label/examples/spacenet-roads/roads_source.json diff --git a/tests/data-files/examples/0.8.1/extensions/pointcloud/examples/example-autzen.json b/tests/v1/data-files/examples/0.8.1/extensions/pointcloud/examples/example-autzen.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/pointcloud/examples/example-autzen.json rename to tests/v1/data-files/examples/0.8.1/extensions/pointcloud/examples/example-autzen.json diff --git a/tests/data-files/examples/0.8.1/extensions/sar/examples/envisat.json b/tests/v1/data-files/examples/0.8.1/extensions/sar/examples/envisat.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/sar/examples/envisat.json rename to tests/v1/data-files/examples/0.8.1/extensions/sar/examples/envisat.json diff --git a/tests/data-files/examples/0.8.1/extensions/sar/examples/sentinel1.json b/tests/v1/data-files/examples/0.8.1/extensions/sar/examples/sentinel1.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/sar/examples/sentinel1.json rename to tests/v1/data-files/examples/0.8.1/extensions/sar/examples/sentinel1.json diff --git a/tests/data-files/examples/0.8.1/extensions/scientific/examples/collection.json b/tests/v1/data-files/examples/0.8.1/extensions/scientific/examples/collection.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/scientific/examples/collection.json rename to tests/v1/data-files/examples/0.8.1/extensions/scientific/examples/collection.json diff --git a/tests/data-files/examples/0.8.1/extensions/scientific/examples/item.json b/tests/v1/data-files/examples/0.8.1/extensions/scientific/examples/item.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/scientific/examples/item.json rename to tests/v1/data-files/examples/0.8.1/extensions/scientific/examples/item.json diff --git a/tests/data-files/examples/0.8.1/extensions/single-file-stac/examples/example-search.json b/tests/v1/data-files/examples/0.8.1/extensions/single-file-stac/examples/example-search.json similarity index 100% rename from tests/data-files/examples/0.8.1/extensions/single-file-stac/examples/example-search.json rename to tests/v1/data-files/examples/0.8.1/extensions/single-file-stac/examples/example-search.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/digitalglobe-sample.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/digitalglobe-sample.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/digitalglobe-sample.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/digitalglobe-sample.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-full.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-full.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-full.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-full.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-minimal.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-minimal.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-minimal.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/itemcollection-sample-minimal.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/landsat8-sample.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/landsat8-sample.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/landsat8-sample.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/landsat8-sample.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/planet-sample.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/planet-sample.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/planet-sample.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/planet-sample.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/sample-full.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/sample-full.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/sample-full.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/sample-full.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/sample.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/sample.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/sample.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/sample.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/sentinel-s2-l1c-collection.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/sentinel-s2-l1c-collection.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/sentinel-s2-l1c-collection.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/sentinel-s2-l1c-collection.json diff --git a/tests/data-files/examples/0.8.1/item-spec/examples/sentinel2-sample.json b/tests/v1/data-files/examples/0.8.1/item-spec/examples/sentinel2-sample.json similarity index 100% rename from tests/data-files/examples/0.8.1/item-spec/examples/sentinel2-sample.json rename to tests/v1/data-files/examples/0.8.1/item-spec/examples/sentinel2-sample.json diff --git a/tests/data-files/examples/0.9.0/catalog-spec/examples/catalog.json b/tests/v1/data-files/examples/0.9.0/catalog-spec/examples/catalog.json similarity index 100% rename from tests/data-files/examples/0.9.0/catalog-spec/examples/catalog.json rename to tests/v1/data-files/examples/0.9.0/catalog-spec/examples/catalog.json diff --git a/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json b/tests/v1/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json rename to tests/v1/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json diff --git a/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-item.json b/tests/v1/data-files/examples/0.9.0/collection-spec/examples/landsat-item.json similarity index 100% rename from tests/data-files/examples/0.9.0/collection-spec/examples/landsat-item.json rename to tests/v1/data-files/examples/0.9.0/collection-spec/examples/landsat-item.json diff --git a/tests/data-files/examples/0.9.0/collection-spec/examples/sentinel2.json b/tests/v1/data-files/examples/0.9.0/collection-spec/examples/sentinel2.json similarity index 100% rename from tests/data-files/examples/0.9.0/collection-spec/examples/sentinel2.json rename to tests/v1/data-files/examples/0.9.0/collection-spec/examples/sentinel2.json diff --git a/tests/data-files/examples/0.9.0/extensions/asset/examples/example-landsat8.json b/tests/v1/data-files/examples/0.9.0/extensions/asset/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/asset/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.9.0/extensions/asset/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.9.0/extensions/checksum/examples/sentinel1.json b/tests/v1/data-files/examples/0.9.0/extensions/checksum/examples/sentinel1.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/checksum/examples/sentinel1.json rename to tests/v1/data-files/examples/0.9.0/extensions/checksum/examples/sentinel1.json diff --git a/tests/data-files/examples/0.9.0/extensions/commons/examples/landsat-collection.json b/tests/v1/data-files/examples/0.9.0/extensions/commons/examples/landsat-collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/commons/examples/landsat-collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/commons/examples/landsat-collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/commons/examples/landsat-item.json b/tests/v1/data-files/examples/0.9.0/extensions/commons/examples/landsat-item.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/commons/examples/landsat-item.json rename to tests/v1/data-files/examples/0.9.0/extensions/commons/examples/landsat-item.json diff --git a/tests/data-files/examples/0.9.0/extensions/datacube/examples/example-collection.json b/tests/v1/data-files/examples/0.9.0/extensions/datacube/examples/example-collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/datacube/examples/example-collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/datacube/examples/example-collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/datacube/examples/example-item.json b/tests/v1/data-files/examples/0.9.0/extensions/datacube/examples/example-item.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/datacube/examples/example-item.json rename to tests/v1/data-files/examples/0.9.0/extensions/datacube/examples/example-item.json diff --git a/tests/data-files/examples/0.9.0/extensions/eo/examples/example-landsat8.json b/tests/v1/data-files/examples/0.9.0/extensions/eo/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/eo/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.9.0/extensions/eo/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/catalog.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/catalog.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/catalog.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/catalog.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/spacenet-buildings/collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/multidataset/zanzibar/znz001.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_item.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_item.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_item.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_item.json diff --git a/tests/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_source.json b/tests/v1/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_source.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_source.json rename to tests/v1/data-files/examples/0.9.0/extensions/label/examples/spacenet-roads/roads_source.json diff --git a/tests/data-files/examples/0.9.0/extensions/pointcloud/examples/example-autzen.json b/tests/v1/data-files/examples/0.9.0/extensions/pointcloud/examples/example-autzen.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/pointcloud/examples/example-autzen.json rename to tests/v1/data-files/examples/0.9.0/extensions/pointcloud/examples/example-autzen.json diff --git a/tests/data-files/examples/0.9.0/extensions/projection/examples/example-landsat8.json b/tests/v1/data-files/examples/0.9.0/extensions/projection/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/projection/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.9.0/extensions/projection/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.9.0/extensions/sar/examples/envisat.json b/tests/v1/data-files/examples/0.9.0/extensions/sar/examples/envisat.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/sar/examples/envisat.json rename to tests/v1/data-files/examples/0.9.0/extensions/sar/examples/envisat.json diff --git a/tests/data-files/examples/0.9.0/extensions/sar/examples/sentinel1.json b/tests/v1/data-files/examples/0.9.0/extensions/sar/examples/sentinel1.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/sar/examples/sentinel1.json rename to tests/v1/data-files/examples/0.9.0/extensions/sar/examples/sentinel1.json diff --git a/tests/data-files/examples/0.9.0/extensions/sat/examples/example-landsat8.json b/tests/v1/data-files/examples/0.9.0/extensions/sat/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/sat/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.9.0/extensions/sat/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.9.0/extensions/scientific/examples/collection.json b/tests/v1/data-files/examples/0.9.0/extensions/scientific/examples/collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/scientific/examples/collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/scientific/examples/collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/scientific/examples/item.json b/tests/v1/data-files/examples/0.9.0/extensions/scientific/examples/item.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/scientific/examples/item.json rename to tests/v1/data-files/examples/0.9.0/extensions/scientific/examples/item.json diff --git a/tests/data-files/examples/0.9.0/extensions/version/examples/collection.json b/tests/v1/data-files/examples/0.9.0/extensions/version/examples/collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/version/examples/collection.json rename to tests/v1/data-files/examples/0.9.0/extensions/version/examples/collection.json diff --git a/tests/data-files/examples/0.9.0/extensions/version/examples/item.json b/tests/v1/data-files/examples/0.9.0/extensions/version/examples/item.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/version/examples/item.json rename to tests/v1/data-files/examples/0.9.0/extensions/version/examples/item.json diff --git a/tests/data-files/examples/0.9.0/extensions/view/examples/example-landsat8.json b/tests/v1/data-files/examples/0.9.0/extensions/view/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/0.9.0/extensions/view/examples/example-landsat8.json rename to tests/v1/data-files/examples/0.9.0/extensions/view/examples/example-landsat8.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/datetimerange.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/datetimerange.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/datetimerange.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/datetimerange.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/digitalglobe-sample.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/digitalglobe-sample.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/digitalglobe-sample.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/digitalglobe-sample.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-full.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-full.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-full.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-full.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-minimal.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-minimal.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-minimal.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/itemcollection-sample-minimal.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/landsat8-sample.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/landsat8-sample.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/landsat8-sample.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/landsat8-sample.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/planet-sample.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/planet-sample.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/planet-sample.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/planet-sample.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/sample-full.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/sample-full.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/sample-full.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/sample-full.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/sample.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/sample.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/sample.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/sample.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/sentinel-s2-l2a-collection.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/sentinel-s2-l2a-collection.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/sentinel-s2-l2a-collection.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/sentinel-s2-l2a-collection.json diff --git a/tests/data-files/examples/0.9.0/item-spec/examples/sentinel2-sample.json b/tests/v1/data-files/examples/0.9.0/item-spec/examples/sentinel2-sample.json similarity index 100% rename from tests/data-files/examples/0.9.0/item-spec/examples/sentinel2-sample.json rename to tests/v1/data-files/examples/0.9.0/item-spec/examples/sentinel2-sample.json diff --git a/tests/data-files/examples/1.0.0-RC1/catalog.json b/tests/v1/data-files/examples/1.0.0-RC1/catalog.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/catalog.json rename to tests/v1/data-files/examples/1.0.0-RC1/catalog.json diff --git a/tests/data-files/examples/1.0.0-RC1/collection-only/collection.json b/tests/v1/data-files/examples/1.0.0-RC1/collection-only/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/collection-only/collection.json rename to tests/v1/data-files/examples/1.0.0-RC1/collection-only/collection.json diff --git a/tests/data-files/examples/1.0.0-RC1/collection.json b/tests/v1/data-files/examples/1.0.0-RC1/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/collection.json rename to tests/v1/data-files/examples/1.0.0-RC1/collection.json diff --git a/tests/data-files/examples/1.0.0-RC1/collectionless-item.json b/tests/v1/data-files/examples/1.0.0-RC1/collectionless-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/collectionless-item.json rename to tests/v1/data-files/examples/1.0.0-RC1/collectionless-item.json diff --git a/tests/data-files/examples/1.0.0-RC1/core-item.json b/tests/v1/data-files/examples/1.0.0-RC1/core-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/core-item.json rename to tests/v1/data-files/examples/1.0.0-RC1/core-item.json diff --git a/tests/data-files/examples/1.0.0-RC1/extended-item.json b/tests/v1/data-files/examples/1.0.0-RC1/extended-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/extended-item.json rename to tests/v1/data-files/examples/1.0.0-RC1/extended-item.json diff --git a/tests/data-files/examples/1.0.0-RC1/extensions-collection/collection.json b/tests/v1/data-files/examples/1.0.0-RC1/extensions-collection/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/extensions-collection/collection.json rename to tests/v1/data-files/examples/1.0.0-RC1/extensions-collection/collection.json diff --git a/tests/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json b/tests/v1/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json rename to tests/v1/data-files/examples/1.0.0-RC1/extensions-collection/proj-example/proj-example.json diff --git a/tests/data-files/examples/1.0.0-RC1/simple-item.json b/tests/v1/data-files/examples/1.0.0-RC1/simple-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC1/simple-item.json rename to tests/v1/data-files/examples/1.0.0-RC1/simple-item.json diff --git a/tests/data-files/examples/1.0.0-RC2/extended-item.json b/tests/v1/data-files/examples/1.0.0-RC2/extended-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-RC2/extended-item.json rename to tests/v1/data-files/examples/1.0.0-RC2/extended-item.json diff --git a/tests/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog-items.json b/tests/v1/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog-items.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog-items.json rename to tests/v1/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog-items.json diff --git a/tests/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog.json b/tests/v1/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog.json rename to tests/v1/data-files/examples/1.0.0-beta.2/catalog-spec/examples/catalog.json diff --git a/tests/data-files/examples/1.0.0-beta.2/collection-spec/examples/landsat-collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/collection-spec/examples/landsat-collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/collection-spec/examples/landsat-collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/collection-spec/examples/landsat-collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/collection-spec/examples/sentinel2.json b/tests/v1/data-files/examples/1.0.0-beta.2/collection-spec/examples/sentinel2.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/collection-spec/examples/sentinel2.json rename to tests/v1/data-files/examples/1.0.0-beta.2/collection-spec/examples/sentinel2.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/collection-assets/examples/example-esm.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-item.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-item.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/datacube/examples/example-item.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/eo/examples/example-landsat8.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/eo/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/eo/examples/example-landsat8.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/eo/examples/example-landsat8.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/item-assets/examples/example-landsat8.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/catalog.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_2_Vegas_img2636.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_3_Paris_img1648.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/AOI_4_Shanghai_img3344.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/spacenet-buildings/collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz001.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/multidataset/zanzibar/znz029.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_item.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/label/examples/spacenet-roads/roads_source.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/pointcloud/examples/example-autzen.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/projection/examples/example-landsat8.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/projection/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/projection/examples/example-landsat8.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/projection/examples/example-landsat8.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/sar/examples/envisat.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/sar/examples/envisat.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/sar/examples/envisat.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/sar/examples/envisat.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/sar/examples/sentinel1.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/sar/examples/sentinel1.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/sar/examples/sentinel1.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/sar/examples/sentinel1.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/sat/examples/example-landsat8.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/sat/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/sat/examples/example-landsat8.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/sat/examples/example-landsat8.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/item.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/item.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/item.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/scientific/examples/item.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/single-file-stac/examples/example-search.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/single-file-stac/examples/example-search.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/single-file-stac/examples/example-search.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/single-file-stac/examples/example-search.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-dimension.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/tiled-assets/examples/example-tiled.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/timestamps/examples/example-landsat8.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/version/examples/collection.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/version/examples/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/version/examples/collection.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/version/examples/collection.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/version/examples/item.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/version/examples/item.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/version/examples/item.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/version/examples/item.json diff --git a/tests/data-files/examples/1.0.0-beta.2/extensions/view/examples/example-landsat8.json b/tests/v1/data-files/examples/1.0.0-beta.2/extensions/view/examples/example-landsat8.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/extensions/view/examples/example-landsat8.json rename to tests/v1/data-files/examples/1.0.0-beta.2/extensions/view/examples/example-landsat8.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/CBERS_4_MUX_20181029_177_106_L4.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/datetimerange.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/digitalglobe-sample.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/landsat8-sample.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/planet-sample.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/planet-sample.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/planet-sample.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/planet-sample.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/sample-full.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/sample.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/sample.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/sample.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/sample.json diff --git a/tests/data-files/examples/1.0.0-beta.2/item-spec/examples/sentinel2-sample.json b/tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/sentinel2-sample.json similarity index 100% rename from tests/data-files/examples/1.0.0-beta.2/item-spec/examples/sentinel2-sample.json rename to tests/v1/data-files/examples/1.0.0-beta.2/item-spec/examples/sentinel2-sample.json diff --git a/tests/data-files/examples/1.0.0/README.md b/tests/v1/data-files/examples/1.0.0/README.md similarity index 100% rename from tests/data-files/examples/1.0.0/README.md rename to tests/v1/data-files/examples/1.0.0/README.md diff --git a/tests/data-files/examples/1.0.0/catalog.json b/tests/v1/data-files/examples/1.0.0/catalog.json similarity index 100% rename from tests/data-files/examples/1.0.0/catalog.json rename to tests/v1/data-files/examples/1.0.0/catalog.json diff --git a/tests/data-files/examples/1.0.0/collection-only/collection-with-schemas.json b/tests/v1/data-files/examples/1.0.0/collection-only/collection-with-schemas.json similarity index 100% rename from tests/data-files/examples/1.0.0/collection-only/collection-with-schemas.json rename to tests/v1/data-files/examples/1.0.0/collection-only/collection-with-schemas.json diff --git a/tests/data-files/examples/1.0.0/collection-only/collection.json b/tests/v1/data-files/examples/1.0.0/collection-only/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0/collection-only/collection.json rename to tests/v1/data-files/examples/1.0.0/collection-only/collection.json diff --git a/tests/data-files/examples/1.0.0/collection.json b/tests/v1/data-files/examples/1.0.0/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0/collection.json rename to tests/v1/data-files/examples/1.0.0/collection.json diff --git a/tests/data-files/examples/1.0.0/collectionless-item.json b/tests/v1/data-files/examples/1.0.0/collectionless-item.json similarity index 100% rename from tests/data-files/examples/1.0.0/collectionless-item.json rename to tests/v1/data-files/examples/1.0.0/collectionless-item.json diff --git a/tests/data-files/examples/1.0.0/core-item.json b/tests/v1/data-files/examples/1.0.0/core-item.json similarity index 100% rename from tests/data-files/examples/1.0.0/core-item.json rename to tests/v1/data-files/examples/1.0.0/core-item.json diff --git a/tests/data-files/examples/1.0.0/example-sentinel2.json b/tests/v1/data-files/examples/1.0.0/example-sentinel2.json similarity index 100% rename from tests/data-files/examples/1.0.0/example-sentinel2.json rename to tests/v1/data-files/examples/1.0.0/example-sentinel2.json diff --git a/tests/data-files/examples/1.0.0/extended-item.json b/tests/v1/data-files/examples/1.0.0/extended-item.json similarity index 100% rename from tests/data-files/examples/1.0.0/extended-item.json rename to tests/v1/data-files/examples/1.0.0/extended-item.json diff --git a/tests/data-files/examples/1.0.0/extensions-collection/collection.json b/tests/v1/data-files/examples/1.0.0/extensions-collection/collection.json similarity index 100% rename from tests/data-files/examples/1.0.0/extensions-collection/collection.json rename to tests/v1/data-files/examples/1.0.0/extensions-collection/collection.json diff --git a/tests/data-files/examples/1.0.0/extensions-collection/proj-example/proj-example.json b/tests/v1/data-files/examples/1.0.0/extensions-collection/proj-example/proj-example.json similarity index 100% rename from tests/data-files/examples/1.0.0/extensions-collection/proj-example/proj-example.json rename to tests/v1/data-files/examples/1.0.0/extensions-collection/proj-example/proj-example.json diff --git a/tests/data-files/examples/1.0.0/simple-item.json b/tests/v1/data-files/examples/1.0.0/simple-item.json similarity index 100% rename from tests/data-files/examples/1.0.0/simple-item.json rename to tests/v1/data-files/examples/1.0.0/simple-item.json diff --git a/tests/v1/data-files/examples/1.1.0/README.md b/tests/v1/data-files/examples/1.1.0/README.md new file mode 100644 index 000000000..a5f8d3f62 --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/README.md @@ -0,0 +1,91 @@ +# STAC Examples + +This directory contains various examples for all parts of the STAC specification. +It is structured to be two valid STACs, meaning both [catalog.json](catalog.json) and [collection.json](collection.json) +should successfully load in various tools. They do not follow *all* the [best practices](../best-practices.md) for STAC, mostly +due to the fact that they contrive examples to show the spec and we are hosting in GitHub. But we note below where they differ from an ideal catalog. + +The various fields are mostly fictional, to be able to demonstrate the various aspects of the spec as tersely as possible. To get a sense +of real world STAC implementations we recommend exploring the [stac-examples](http://github.com/stac-utils/stac-examples) repo, which +gathers in one place copies of STAC [Items](../item-spec/item-spec.md) and [Collection](../collection-spec/collection-spec.md) +from a number of different production catalogs that all follow good STAC practices. And you should also explore the various catalogs +listed on [STAC Index](http://stacindex.org), to see full catalogs in production. + +## Organization + +This directory contains two STAC implementations, both valid, but simplified a bit to be illustrative of the key concepts, so +they do not quite follow all the best practices. + +### Simple Collection + +This STAC implementation consists of three files, all contained at the root of the examples directory + +**[collection.json](collection.json)** is a minimal 'simple collection', that links to three items. + +**[simple-item.json](simple-item.json)** is the most minimal possible compliant Item record. Most all data will +include additional fields, as STAC is designed to be a minimal common subset. But it is useful for showing exactly what is +required. + +**[core-item.json](core-item.json)** is a more realistic example, for a hypothetical analytic image +acquisition from a satellite company called 'Remote Data'. It includes additional fields covering the [common +metadata](../commons/common-metadata.md). It also links to a variety of assets that is typical for +satellite imagery, as most providers include a number of complementary files. + +**[extended-item.json](extended-item.json)** is arguably an even more realistic example, as it includes a number of the +[extensions](../extensions/) that are commonly used, to demonstrate how implementations tend to start with the core, and add in +a number of the core extensions. + +**[collectionless-item.json](collectionless-item.json)** demonstrates the common metadata that is only used when an Item does not have +a collection. It is recommended to organize items in collections, but we wanted to show how this works. This is not technically in the +'simple collection' of this section, but it follows the same pattern, so is included here. + +### Nested Catalog + +This STAC implementation shows a common pattern, starting with a catalog that links to a number of distinct collections, which may +link down to a number of items. + +**[catalog.json](catalog.json)** is a minimal catalog implementation, linking to two other collections. + +**[collection-only/collection.json](collection-only/collection.json)** is a collection that does not link to any items. This +demonstrates how is is possible to make use of STAC Collections without needing items, to serve as nice summarizing metadata for +tools that work with full layers / collections. This example collection is based on real Sentinel-2 values, so is not quite fictional, +but should be taken as just an example. + +**[extensions-collection/collection.json](extensions-collection/collection.json)** contains a small number of items, that demonstrate +more functionality available in STAC [extensions](../extensions/). These are linked to directly from the individual extensions. These +items follow the recommendations for [Catalog Layout Best Practices](../best-practices.md#catalog-layout). + +## In Depth + +As mentioned above, the files in this examples directory form valid STAC implementations. They are all based on a +fictional remote sensing company called 'Remote Data', with a URL at remotedata.io. This domain has not been set up, so those links +will not work, but any valid data provider should provide valid links to their homepage. + +The examples use the `rd:` prefix to show how providers can use custom fields when there are not set fields. In the examples these +do not link to a schema which is completely valid, but it is recommended that providers do write a JSON schema that can validate +their custom fields (we will work to add an example schema for the `rd:` fields in the future). + +### Catalog Type + +One of the most important STAC Best Practices is to [use links consistently](../best-practices.md#use-of-links), following one of the +described 'catalog types'. The catalogs described here are [Relative Published Catalogs](../best-practices.md#relative-published-catalog), +that use absolute URL's to refer to their assets (so would be an example of a [Self-contained Metadata +Only](../best-practices.md#self-contained-metadata-only) catalog that is published). + +### Differences with STAC Best Practices + +One of the most important documents in this repository is the one about [best practices](../best-practices.md). It describes a number +of practical recommendations gained by people actually implementing STAC. The core spec is designed to be as flexible as possible, so +that it is not too rigid and unable to handle unanticipated needs. But we recommend following as many of the best practices as is +feasible, as it will help ensure various STAC tools work much better. The examples in this folder don't align with all the best +practices, mostly because they are meant to demonstrate things as tersely as possible, and also because they live directly inside +a github repository. As many people will look at these examples and take them as 'how things should be' we felt its important to +highlight where things here differ from the actual best practices. + +#### Catalog Layout + +Another important recommendations concerns the [layout of STAC catalogs](../best-practices.md#catalog-layout). This is important +for tools to be able to expect a certain layout, and most tools will follow the described layout. The simple collection that consists +of the collection.json and its 3 linked items violates this. This is done to be able to show item examples directly in the root of +the 'examples' folder, so people don't have to dig deep into folders to get a quick example. But a proper catalog layout would +put the items in sub-directories, along with their assets. diff --git a/tests/v1/data-files/examples/1.1.0/catalog.json b/tests/v1/data-files/examples/1.1.0/catalog.json new file mode 100644 index 000000000..a9fe3ef8e --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/catalog.json @@ -0,0 +1,43 @@ +{ + "id": "examples", + "type": "Catalog", + "title": "Example Catalog", + "stac_version": "1.1.0", + "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.", + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json" + }, + { + "rel": "child", + "href": "./extensions-collection/collection.json", + "type": "application/json", + "title": "Collection Demonstrating STAC Extensions" + }, + { + "rel": "child", + "href": "./collection-only/collection.json", + "type": "application/json", + "title": "Collection with no items (standalone)" + }, + { + "rel": "child", + "href": "./collection-only/collection-with-schemas.json", + "type": "application/json", + "title": "Collection with no items (standalone with JSON Schemas)" + }, + { + "rel": "item", + "href": "./collectionless-item.json", + "type": "application/json", + "title": "Item that does not have a collection (not recommended, but allowed by the spec)" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.1.0/examples/catalog.json", + "type": "application/json" + } + ] +} diff --git a/tests/data-files/examples/1.1.0/collection-only/collection-with-schemas.json b/tests/v1/data-files/examples/1.1.0/collection-only/collection-with-schemas.json similarity index 100% rename from tests/data-files/examples/1.1.0/collection-only/collection-with-schemas.json rename to tests/v1/data-files/examples/1.1.0/collection-only/collection-with-schemas.json diff --git a/tests/data-files/examples/1.1.0/collection-only/collection.json b/tests/v1/data-files/examples/1.1.0/collection-only/collection.json similarity index 100% rename from tests/data-files/examples/1.1.0/collection-only/collection.json rename to tests/v1/data-files/examples/1.1.0/collection-only/collection.json diff --git a/tests/v1/data-files/examples/1.1.0/collection.json b/tests/v1/data-files/examples/1.1.0/collection.json new file mode 100644 index 000000000..9de9cbeae --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/collection.json @@ -0,0 +1,136 @@ +{ + "id": "simple-collection", + "type": "Collection", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v2.0.0/schema.json", + "https://stac-extensions.github.io/projection/v2.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "stac_version": "1.1.0", + "description": "A simple collection demonstrating core catalog fields with links to a couple of items", + "title": "Simple Example Collection", + "keywords": [ + "simple", + "example", + "collection" + ], + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-12-11T22:38:32.125Z", + "2020-12-14T18:02:31.437Z" + ] + ] + } + }, + "license": "CC-BY-4.0", + "summaries": { + "platform": [ + "cool_sat1", + "cool_sat2" + ], + "constellation": [ + "ion" + ], + "instruments": [ + "cool_sensor_v1", + "cool_sensor_v2" + ], + "gsd": { + "minimum": 0.512, + "maximum": 0.66 + }, + "eo:cloud_cover": { + "minimum": 1.2, + "maximum": 1.2 + }, + "proj:cpde": [ + "EPSG:32659" + ], + "view:sun_elevation": { + "minimum": 54.9, + "maximum": 54.9 + }, + "view:off_nadir": { + "minimum": 3.8, + "maximum": 3.8 + }, + "view:sun_azimuth": { + "minimum": 135.7, + "maximum": 135.7 + }, + "statistics": { + "type": "object", + "properties": { + "vegetation": { + "description": "Percentage of pixels that are detected as vegetation, e.g. forests, grasslands, etc.", + "minimum": 0, + "maximum": 100 + }, + "water": { + "description": "Percentage of pixels that are detected as water, e.g. rivers, oceans and ponds.", + "minimum": 0, + "maximum": 100 + }, + "urban": { + "description": "Percentage of pixels that detected as urban, e.g. roads and buildings.", + "minimum": 0, + "maximum": 100 + } + } + } + }, + "links": [ + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "item", + "href": "./simple-item.json", + "type": "application/geo+json", + "title": "Simple Item" + }, + { + "rel": "item", + "href": "./core-item.json", + "type": "application/geo+json", + "title": "Core Item" + }, + { + "rel": "item", + "href": "./extended-item.json", + "type": "application/geo+json", + "title": "Extended Item" + }, + { + "rel": "self", + "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.1.0/examples/collection.json", + "type": "application/json" + } + ] +} diff --git a/tests/v1/data-files/examples/1.1.0/collectionless-item.json b/tests/v1/data-files/examples/1.1.0/collectionless-item.json new file mode 100644 index 000000000..3562d6652 --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/collectionless-item.json @@ -0,0 +1,143 @@ +{ + "stac_version": "1.1.0", + "stac_extensions": [ + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "CS3-20160503_132131_08", + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.2880486, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, + "properties": { + "title": "Full Item", + "description": "A sample STAC Item demonstrates an Item that does not have a collection, which is not recommended, but allowed by the spec.", + "datetime": null, + "start_datetime": "2016-05-03T13:22:30Z", + "end_datetime": "2016-05-03T13:27:30Z", + "created": "2016-05-04T00:00:01Z", + "updated": "2017-01-01T00:30:55Z", + "license": "other", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.it" + } + ], + "platform": "cool_sat2", + "instruments": [ + "cool_sensor_v1" + ], + "view:sun_elevation": 33.4, + "gsd": 0.512, + "cs:type": "scene", + "cs:anomalous_pixels": 0.14, + "cs:earth_sun_distance": 1.014156, + "cs:sat_id": "CS3", + "cs:product_level": "LV1B" + }, + "links": [ + { + "rel": "root", + "href": "./catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "parent", + "href": "./catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/CS3-20160503_132130_04.html", + "title": "HTML representation of this STAC Item" + }, + { + "rel": "license", + "type": "text/html", + "href": "http://remotedata.io/license.html", + "title": "Data License for Remote Data, Inc." + } + ], + "assets": { + "analytic": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", + "title": "4-Band Analytic", + "bands": [ + { + "name": "band1" + }, + { + "name": "band1" + }, + { + "name": "band2" + }, + { + "name": "band3" + } + ] + }, + "thumbnail": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "udm": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/UDM.tif", + "title": "Unusable Data Mask" + }, + "json-metadata": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/S3-20160503_132130_04.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/v1/data-files/examples/1.1.0/core-item.json b/tests/v1/data-files/examples/1.1.0/core-item.json new file mode 100644 index 000000000..e151b1353 --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/core-item.json @@ -0,0 +1,125 @@ +{ + "stac_version": "1.1.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Core Item", + "description": "A sample STAC Item that includes examples of all common metadata", + "datetime": null, + "start_datetime": "2020-12-11T22:38:32.125Z", + "end_datetime": "2020-12-11T22:38:32.327Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat1", + "instruments": [ + "cool_sensor_v1" + ], + "constellation": "ion", + "mission": "collection 5624", + "gsd": 0.512 + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } +} diff --git a/tests/data-files/examples/1.1.0/extended-item.json b/tests/v1/data-files/examples/1.1.0/extended-item.json similarity index 100% rename from tests/data-files/examples/1.1.0/extended-item.json rename to tests/v1/data-files/examples/1.1.0/extended-item.json diff --git a/tests/v1/data-files/examples/1.1.0/extensions-collection/collection.json b/tests/v1/data-files/examples/1.1.0/extensions-collection/collection.json new file mode 100644 index 000000000..825f76acb --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/extensions-collection/collection.json @@ -0,0 +1,68 @@ +{ + "id": "extensions-collection", + "type": "Collection", + "stac_version": "1.1.0", + "description": "A heterogeneous collection containing deeper examples of various extensions", + "links": [ + { + "rel": "parent", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "root", + "href": "../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "item", + "href": "./proj-example/proj-example.json", + "title": "Proj extension example" + }, + { + "rel": "license", + "href": "https://remotedata.io/license.html", + "title": "Remote Data License Terms" + } + ], + "stac_extensions": [], + "title": "Collection of Extension Items", + "keywords": [ + "examples", + "sar", + "projection" + ], + "providers": [ + { + "name": "Remote Data, Inc.", + "roles": [ + "producer", + "licensor" + ], + "url": "https://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -56, + 180, + 83 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2009-05-20T02:40:01.042784Z", + "2018-11-03T23:59:55.112875Z" + ] + ] + } + }, + "license": "other" +} diff --git a/tests/v1/data-files/examples/1.1.0/extensions-collection/proj-example/proj-example.json b/tests/v1/data-files/examples/1.1.0/extensions-collection/proj-example/proj-example.json new file mode 100644 index 000000000..2884e3882 --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/extensions-collection/proj-example/proj-example.json @@ -0,0 +1,285 @@ +{ + "type": "Feature", + "stac_version": "1.1.0", + "id": "proj-example", + "properties": { + "datetime": "2018-10-01T01:08:32.033000Z", + "proj:code": "EPSG:32614", + "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "proj:projjson": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 14N", + "base_crs": { + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "conversion": { + "name": "UTM zone 14N", + "method": { + "name": "Transverse Mercator", + "id": { + "authority": "EPSG", + "code": 9807 + } + }, + "parameters": [ + { + "name": "Latitude of natural origin", + "value": 0, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8801 + } + }, + { + "name": "Longitude of natural origin", + "value": -99, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8802 + } + }, + { + "name": "Scale factor at natural origin", + "value": 0.9996, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8805 + } + }, + { + "name": "False easting", + "value": 500000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": 0, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + }, + "area": "World - N hemisphere - 102°W to 96°W - by country", + "bbox": { + "south_latitude": 0, + "west_longitude": -102, + "north_latitude": 84, + "east_longitude": -96 + }, + "id": { + "authority": "EPSG", + "code": 32614 + } + }, + "proj:geometry": { + "coordinates": [ + [ + [ + 169200, + 3712800 + ], + [ + 403200, + 3712800 + ], + [ + 403200, + 3951000 + ], + [ + 169200, + 3951000 + ], + [ + 169200, + 3712800 + ] + ] + ], + "type": "Polygon" + }, + "proj:bbox": [ + 169200, + 3712800, + 403200, + 3951000 + ], + "proj:centroid": { + "lat": 34.595302781575604, + "lon": -101.34448382627504 + }, + "proj:shape": [ + 8391, + 8311 + ], + "proj:transform": [ + 30, + 0, + 224985, + 0, + -30, + 6790215, + 0, + 0, + 1 + ] + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 152.52758, + 60.63437 + ], + [ + 149.1755, + 61.19016 + ], + [ + 148.13933, + 59.51584 + ], + [ + 151.33786, + 58.97792 + ], + [ + 152.52758, + 60.63437 + ] + ] + ] + }, + "links": [ + { + "rel": "root", + "href": "../../catalog.json", + "type": "application/json", + "title": "Example Catalog" + }, + { + "rel": "parent", + "href": "../collection.json", + "type": "application/json", + "title": "Collection of Extension Items" + }, + { + "rel": "collection", + "href": "../collection.json", + "type": "application/json", + "title": "Collection of Extension Items" + } + ], + "assets": { + "B1": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 1 (coastal)", + "bands": [ + { + "name": "B1", + "eo:common_name": "coastal", + "eo:center_wavelength": 0.44, + "eo:full_width_half_max": 0.02 + } + ] + }, + "B8": { + "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", + "type": "image/tiff; application=geotiff", + "title": "Band 8 (panchromatic)", + "bands": [ + { + "name": "B8", + "eo:center_wavelength": 0.59, + "eo:full_width_half_max": 0.18 + } + ], + "proj:shape": [ + 16781, + 16621 + ], + "proj:transform": [ + 15, + 0, + 224992.5, + 0, + -15, + 6790207.5, + 0, + 0, + 1 + ] + } + }, + "bbox": [ + 148.13933, + 59.51584, + 152.52758, + 60.63437 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v2.0.0/schema.json", + "https://stac-extensions.github.io/projection/v2.0.0/schema.json" + ], + "collection": "landsat-8-l1" +} diff --git a/tests/v1/data-files/examples/1.1.0/simple-item.json b/tests/v1/data-files/examples/1.1.0/simple-item.json new file mode 100644 index 000000000..449352edf --- /dev/null +++ b/tests/v1/data-files/examples/1.1.0/simple-item.json @@ -0,0 +1,81 @@ +{ + "stac_version": "1.1.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "datetime": "2020-12-11T22:38:32.125000Z" + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + } + ], + "assets": { + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/jpeg", + "roles": [ + "thumbnail" + ] + } + } +} diff --git a/tests/data-files/examples/example-info.csv b/tests/v1/data-files/examples/example-info.csv similarity index 100% rename from tests/data-files/examples/example-info.csv rename to tests/v1/data-files/examples/example-info.csv diff --git a/tests/data-files/examples/hand-0.8.1/010100/010100.json b/tests/v1/data-files/examples/hand-0.8.1/010100/010100.json similarity index 100% rename from tests/data-files/examples/hand-0.8.1/010100/010100.json rename to tests/v1/data-files/examples/hand-0.8.1/010100/010100.json diff --git a/tests/data-files/examples/hand-0.8.1/collection.json b/tests/v1/data-files/examples/hand-0.8.1/collection.json similarity index 100% rename from tests/data-files/examples/hand-0.8.1/collection.json rename to tests/v1/data-files/examples/hand-0.8.1/collection.json diff --git a/tests/data-files/examples/hand-0.9.0/010100/010100.json b/tests/v1/data-files/examples/hand-0.9.0/010100/010100.json similarity index 100% rename from tests/data-files/examples/hand-0.9.0/010100/010100.json rename to tests/v1/data-files/examples/hand-0.9.0/010100/010100.json diff --git a/tests/data-files/examples/hand-0.9.0/collection.json b/tests/v1/data-files/examples/hand-0.9.0/collection.json similarity index 100% rename from tests/data-files/examples/hand-0.9.0/collection.json rename to tests/v1/data-files/examples/hand-0.9.0/collection.json diff --git a/tests/data-files/file/catalog.json b/tests/v1/data-files/file/catalog.json similarity index 100% rename from tests/data-files/file/catalog.json rename to tests/v1/data-files/file/catalog.json diff --git a/tests/data-files/file/collection.json b/tests/v1/data-files/file/collection.json similarity index 100% rename from tests/data-files/file/collection.json rename to tests/v1/data-files/file/collection.json diff --git a/tests/data-files/file/item.json b/tests/v1/data-files/file/item.json similarity index 100% rename from tests/data-files/file/item.json rename to tests/v1/data-files/file/item.json diff --git a/tests/data-files/geojson/sample.geojson b/tests/v1/data-files/geojson/sample.geojson similarity index 100% rename from tests/data-files/geojson/sample.geojson rename to tests/v1/data-files/geojson/sample.geojson diff --git a/tests/data-files/get_examples.py b/tests/v1/data-files/get_examples.py similarity index 100% rename from tests/data-files/get_examples.py rename to tests/v1/data-files/get_examples.py diff --git a/tests/data-files/grid/example-landsat.json b/tests/v1/data-files/grid/example-landsat.json similarity index 100% rename from tests/data-files/grid/example-landsat.json rename to tests/v1/data-files/grid/example-landsat.json diff --git a/tests/data-files/grid/example-sentinel2.json b/tests/v1/data-files/grid/example-sentinel2.json similarity index 100% rename from tests/data-files/grid/example-sentinel2.json rename to tests/v1/data-files/grid/example-sentinel2.json diff --git a/tests/data-files/invalid/shared-id/catalog.json b/tests/v1/data-files/invalid/shared-id/catalog.json similarity index 100% rename from tests/data-files/invalid/shared-id/catalog.json rename to tests/v1/data-files/invalid/shared-id/catalog.json diff --git a/tests/data-files/invalid/shared-id/test/collection.json b/tests/v1/data-files/invalid/shared-id/test/collection.json similarity index 100% rename from tests/data-files/invalid/shared-id/test/collection.json rename to tests/v1/data-files/invalid/shared-id/test/collection.json diff --git a/tests/data-files/invalid/shared-id/test/test-item-1/test-item-1.json b/tests/v1/data-files/invalid/shared-id/test/test-item-1/test-item-1.json similarity index 100% rename from tests/data-files/invalid/shared-id/test/test-item-1/test-item-1.json rename to tests/v1/data-files/invalid/shared-id/test/test-item-1/test-item-1.json diff --git a/tests/data-files/item-assets/example-landsat8.json b/tests/v1/data-files/item-assets/example-landsat8.json similarity index 100% rename from tests/data-files/item-assets/example-landsat8.json rename to tests/v1/data-files/item-assets/example-landsat8.json diff --git a/tests/data-files/item-collection/sample-item-collection.json b/tests/v1/data-files/item-collection/sample-item-collection.json similarity index 100% rename from tests/data-files/item-collection/sample-item-collection.json rename to tests/v1/data-files/item-collection/sample-item-collection.json diff --git a/tests/data-files/item/sample-item-asset-properties.json b/tests/v1/data-files/item/sample-item-asset-properties.json similarity index 100% rename from tests/data-files/item/sample-item-asset-properties.json rename to tests/v1/data-files/item/sample-item-asset-properties.json diff --git a/tests/data-files/item/sample-item-with-relative-extension-path.json b/tests/v1/data-files/item/sample-item-with-relative-extension-path.json similarity index 100% rename from tests/data-files/item/sample-item-with-relative-extension-path.json rename to tests/v1/data-files/item/sample-item-with-relative-extension-path.json diff --git a/tests/data-files/item/sample-item.json b/tests/v1/data-files/item/sample-item.json similarity index 100% rename from tests/data-files/item/sample-item.json rename to tests/v1/data-files/item/sample-item.json diff --git a/tests/data-files/mgrs/item.json b/tests/v1/data-files/mgrs/item.json similarity index 100% rename from tests/data-files/mgrs/item.json rename to tests/v1/data-files/mgrs/item.json diff --git a/tests/data-files/mlm/collection.json b/tests/v1/data-files/mlm/collection.json similarity index 100% rename from tests/data-files/mlm/collection.json rename to tests/v1/data-files/mlm/collection.json diff --git a/tests/data-files/mlm/item_basic.json b/tests/v1/data-files/mlm/item_basic.json similarity index 100% rename from tests/data-files/mlm/item_basic.json rename to tests/v1/data-files/mlm/item_basic.json diff --git a/tests/data-files/pointcloud/example-laz-no-statistics.json b/tests/v1/data-files/pointcloud/example-laz-no-statistics.json similarity index 100% rename from tests/data-files/pointcloud/example-laz-no-statistics.json rename to tests/v1/data-files/pointcloud/example-laz-no-statistics.json diff --git a/tests/data-files/pointcloud/example-laz.json b/tests/v1/data-files/pointcloud/example-laz.json similarity index 100% rename from tests/data-files/pointcloud/example-laz.json rename to tests/v1/data-files/pointcloud/example-laz.json diff --git a/tests/data-files/projection/another-1.1.json b/tests/v1/data-files/projection/another-1.1.json similarity index 100% rename from tests/data-files/projection/another-1.1.json rename to tests/v1/data-files/projection/another-1.1.json diff --git a/tests/data-files/projection/collection-with-summaries.json b/tests/v1/data-files/projection/collection-with-summaries.json similarity index 100% rename from tests/data-files/projection/collection-with-summaries.json rename to tests/v1/data-files/projection/collection-with-summaries.json diff --git a/tests/data-files/projection/example-landsat8.json b/tests/v1/data-files/projection/example-landsat8.json similarity index 100% rename from tests/data-files/projection/example-landsat8.json rename to tests/v1/data-files/projection/example-landsat8.json diff --git a/tests/data-files/projection/example-with-version-1.1.json b/tests/v1/data-files/projection/example-with-version-1.1.json similarity index 100% rename from tests/data-files/projection/example-with-version-1.1.json rename to tests/v1/data-files/projection/example-with-version-1.1.json diff --git a/tests/data-files/projection/example-with-version-1.2.json b/tests/v1/data-files/projection/example-with-version-1.2.json similarity index 100% rename from tests/data-files/projection/example-with-version-1.2.json rename to tests/v1/data-files/projection/example-with-version-1.2.json diff --git a/tests/data-files/projection/optional-epsg.json b/tests/v1/data-files/projection/optional-epsg.json similarity index 100% rename from tests/data-files/projection/optional-epsg.json rename to tests/v1/data-files/projection/optional-epsg.json diff --git a/tests/data-files/raster/gdalinfo.json b/tests/v1/data-files/raster/gdalinfo.json similarity index 100% rename from tests/data-files/raster/gdalinfo.json rename to tests/v1/data-files/raster/gdalinfo.json diff --git a/tests/data-files/raster/landsat-collection-example.json b/tests/v1/data-files/raster/landsat-collection-example.json similarity index 100% rename from tests/data-files/raster/landsat-collection-example.json rename to tests/v1/data-files/raster/landsat-collection-example.json diff --git a/tests/data-files/raster/raster-planet-example.json b/tests/v1/data-files/raster/raster-planet-example.json similarity index 100% rename from tests/data-files/raster/raster-planet-example.json rename to tests/v1/data-files/raster/raster-planet-example.json diff --git a/tests/data-files/raster/raster-sentinel2-example.json b/tests/v1/data-files/raster/raster-sentinel2-example.json similarity index 100% rename from tests/data-files/raster/raster-sentinel2-example.json rename to tests/v1/data-files/raster/raster-sentinel2-example.json diff --git a/tests/data-files/render/collection.json b/tests/v1/data-files/render/collection.json similarity index 100% rename from tests/data-files/render/collection.json rename to tests/v1/data-files/render/collection.json diff --git a/tests/data-files/render/item.json b/tests/v1/data-files/render/item.json similarity index 100% rename from tests/data-files/render/item.json rename to tests/v1/data-files/render/item.json diff --git a/tests/data-files/sar/sentinel-1.json b/tests/v1/data-files/sar/sentinel-1.json similarity index 100% rename from tests/data-files/sar/sentinel-1.json rename to tests/v1/data-files/sar/sentinel-1.json diff --git a/tests/data-files/sat/sentinel-1.json b/tests/v1/data-files/sat/sentinel-1.json similarity index 100% rename from tests/data-files/sat/sentinel-1.json rename to tests/v1/data-files/sat/sentinel-1.json diff --git a/tests/data-files/schemas/v1.0.0-projection.json b/tests/v1/data-files/schemas/v1.0.0-projection.json similarity index 100% rename from tests/data-files/schemas/v1.0.0-projection.json rename to tests/v1/data-files/schemas/v1.0.0-projection.json diff --git a/tests/data-files/scientific/collection.json b/tests/v1/data-files/scientific/collection.json similarity index 100% rename from tests/data-files/scientific/collection.json rename to tests/v1/data-files/scientific/collection.json diff --git a/tests/data-files/scientific/item.json b/tests/v1/data-files/scientific/item.json similarity index 100% rename from tests/data-files/scientific/item.json rename to tests/v1/data-files/scientific/item.json diff --git a/tests/data-files/storage/collection-naip.json b/tests/v1/data-files/storage/collection-naip.json similarity index 100% rename from tests/data-files/storage/collection-naip.json rename to tests/v1/data-files/storage/collection-naip.json diff --git a/tests/data-files/storage/item-naip.json b/tests/v1/data-files/storage/item-naip.json similarity index 100% rename from tests/data-files/storage/item-naip.json rename to tests/v1/data-files/storage/item-naip.json diff --git a/tests/data-files/summaries/fields_no_bands.json b/tests/v1/data-files/summaries/fields_no_bands.json similarity index 100% rename from tests/data-files/summaries/fields_no_bands.json rename to tests/v1/data-files/summaries/fields_no_bands.json diff --git a/tests/data-files/table/README.md b/tests/v1/data-files/table/README.md similarity index 100% rename from tests/data-files/table/README.md rename to tests/v1/data-files/table/README.md diff --git a/tests/data-files/table/collection-2.json b/tests/v1/data-files/table/collection-2.json similarity index 100% rename from tests/data-files/table/collection-2.json rename to tests/v1/data-files/table/collection-2.json diff --git a/tests/data-files/table/collection.json b/tests/v1/data-files/table/collection.json similarity index 100% rename from tests/data-files/table/collection.json rename to tests/v1/data-files/table/collection.json diff --git a/tests/data-files/table/item.json b/tests/v1/data-files/table/item.json similarity index 100% rename from tests/data-files/table/item.json rename to tests/v1/data-files/table/item.json diff --git a/tests/data-files/table/table-collection.json b/tests/v1/data-files/table/table-collection.json similarity index 100% rename from tests/data-files/table/table-collection.json rename to tests/v1/data-files/table/table-collection.json diff --git a/tests/data-files/timestamps/example-landsat8.json b/tests/v1/data-files/timestamps/example-landsat8.json similarity index 100% rename from tests/data-files/timestamps/example-landsat8.json rename to tests/v1/data-files/timestamps/example-landsat8.json diff --git a/tests/data-files/version/collection.json b/tests/v1/data-files/version/collection.json similarity index 100% rename from tests/data-files/version/collection.json rename to tests/v1/data-files/version/collection.json diff --git a/tests/data-files/version/item.json b/tests/v1/data-files/version/item.json similarity index 100% rename from tests/data-files/version/item.json rename to tests/v1/data-files/version/item.json diff --git a/tests/data-files/view/collection-with-summaries.json b/tests/v1/data-files/view/collection-with-summaries.json similarity index 100% rename from tests/data-files/view/collection-with-summaries.json rename to tests/v1/data-files/view/collection-with-summaries.json diff --git a/tests/data-files/view/example-landsat8.json b/tests/v1/data-files/view/example-landsat8.json similarity index 100% rename from tests/data-files/view/example-landsat8.json rename to tests/v1/data-files/view/example-landsat8.json diff --git a/tests/data-files/windows_hrefs/catalog.json b/tests/v1/data-files/windows_hrefs/catalog.json similarity index 100% rename from tests/data-files/windows_hrefs/catalog.json rename to tests/v1/data-files/windows_hrefs/catalog.json diff --git a/tests/data-files/windows_hrefs/test-collection/collection.json b/tests/v1/data-files/windows_hrefs/test-collection/collection.json similarity index 100% rename from tests/data-files/windows_hrefs/test-collection/collection.json rename to tests/v1/data-files/windows_hrefs/test-collection/collection.json diff --git a/tests/data-files/windows_hrefs/test-collection/test-item/test-asset.txt b/tests/v1/data-files/windows_hrefs/test-collection/test-item/test-asset.txt similarity index 100% rename from tests/data-files/windows_hrefs/test-collection/test-item/test-asset.txt rename to tests/v1/data-files/windows_hrefs/test-collection/test-item/test-asset.txt diff --git a/tests/data-files/windows_hrefs/test-collection/test-item/test-item.json b/tests/v1/data-files/windows_hrefs/test-collection/test-item/test-item.json similarity index 100% rename from tests/data-files/windows_hrefs/test-collection/test-item/test-item.json rename to tests/v1/data-files/windows_hrefs/test-collection/test-item/test-item.json diff --git a/tests/data-files/xarray-assets/collection.json b/tests/v1/data-files/xarray-assets/collection.json similarity index 100% rename from tests/data-files/xarray-assets/collection.json rename to tests/v1/data-files/xarray-assets/collection.json diff --git a/tests/data-files/xarray-assets/item.json b/tests/v1/data-files/xarray-assets/item.json similarity index 100% rename from tests/data-files/xarray-assets/item.json rename to tests/v1/data-files/xarray-assets/item.json diff --git a/tests/extensions/__init__.py b/tests/v1/extensions/__init__.py similarity index 100% rename from tests/extensions/__init__.py rename to tests/v1/extensions/__init__.py diff --git a/tests/extensions/cassettes/test_classification/test_apply_bitfields.yaml b/tests/v1/extensions/cassettes/test_classification/test_apply_bitfields.yaml similarity index 100% rename from tests/extensions/cassettes/test_classification/test_apply_bitfields.yaml rename to tests/v1/extensions/cassettes/test_classification/test_apply_bitfields.yaml diff --git a/tests/extensions/cassettes/test_classification/test_apply_classes.yaml b/tests/v1/extensions/cassettes/test_classification/test_apply_classes.yaml similarity index 100% rename from tests/extensions/cassettes/test_classification/test_apply_classes.yaml rename to tests/v1/extensions/cassettes/test_classification/test_apply_classes.yaml diff --git a/tests/extensions/cassettes/test_classification/test_validate_classification.yaml b/tests/v1/extensions/cassettes/test_classification/test_validate_classification.yaml similarity index 100% rename from tests/extensions/cassettes/test_classification/test_validate_classification.yaml rename to tests/v1/extensions/cassettes/test_classification/test_validate_classification.yaml diff --git a/tests/extensions/cassettes/test_datacube/test_set_dimensions.yaml b/tests/v1/extensions/cassettes/test_datacube/test_set_dimensions.yaml similarity index 100% rename from tests/extensions/cassettes/test_datacube/test_set_dimensions.yaml rename to tests/v1/extensions/cassettes/test_datacube/test_set_dimensions.yaml diff --git a/tests/extensions/cassettes/test_datacube/test_set_variables.yaml b/tests/v1/extensions/cassettes/test_datacube/test_set_variables.yaml similarity index 100% rename from tests/extensions/cassettes/test_datacube/test_set_variables.yaml rename to tests/v1/extensions/cassettes/test_datacube/test_set_variables.yaml diff --git a/tests/extensions/cassettes/test_datacube/test_validate.yaml b/tests/v1/extensions/cassettes/test_datacube/test_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_datacube/test_validate.yaml rename to tests/v1/extensions/cassettes/test_datacube/test_validate.yaml diff --git a/tests/extensions/cassettes/test_eo/test_asset_bands.yaml b/tests/v1/extensions/cassettes/test_eo/test_asset_bands.yaml similarity index 100% rename from tests/extensions/cassettes/test_eo/test_asset_bands.yaml rename to tests/v1/extensions/cassettes/test_eo/test_asset_bands.yaml diff --git a/tests/extensions/cassettes/test_eo/test_bands.yaml b/tests/v1/extensions/cassettes/test_eo/test_bands.yaml similarity index 100% rename from tests/extensions/cassettes/test_eo/test_bands.yaml rename to tests/v1/extensions/cassettes/test_eo/test_bands.yaml diff --git a/tests/extensions/cassettes/test_eo/test_cloud_cover.yaml b/tests/v1/extensions/cassettes/test_eo/test_cloud_cover.yaml similarity index 100% rename from tests/extensions/cassettes/test_eo/test_cloud_cover.yaml rename to tests/v1/extensions/cassettes/test_eo/test_cloud_cover.yaml diff --git a/tests/extensions/cassettes/test_eo/test_set_field[cloud_cover-7.8].yaml b/tests/v1/extensions/cassettes/test_eo/test_set_field[cloud_cover-7.8].yaml similarity index 100% rename from tests/extensions/cassettes/test_eo/test_set_field[cloud_cover-7.8].yaml rename to tests/v1/extensions/cassettes/test_eo/test_set_field[cloud_cover-7.8].yaml diff --git a/tests/extensions/cassettes/test_eo/test_set_field[snow_cover-99].yaml b/tests/v1/extensions/cassettes/test_eo/test_set_field[snow_cover-99].yaml similarity index 100% rename from tests/extensions/cassettes/test_eo/test_set_field[snow_cover-99].yaml rename to tests/v1/extensions/cassettes/test_eo/test_set_field[snow_cover-99].yaml diff --git a/tests/extensions/cassettes/test_eo/test_validate_eo.yaml b/tests/v1/extensions/cassettes/test_eo/test_validate_eo.yaml similarity index 100% rename from tests/extensions/cassettes/test_eo/test_validate_eo.yaml rename to tests/v1/extensions/cassettes/test_eo/test_validate_eo.yaml diff --git a/tests/extensions/cassettes/test_file/test_migrate_from_v1_0_0.yaml b/tests/v1/extensions/cassettes/test_file/test_migrate_from_v1_0_0.yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_migrate_from_v1_0_0.yaml rename to tests/v1/extensions/cassettes/test_file/test_migrate_from_v1_0_0.yaml diff --git a/tests/extensions/cassettes/test_file/test_migrate_from_v2_0_0.yaml b/tests/v1/extensions/cassettes/test_file/test_migrate_from_v2_0_0.yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_migrate_from_v2_0_0.yaml rename to tests/v1/extensions/cassettes/test_file/test_migrate_from_v2_0_0.yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_asset[calibrations-local_path-different-file.xml].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[calibrations-local_path-different-file.xml].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_asset[calibrations-local_path-different-file.xml].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[calibrations-local_path-different-file.xml].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_asset[measurement-header_size-8192].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[measurement-header_size-8192].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_asset[measurement-header_size-8192].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[measurement-header_size-8192].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-byte_order-little-endian].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-byte_order-little-endian].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-byte_order-little-endian].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-byte_order-little-endian].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-checksum-90e40210163700a8a6501eccd00b6d3b44ddaed0].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-checksum-90e40210163700a8a6501eccd00b6d3b44ddaed0].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-checksum-90e40210163700a8a6501eccd00b6d3b44ddaed0].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-checksum-90e40210163700a8a6501eccd00b6d3b44ddaed0].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-size-1].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-size-1].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-size-1].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_asset[thumbnail-size-1].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_link[about-byte_order-big-endian].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-byte_order-big-endian].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_link[about-byte_order-big-endian].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-byte_order-big-endian].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_link[about-checksum-90e40210163700a8a6501eccd00b6d3b44ddaedb].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-checksum-90e40210163700a8a6501eccd00b6d3b44ddaedb].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_link[about-checksum-90e40210163700a8a6501eccd00b6d3b44ddaedb].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-checksum-90e40210163700a8a6501eccd00b6d3b44ddaedb].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_link[about-header_size-4092].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-header_size-4092].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_link[about-header_size-4092].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-header_size-4092].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_link[about-local_path-a-path].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-local_path-a-path].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_link[about-local_path-a-path].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-local_path-a-path].yaml diff --git a/tests/extensions/cassettes/test_file/test_set_field_on_link[about-size-129302].yaml b/tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-size-129302].yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_set_field_on_link[about-size-129302].yaml rename to tests/v1/extensions/cassettes/test_file/test_set_field_on_link[about-size-129302].yaml diff --git a/tests/extensions/cassettes/test_file/test_validate_catalog.yaml b/tests/v1/extensions/cassettes/test_file/test_validate_catalog.yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_validate_catalog.yaml rename to tests/v1/extensions/cassettes/test_file/test_validate_catalog.yaml diff --git a/tests/extensions/cassettes/test_file/test_validate_collection.yaml b/tests/v1/extensions/cassettes/test_file/test_validate_collection.yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_validate_collection.yaml rename to tests/v1/extensions/cassettes/test_file/test_validate_collection.yaml diff --git a/tests/extensions/cassettes/test_file/test_validate_item.yaml b/tests/v1/extensions/cassettes/test_file/test_validate_item.yaml similarity index 100% rename from tests/extensions/cassettes/test_file/test_validate_item.yaml rename to tests/v1/extensions/cassettes/test_file/test_validate_item.yaml diff --git a/tests/extensions/cassettes/test_grid/test_attributes.yaml b/tests/v1/extensions/cassettes/test_grid/test_attributes.yaml similarity index 100% rename from tests/extensions/cassettes/test_grid/test_attributes.yaml rename to tests/v1/extensions/cassettes/test_grid/test_attributes.yaml diff --git a/tests/extensions/cassettes/test_grid/test_modify.yaml b/tests/v1/extensions/cassettes/test_grid/test_modify.yaml similarity index 100% rename from tests/extensions/cassettes/test_grid/test_modify.yaml rename to tests/v1/extensions/cassettes/test_grid/test_modify.yaml diff --git a/tests/extensions/cassettes/test_mgrs/test_set_field[grid_square-ZA].yaml b/tests/v1/extensions/cassettes/test_mgrs/test_set_field[grid_square-ZA].yaml similarity index 100% rename from tests/extensions/cassettes/test_mgrs/test_set_field[grid_square-ZA].yaml rename to tests/v1/extensions/cassettes/test_mgrs/test_set_field[grid_square-ZA].yaml diff --git a/tests/extensions/cassettes/test_mgrs/test_set_field[latitude_band-C].yaml b/tests/v1/extensions/cassettes/test_mgrs/test_set_field[latitude_band-C].yaml similarity index 100% rename from tests/extensions/cassettes/test_mgrs/test_set_field[latitude_band-C].yaml rename to tests/v1/extensions/cassettes/test_mgrs/test_set_field[latitude_band-C].yaml diff --git a/tests/extensions/cassettes/test_mgrs/test_set_field[utm_zone-59].yaml b/tests/v1/extensions/cassettes/test_mgrs/test_set_field[utm_zone-59].yaml similarity index 100% rename from tests/extensions/cassettes/test_mgrs/test_set_field[utm_zone-59].yaml rename to tests/v1/extensions/cassettes/test_mgrs/test_set_field[utm_zone-59].yaml diff --git a/tests/extensions/cassettes/test_mgrs/test_validate.yaml b/tests/v1/extensions/cassettes/test_mgrs/test_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_mgrs/test_validate.yaml rename to tests/v1/extensions/cassettes/test_mgrs/test_validate.yaml diff --git a/tests/extensions/cassettes/test_mlm/test_apply.yaml b/tests/v1/extensions/cassettes/test_mlm/test_apply.yaml similarity index 100% rename from tests/extensions/cassettes/test_mlm/test_apply.yaml rename to tests/v1/extensions/cassettes/test_mlm/test_apply.yaml diff --git a/tests/extensions/cassettes/test_mlm/test_validate_mlm.yaml b/tests/v1/extensions/cassettes/test_mlm/test_validate_mlm.yaml similarity index 100% rename from tests/extensions/cassettes/test_mlm/test_validate_mlm.yaml rename to tests/v1/extensions/cassettes/test_mlm/test_validate_mlm.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_count.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_count.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_count.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_count.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_density.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_density.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_density.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_density.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_encoding.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_encoding.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_encoding.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_encoding.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_schemas.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_schemas.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_schemas.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_schemas.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_statistics.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_statistics.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_statistics.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_statistics.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_type.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_type.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_type.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_type.yaml diff --git a/tests/extensions/cassettes/test_pointcloud/test_validate_pointcloud.yaml b/tests/v1/extensions/cassettes/test_pointcloud/test_validate_pointcloud.yaml similarity index 100% rename from tests/extensions/cassettes/test_pointcloud/test_validate_pointcloud.yaml rename to tests/v1/extensions/cassettes/test_pointcloud/test_validate_pointcloud.yaml diff --git a/tests/extensions/cassettes/test_projection/test_bbox.yaml b/tests/v1/extensions/cassettes/test_projection/test_bbox.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_bbox.yaml rename to tests/v1/extensions/cassettes/test_projection/test_bbox.yaml diff --git a/tests/extensions/cassettes/test_projection/test_centroid.yaml b/tests/v1/extensions/cassettes/test_projection/test_centroid.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_centroid.yaml rename to tests/v1/extensions/cassettes/test_projection/test_centroid.yaml diff --git a/tests/extensions/cassettes/test_projection/test_epsg.yaml b/tests/v1/extensions/cassettes/test_projection/test_epsg.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_epsg.yaml rename to tests/v1/extensions/cassettes/test_projection/test_epsg.yaml diff --git a/tests/extensions/cassettes/test_projection/test_geometry.yaml b/tests/v1/extensions/cassettes/test_projection/test_geometry.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_geometry.yaml rename to tests/v1/extensions/cassettes/test_projection/test_geometry.yaml diff --git a/tests/extensions/cassettes/test_projection/test_partial_apply.yaml b/tests/v1/extensions/cassettes/test_projection/test_partial_apply.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_partial_apply.yaml rename to tests/v1/extensions/cassettes/test_projection/test_partial_apply.yaml diff --git a/tests/extensions/cassettes/test_projection/test_projjson.yaml b/tests/v1/extensions/cassettes/test_projection/test_projjson.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_projjson.yaml rename to tests/v1/extensions/cassettes/test_projection/test_projjson.yaml diff --git a/tests/extensions/cassettes/test_projection/test_shape.yaml b/tests/v1/extensions/cassettes/test_projection/test_shape.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_shape.yaml rename to tests/v1/extensions/cassettes/test_projection/test_shape.yaml diff --git a/tests/extensions/cassettes/test_projection/test_transform.yaml b/tests/v1/extensions/cassettes/test_projection/test_transform.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_transform.yaml rename to tests/v1/extensions/cassettes/test_projection/test_transform.yaml diff --git a/tests/extensions/cassettes/test_projection/test_validate_proj.yaml b/tests/v1/extensions/cassettes/test_projection/test_validate_proj.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_validate_proj.yaml rename to tests/v1/extensions/cassettes/test_projection/test_validate_proj.yaml diff --git a/tests/extensions/cassettes/test_projection/test_wkt2.yaml b/tests/v1/extensions/cassettes/test_projection/test_wkt2.yaml similarity index 100% rename from tests/extensions/cassettes/test_projection/test_wkt2.yaml rename to tests/v1/extensions/cassettes/test_projection/test_wkt2.yaml diff --git a/tests/extensions/cassettes/test_raster/test_asset_bands.yaml b/tests/v1/extensions/cassettes/test_raster/test_asset_bands.yaml similarity index 100% rename from tests/extensions/cassettes/test_raster/test_asset_bands.yaml rename to tests/v1/extensions/cassettes/test_raster/test_asset_bands.yaml diff --git a/tests/extensions/cassettes/test_raster/test_validate_raster.yaml b/tests/v1/extensions/cassettes/test_raster/test_validate_raster.yaml similarity index 100% rename from tests/extensions/cassettes/test_raster/test_validate_raster.yaml rename to tests/v1/extensions/cassettes/test_raster/test_validate_raster.yaml diff --git a/tests/extensions/cassettes/test_render/test_collection_validate.yaml b/tests/v1/extensions/cassettes/test_render/test_collection_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_render/test_collection_validate.yaml rename to tests/v1/extensions/cassettes/test_render/test_collection_validate.yaml diff --git a/tests/extensions/cassettes/test_render/test_item_validate.yaml b/tests/v1/extensions/cassettes/test_render/test_item_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_render/test_item_validate.yaml rename to tests/v1/extensions/cassettes/test_render/test_item_validate.yaml diff --git a/tests/extensions/cassettes/test_sar/test_all.yaml b/tests/v1/extensions/cassettes/test_sar/test_all.yaml similarity index 100% rename from tests/extensions/cassettes/test_sar/test_all.yaml rename to tests/v1/extensions/cassettes/test_sar/test_all.yaml diff --git a/tests/extensions/cassettes/test_sar/test_required.yaml b/tests/v1/extensions/cassettes/test_sar/test_required.yaml similarity index 100% rename from tests/extensions/cassettes/test_sar/test_required.yaml rename to tests/v1/extensions/cassettes/test_sar/test_required.yaml diff --git a/tests/extensions/cassettes/test_sat/test_absolute_orbit.yaml b/tests/v1/extensions/cassettes/test_sat/test_absolute_orbit.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_absolute_orbit.yaml rename to tests/v1/extensions/cassettes/test_sat/test_absolute_orbit.yaml diff --git a/tests/extensions/cassettes/test_sat/test_anx_datetime.yaml b/tests/v1/extensions/cassettes/test_sat/test_anx_datetime.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_anx_datetime.yaml rename to tests/v1/extensions/cassettes/test_sat/test_anx_datetime.yaml diff --git a/tests/extensions/cassettes/test_sat/test_both.yaml b/tests/v1/extensions/cassettes/test_sat/test_both.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_both.yaml rename to tests/v1/extensions/cassettes/test_sat/test_both.yaml diff --git a/tests/extensions/cassettes/test_sat/test_clear_orbit_state.yaml b/tests/v1/extensions/cassettes/test_sat/test_clear_orbit_state.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_clear_orbit_state.yaml rename to tests/v1/extensions/cassettes/test_sat/test_clear_orbit_state.yaml diff --git a/tests/extensions/cassettes/test_sat/test_clear_relative_orbit.yaml b/tests/v1/extensions/cassettes/test_sat/test_clear_relative_orbit.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_clear_relative_orbit.yaml rename to tests/v1/extensions/cassettes/test_sat/test_clear_relative_orbit.yaml diff --git a/tests/extensions/cassettes/test_sat/test_modify.yaml b/tests/v1/extensions/cassettes/test_sat/test_modify.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_modify.yaml rename to tests/v1/extensions/cassettes/test_sat/test_modify.yaml diff --git a/tests/extensions/cassettes/test_sat/test_no_args_fails.yaml b/tests/v1/extensions/cassettes/test_sat/test_no_args_fails.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_no_args_fails.yaml rename to tests/v1/extensions/cassettes/test_sat/test_no_args_fails.yaml diff --git a/tests/extensions/cassettes/test_sat/test_orbit_state.yaml b/tests/v1/extensions/cassettes/test_sat/test_orbit_state.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_orbit_state.yaml rename to tests/v1/extensions/cassettes/test_sat/test_orbit_state.yaml diff --git a/tests/extensions/cassettes/test_sat/test_platform_international_designator.yaml b/tests/v1/extensions/cassettes/test_sat/test_platform_international_designator.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_platform_international_designator.yaml rename to tests/v1/extensions/cassettes/test_sat/test_platform_international_designator.yaml diff --git a/tests/extensions/cassettes/test_sat/test_relative_orbit.yaml b/tests/v1/extensions/cassettes/test_sat/test_relative_orbit.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_relative_orbit.yaml rename to tests/v1/extensions/cassettes/test_sat/test_relative_orbit.yaml diff --git a/tests/extensions/cassettes/test_sat/test_relative_orbit_no_negative.yaml b/tests/v1/extensions/cassettes/test_sat/test_relative_orbit_no_negative.yaml similarity index 100% rename from tests/extensions/cassettes/test_sat/test_relative_orbit_no_negative.yaml rename to tests/v1/extensions/cassettes/test_sat/test_relative_orbit_no_negative.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_citation.yaml b/tests/v1/extensions/cassettes/test_scientific/test_citation.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_citation.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_citation.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_citation.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_citation.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_citation.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_citation.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_doi.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_doi.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_doi.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_doi.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_publications.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_publications.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_publications.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_publications.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_publications_one.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_publications_one.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_publications_one.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_publications_one.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_remove_all_publications_one.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_remove_all_publications_one.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_remove_all_publications_one.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_remove_all_publications_one.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_none.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_none.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_none.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_none.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_some.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_some.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_some.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_remove_all_publications_with_some.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_remove_publication_forward.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_remove_publication_forward.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_remove_publication_forward.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_remove_publication_forward.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_remove_publication_one.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_remove_publication_one.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_remove_publication_one.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_remove_publication_one.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_collection_remove_publication_reverse.yaml b/tests/v1/extensions/cassettes/test_scientific/test_collection_remove_publication_reverse.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_collection_remove_publication_reverse.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_collection_remove_publication_reverse.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_doi.yaml b/tests/v1/extensions/cassettes/test_scientific/test_doi.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_doi.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_doi.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_publications.yaml b/tests/v1/extensions/cassettes/test_scientific/test_publications.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_publications.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_publications.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_publications_one.yaml b/tests/v1/extensions/cassettes/test_scientific/test_publications_one.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_publications_one.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_publications_one.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_remove_all_publications_one.yaml b/tests/v1/extensions/cassettes/test_scientific/test_remove_all_publications_one.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_remove_all_publications_one.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_remove_all_publications_one.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_remove_all_publications_with_none.yaml b/tests/v1/extensions/cassettes/test_scientific/test_remove_all_publications_with_none.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_remove_all_publications_with_none.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_remove_all_publications_with_none.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_remove_all_publications_with_some.yaml b/tests/v1/extensions/cassettes/test_scientific/test_remove_all_publications_with_some.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_remove_all_publications_with_some.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_remove_all_publications_with_some.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_remove_publication_forward.yaml b/tests/v1/extensions/cassettes/test_scientific/test_remove_publication_forward.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_remove_publication_forward.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_remove_publication_forward.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_remove_publication_one.yaml b/tests/v1/extensions/cassettes/test_scientific/test_remove_publication_one.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_remove_publication_one.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_remove_publication_one.yaml diff --git a/tests/extensions/cassettes/test_scientific/test_remove_publication_reverse.yaml b/tests/v1/extensions/cassettes/test_scientific/test_remove_publication_reverse.yaml similarity index 100% rename from tests/extensions/cassettes/test_scientific/test_remove_publication_reverse.yaml rename to tests/v1/extensions/cassettes/test_scientific/test_remove_publication_reverse.yaml diff --git a/tests/extensions/cassettes/test_storage/test_asset_platform.yaml b/tests/v1/extensions/cassettes/test_storage/test_asset_platform.yaml similarity index 100% rename from tests/extensions/cassettes/test_storage/test_asset_platform.yaml rename to tests/v1/extensions/cassettes/test_storage/test_asset_platform.yaml diff --git a/tests/extensions/cassettes/test_storage/test_asset_region.yaml b/tests/v1/extensions/cassettes/test_storage/test_asset_region.yaml similarity index 100% rename from tests/extensions/cassettes/test_storage/test_asset_region.yaml rename to tests/v1/extensions/cassettes/test_storage/test_asset_region.yaml diff --git a/tests/extensions/cassettes/test_storage/test_asset_requester_pays.yaml b/tests/v1/extensions/cassettes/test_storage/test_asset_requester_pays.yaml similarity index 100% rename from tests/extensions/cassettes/test_storage/test_asset_requester_pays.yaml rename to tests/v1/extensions/cassettes/test_storage/test_asset_requester_pays.yaml diff --git a/tests/extensions/cassettes/test_storage/test_asset_tier.yaml b/tests/v1/extensions/cassettes/test_storage/test_asset_tier.yaml similarity index 100% rename from tests/extensions/cassettes/test_storage/test_asset_tier.yaml rename to tests/v1/extensions/cassettes/test_storage/test_asset_tier.yaml diff --git a/tests/extensions/cassettes/test_storage/test_validate_storage.yaml b/tests/v1/extensions/cassettes/test_storage/test_validate_storage.yaml similarity index 100% rename from tests/extensions/cassettes/test_storage/test_validate_storage.yaml rename to tests/v1/extensions/cassettes/test_storage/test_validate_storage.yaml diff --git a/tests/extensions/cassettes/test_table/test_validate.yaml b/tests/v1/extensions/cassettes/test_table/test_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_table/test_validate.yaml rename to tests/v1/extensions/cassettes/test_table/test_validate.yaml diff --git a/tests/extensions/cassettes/test_timestamps/test_expires.yaml b/tests/v1/extensions/cassettes/test_timestamps/test_expires.yaml similarity index 100% rename from tests/extensions/cassettes/test_timestamps/test_expires.yaml rename to tests/v1/extensions/cassettes/test_timestamps/test_expires.yaml diff --git a/tests/extensions/cassettes/test_timestamps/test_published.yaml b/tests/v1/extensions/cassettes/test_timestamps/test_published.yaml similarity index 100% rename from tests/extensions/cassettes/test_timestamps/test_published.yaml rename to tests/v1/extensions/cassettes/test_timestamps/test_published.yaml diff --git a/tests/extensions/cassettes/test_timestamps/test_unpublished.yaml b/tests/v1/extensions/cassettes/test_timestamps/test_unpublished.yaml similarity index 100% rename from tests/extensions/cassettes/test_timestamps/test_unpublished.yaml rename to tests/v1/extensions/cassettes/test_timestamps/test_unpublished.yaml diff --git a/tests/extensions/cassettes/test_timestamps/test_validate_timestamps.yaml b/tests/v1/extensions/cassettes/test_timestamps/test_validate_timestamps.yaml similarity index 100% rename from tests/extensions/cassettes/test_timestamps/test_validate_timestamps.yaml rename to tests/v1/extensions/cassettes/test_timestamps/test_validate_timestamps.yaml diff --git a/tests/extensions/cassettes/test_version/test_add_deprecated_version.yaml b/tests/v1/extensions/cassettes/test_version/test_add_deprecated_version.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_add_deprecated_version.yaml rename to tests/v1/extensions/cassettes/test_version/test_add_deprecated_version.yaml diff --git a/tests/extensions/cassettes/test_version/test_add_not_deprecated_version.yaml b/tests/v1/extensions/cassettes/test_version/test_add_not_deprecated_version.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_add_not_deprecated_version.yaml rename to tests/v1/extensions/cassettes/test_version/test_add_not_deprecated_version.yaml diff --git a/tests/extensions/cassettes/test_version/test_add_version.yaml b/tests/v1/extensions/cassettes/test_version/test_add_version.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_add_version.yaml rename to tests/v1/extensions/cassettes/test_version/test_add_version.yaml diff --git a/tests/extensions/cassettes/test_version/test_all_links.yaml b/tests/v1/extensions/cassettes/test_version/test_all_links.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_all_links.yaml rename to tests/v1/extensions/cassettes/test_version/test_all_links.yaml diff --git a/tests/extensions/cassettes/test_version/test_assets.yaml b/tests/v1/extensions/cassettes/test_version/test_assets.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_assets.yaml rename to tests/v1/extensions/cassettes/test_version/test_assets.yaml diff --git a/tests/extensions/cassettes/test_version/test_catalog_add_version.yaml b/tests/v1/extensions/cassettes/test_version/test_catalog_add_version.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_catalog_add_version.yaml rename to tests/v1/extensions/cassettes/test_version/test_catalog_add_version.yaml diff --git a/tests/extensions/cassettes/test_version/test_catalog_validate_all.yaml b/tests/v1/extensions/cassettes/test_version/test_catalog_validate_all.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_catalog_validate_all.yaml rename to tests/v1/extensions/cassettes/test_version/test_catalog_validate_all.yaml diff --git a/tests/extensions/cassettes/test_version/test_collection_add_version.yaml b/tests/v1/extensions/cassettes/test_version/test_collection_add_version.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_collection_add_version.yaml rename to tests/v1/extensions/cassettes/test_version/test_collection_add_version.yaml diff --git a/tests/extensions/cassettes/test_version/test_collection_validate_all.yaml b/tests/v1/extensions/cassettes/test_version/test_collection_validate_all.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_collection_validate_all.yaml rename to tests/v1/extensions/cassettes/test_version/test_collection_validate_all.yaml diff --git a/tests/extensions/cassettes/test_version/test_latest.yaml b/tests/v1/extensions/cassettes/test_version/test_latest.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_latest.yaml rename to tests/v1/extensions/cassettes/test_version/test_latest.yaml diff --git a/tests/extensions/cassettes/test_version/test_optional_version.yaml b/tests/v1/extensions/cassettes/test_version/test_optional_version.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_optional_version.yaml rename to tests/v1/extensions/cassettes/test_version/test_optional_version.yaml diff --git a/tests/extensions/cassettes/test_version/test_predecessor.yaml b/tests/v1/extensions/cassettes/test_version/test_predecessor.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_predecessor.yaml rename to tests/v1/extensions/cassettes/test_version/test_predecessor.yaml diff --git a/tests/extensions/cassettes/test_version/test_successor.yaml b/tests/v1/extensions/cassettes/test_version/test_successor.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_successor.yaml rename to tests/v1/extensions/cassettes/test_version/test_successor.yaml diff --git a/tests/extensions/cassettes/test_version/test_version_in_properties.yaml b/tests/v1/extensions/cassettes/test_version/test_version_in_properties.yaml similarity index 100% rename from tests/extensions/cassettes/test_version/test_version_in_properties.yaml rename to tests/v1/extensions/cassettes/test_version/test_version_in_properties.yaml diff --git a/tests/extensions/cassettes/test_view/test_azimuth.yaml b/tests/v1/extensions/cassettes/test_view/test_azimuth.yaml similarity index 100% rename from tests/extensions/cassettes/test_view/test_azimuth.yaml rename to tests/v1/extensions/cassettes/test_view/test_azimuth.yaml diff --git a/tests/extensions/cassettes/test_view/test_incidence_angle.yaml b/tests/v1/extensions/cassettes/test_view/test_incidence_angle.yaml similarity index 100% rename from tests/extensions/cassettes/test_view/test_incidence_angle.yaml rename to tests/v1/extensions/cassettes/test_view/test_incidence_angle.yaml diff --git a/tests/extensions/cassettes/test_view/test_off_nadir.yaml b/tests/v1/extensions/cassettes/test_view/test_off_nadir.yaml similarity index 100% rename from tests/extensions/cassettes/test_view/test_off_nadir.yaml rename to tests/v1/extensions/cassettes/test_view/test_off_nadir.yaml diff --git a/tests/extensions/cassettes/test_view/test_sun_azimuth.yaml b/tests/v1/extensions/cassettes/test_view/test_sun_azimuth.yaml similarity index 100% rename from tests/extensions/cassettes/test_view/test_sun_azimuth.yaml rename to tests/v1/extensions/cassettes/test_view/test_sun_azimuth.yaml diff --git a/tests/extensions/cassettes/test_view/test_sun_elevation.yaml b/tests/v1/extensions/cassettes/test_view/test_sun_elevation.yaml similarity index 100% rename from tests/extensions/cassettes/test_view/test_sun_elevation.yaml rename to tests/v1/extensions/cassettes/test_view/test_sun_elevation.yaml diff --git a/tests/extensions/cassettes/test_view/test_validate_view.yaml b/tests/v1/extensions/cassettes/test_view/test_validate_view.yaml similarity index 100% rename from tests/extensions/cassettes/test_view/test_validate_view.yaml rename to tests/v1/extensions/cassettes/test_view/test_validate_view.yaml diff --git a/tests/extensions/cassettes/test_xarray_assets/test_collection_validate.yaml b/tests/v1/extensions/cassettes/test_xarray_assets/test_collection_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_xarray_assets/test_collection_validate.yaml rename to tests/v1/extensions/cassettes/test_xarray_assets/test_collection_validate.yaml diff --git a/tests/extensions/cassettes/test_xarray_assets/test_item_validate.yaml b/tests/v1/extensions/cassettes/test_xarray_assets/test_item_validate.yaml similarity index 100% rename from tests/extensions/cassettes/test_xarray_assets/test_item_validate.yaml rename to tests/v1/extensions/cassettes/test_xarray_assets/test_item_validate.yaml diff --git a/tests/extensions/cassettes/test_xarray_assets/test_set_field[open_kwargs-value1].yaml b/tests/v1/extensions/cassettes/test_xarray_assets/test_set_field[open_kwargs-value1].yaml similarity index 100% rename from tests/extensions/cassettes/test_xarray_assets/test_set_field[open_kwargs-value1].yaml rename to tests/v1/extensions/cassettes/test_xarray_assets/test_set_field[open_kwargs-value1].yaml diff --git a/tests/extensions/cassettes/test_xarray_assets/test_set_field[storage_options-value0].yaml b/tests/v1/extensions/cassettes/test_xarray_assets/test_set_field[storage_options-value0].yaml similarity index 100% rename from tests/extensions/cassettes/test_xarray_assets/test_set_field[storage_options-value0].yaml rename to tests/v1/extensions/cassettes/test_xarray_assets/test_set_field[storage_options-value0].yaml diff --git a/tests/extensions/test_classification.py b/tests/v1/extensions/test_classification.py similarity index 99% rename from tests/extensions/test_classification.py rename to tests/v1/extensions/test_classification.py index d74838d67..61530cf53 100644 --- a/tests/extensions/test_classification.py +++ b/tests/v1/extensions/test_classification.py @@ -19,7 +19,8 @@ ClassificationExtension, ) from pystac.extensions.raster import RasterBand, RasterExtension -from tests.utils import TestCases + +from ..utils import TestCases logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger() diff --git a/tests/extensions/test_custom.py b/tests/v1/extensions/test_custom.py similarity index 100% rename from tests/extensions/test_custom.py rename to tests/v1/extensions/test_custom.py diff --git a/tests/extensions/test_datacube.py b/tests/v1/extensions/test_datacube.py similarity index 99% rename from tests/extensions/test_datacube.py rename to tests/v1/extensions/test_datacube.py index 1393b6c85..66407b987 100644 --- a/tests/extensions/test_datacube.py +++ b/tests/v1/extensions/test_datacube.py @@ -9,7 +9,8 @@ import pystac.extensions.datacube as dc from pystac import Asset, Item from pystac.errors import ExtensionTypeError -from tests.conftest import get_data_file + +from ..conftest import get_data_file @pytest.fixture diff --git a/tests/extensions/test_eo.py b/tests/v1/extensions/test_eo.py similarity index 99% rename from tests/extensions/test_eo.py rename to tests/v1/extensions/test_eo.py index 89d99990e..abc1c149d 100644 --- a/tests/extensions/test_eo.py +++ b/tests/v1/extensions/test_eo.py @@ -9,8 +9,9 @@ from pystac.extensions.projection import ProjectionExtension from pystac.summaries import RangeSummary from pystac.utils import get_opt -from tests.conftest import get_data_file -from tests.utils import TestCases, assert_to_from_dict + +from ..conftest import get_data_file +from ..utils import TestCases, assert_to_from_dict def test_band_create() -> None: diff --git a/tests/extensions/test_ext.py b/tests/v1/extensions/test_ext.py similarity index 99% rename from tests/extensions/test_ext.py rename to tests/v1/extensions/test_ext.py index 8b9584a56..179a39b26 100644 --- a/tests/extensions/test_ext.py +++ b/tests/v1/extensions/test_ext.py @@ -13,7 +13,8 @@ ItemExt, LinkExt, ) -from tests.conftest import get_data_file + +from ..conftest import get_data_file logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger() diff --git a/tests/extensions/test_file.py b/tests/v1/extensions/test_file.py similarity index 99% rename from tests/extensions/test_file.py rename to tests/v1/extensions/test_file.py index 282af9063..f37951917 100644 --- a/tests/extensions/test_file.py +++ b/tests/v1/extensions/test_file.py @@ -12,7 +12,8 @@ Item, ) from pystac.extensions.file import ByteOrder, FileExtension, MappingObject -from tests.utils import TestCases, assert_to_from_dict + +from ..utils import TestCases, assert_to_from_dict FILE_ITEM_EXAMPLE_URI = TestCases.get_path("data-files/file/item.json") FILE_COLLECTION_EXAMPLE_URI = TestCases.get_path("data-files/file/collection.json") diff --git a/tests/extensions/test_grid.py b/tests/v1/extensions/test_grid.py similarity index 98% rename from tests/extensions/test_grid.py rename to tests/v1/extensions/test_grid.py index 32d3cfdb4..80a59282c 100644 --- a/tests/extensions/test_grid.py +++ b/tests/v1/extensions/test_grid.py @@ -12,8 +12,9 @@ from pystac import ExtensionTypeError from pystac.extensions import grid from pystac.extensions.grid import GridExtension -from tests.conftest import get_data_file -from tests.utils import TestCases + +from ..conftest import get_data_file +from ..utils import TestCases code = "MGRS-4CFJ" diff --git a/tests/extensions/test_mgrs.py b/tests/v1/extensions/test_mgrs.py similarity index 99% rename from tests/extensions/test_mgrs.py rename to tests/v1/extensions/test_mgrs.py index 29ddbf5fd..59f558241 100644 --- a/tests/extensions/test_mgrs.py +++ b/tests/v1/extensions/test_mgrs.py @@ -6,7 +6,8 @@ import pystac from pystac.extensions.mgrs import MgrsExtension -from tests.conftest import get_data_file + +from ..conftest import get_data_file @pytest.fixture diff --git a/tests/extensions/test_mlm.py b/tests/v1/extensions/test_mlm.py similarity index 99% rename from tests/extensions/test_mlm.py rename to tests/v1/extensions/test_mlm.py index cb7090fe7..342b099c2 100644 --- a/tests/extensions/test_mlm.py +++ b/tests/v1/extensions/test_mlm.py @@ -31,7 +31,8 @@ ValueScalingType, ) from pystac.extensions.raster import DataType -from tests.utils import TestCases + +from ..utils import TestCases logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger() diff --git a/tests/extensions/test_pointcloud.py b/tests/v1/extensions/test_pointcloud.py similarity index 99% rename from tests/extensions/test_pointcloud.py rename to tests/v1/extensions/test_pointcloud.py index 2145ea3ec..06e3ae0d7 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/v1/extensions/test_pointcloud.py @@ -15,7 +15,8 @@ Statistic, ) from pystac.summaries import RangeSummary -from tests.utils import TestCases, assert_to_from_dict + +from ..utils import TestCases, assert_to_from_dict # from copy import deepcopy diff --git a/tests/extensions/test_projection.py b/tests/v1/extensions/test_projection.py similarity index 99% rename from tests/extensions/test_projection.py rename to tests/v1/extensions/test_projection.py index 44b28a3cb..95afd95a2 100644 --- a/tests/extensions/test_projection.py +++ b/tests/v1/extensions/test_projection.py @@ -9,7 +9,8 @@ from pystac.errors import ExtensionNotImplemented from pystac.extensions.projection import ProjectionExtension from pystac.utils import get_opt -from tests.utils import TestCases, assert_to_from_dict + +from ..utils import TestCases, assert_to_from_dict WKT2 = """ GEOGCS["WGS 84", diff --git a/tests/extensions/test_raster.py b/tests/v1/extensions/test_raster.py similarity index 98% rename from tests/extensions/test_raster.py rename to tests/v1/extensions/test_raster.py index 9214ab010..b02d8bb79 100644 --- a/tests/extensions/test_raster.py +++ b/tests/v1/extensions/test_raster.py @@ -14,8 +14,9 @@ Statistics, ) from pystac.utils import get_opt -from tests.conftest import get_data_file -from tests.utils import TestCases, assert_to_from_dict + +from ..conftest import get_data_file +from ..utils import TestCases, assert_to_from_dict PLANET_EXAMPLE_URI = get_data_file("raster/raster-planet-example.json") diff --git a/tests/extensions/test_render.py b/tests/v1/extensions/test_render.py similarity index 99% rename from tests/extensions/test_render.py rename to tests/v1/extensions/test_render.py index 15b90f092..f24c99516 100644 --- a/tests/extensions/test_render.py +++ b/tests/v1/extensions/test_render.py @@ -7,7 +7,8 @@ import pystac import pystac.errors from pystac.extensions.render import Render, RenderExtension -from tests.conftest import get_data_file + +from ..conftest import get_data_file @pytest.fixture diff --git a/tests/extensions/test_sar.py b/tests/v1/extensions/test_sar.py similarity index 99% rename from tests/extensions/test_sar.py rename to tests/v1/extensions/test_sar.py index 49b287f74..ca5716dfb 100644 --- a/tests/extensions/test_sar.py +++ b/tests/v1/extensions/test_sar.py @@ -16,7 +16,8 @@ SarExtension, ) from pystac.summaries import RangeSummary -from tests.utils import TestCases + +from ..utils import TestCases @pytest.fixture diff --git a/tests/extensions/test_sat.py b/tests/v1/extensions/test_sat.py similarity index 99% rename from tests/extensions/test_sat.py rename to tests/v1/extensions/test_sat.py index 0a090ce3f..8bd3be627 100644 --- a/tests/extensions/test_sat.py +++ b/tests/v1/extensions/test_sat.py @@ -11,7 +11,8 @@ from pystac.extensions.sat import OrbitState, SatExtension, SummariesSatExtension from pystac.summaries import RangeSummary from pystac.utils import datetime_to_str, str_to_datetime -from tests.utils import TestCases + +from ..utils import TestCases @pytest.fixture diff --git a/tests/extensions/test_scientific.py b/tests/v1/extensions/test_scientific.py similarity index 99% rename from tests/extensions/test_scientific.py rename to tests/v1/extensions/test_scientific.py index 790bcdd2b..58f7283b9 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/v1/extensions/test_scientific.py @@ -16,7 +16,8 @@ ) from pystac.link import Link from pystac.summaries import Summaries -from tests.utils import TestCases + +from ..utils import TestCases URL_TEMPLATE = "http://example.com/catalog/%s.json" diff --git a/tests/extensions/test_storage.py b/tests/v1/extensions/test_storage.py similarity index 99% rename from tests/extensions/test_storage.py rename to tests/v1/extensions/test_storage.py index 4c1ba2322..1e9d4c66b 100644 --- a/tests/extensions/test_storage.py +++ b/tests/v1/extensions/test_storage.py @@ -8,7 +8,8 @@ from pystac import ExtensionTypeError, Item from pystac.collection import Collection from pystac.extensions.storage import CloudPlatform, StorageExtension -from tests.utils import TestCases, assert_to_from_dict + +from ..utils import TestCases, assert_to_from_dict NAIP_EXAMPLE_URI = TestCases.get_path("data-files/storage/item-naip.json") NAIP_COLLECTION_URI = TestCases.get_path("data-files/storage/collection-naip.json") diff --git a/tests/extensions/test_table.py b/tests/v1/extensions/test_table.py similarity index 98% rename from tests/extensions/test_table.py rename to tests/v1/extensions/test_table.py index efeae38e2..9022c6d94 100644 --- a/tests/extensions/test_table.py +++ b/tests/v1/extensions/test_table.py @@ -5,7 +5,8 @@ import pystac from pystac import ExtensionTypeError, Item from pystac.extensions.table import Column, TableExtension -from tests.utils import TestCases + +from ..utils import TestCases @pytest.fixture diff --git a/tests/extensions/test_timestamps.py b/tests/v1/extensions/test_timestamps.py similarity index 99% rename from tests/extensions/test_timestamps.py rename to tests/v1/extensions/test_timestamps.py index 5bf01e971..0ba41dda9 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/v1/extensions/test_timestamps.py @@ -8,7 +8,8 @@ from pystac.extensions.timestamps import TimestampsExtension from pystac.summaries import RangeSummary from pystac.utils import datetime_to_str, get_opt, str_to_datetime -from tests.utils import TestCases, assert_to_from_dict + +from ..utils import TestCases, assert_to_from_dict EXAMPLE_URI = TestCases.get_path("data-files/timestamps/example-landsat8.json") SAMPLE_DATETIME_STR = "2020-01-01T00:00:00Z" diff --git a/tests/extensions/test_version.py b/tests/v1/extensions/test_version.py similarity index 99% rename from tests/extensions/test_version.py rename to tests/v1/extensions/test_version.py index e0e2fc794..efa11ef3e 100644 --- a/tests/extensions/test_version.py +++ b/tests/v1/extensions/test_version.py @@ -23,7 +23,8 @@ VersionRelType, ignore_deprecated, ) -from tests.utils import TestCases + +from ..utils import TestCases URL_TEMPLATE: str = "http://example.com/catalog/%s.json" diff --git a/tests/extensions/test_view.py b/tests/v1/extensions/test_view.py similarity index 99% rename from tests/extensions/test_view.py rename to tests/v1/extensions/test_view.py index ae96e2ad5..3df492f4e 100644 --- a/tests/extensions/test_view.py +++ b/tests/v1/extensions/test_view.py @@ -7,7 +7,8 @@ from pystac.collection import Collection from pystac.extensions.view import ViewExtension from pystac.summaries import RangeSummary -from tests.utils import TestCases, assert_to_from_dict + +from ..utils import TestCases, assert_to_from_dict EXAMPLE_URI = TestCases.get_path("data-files/view/example-landsat8.json") diff --git a/tests/extensions/test_xarray_assets.py b/tests/v1/extensions/test_xarray_assets.py similarity index 99% rename from tests/extensions/test_xarray_assets.py rename to tests/v1/extensions/test_xarray_assets.py index 804e27a87..edfdcf577 100644 --- a/tests/extensions/test_xarray_assets.py +++ b/tests/v1/extensions/test_xarray_assets.py @@ -6,7 +6,8 @@ import pystac from pystac.extensions.xarray_assets import XarrayAssetsExtension -from tests.conftest import get_data_file + +from ..conftest import get_data_file @pytest.fixture diff --git a/tests/html/__init__.py b/tests/v1/html/__init__.py similarity index 100% rename from tests/html/__init__.py rename to tests/v1/html/__init__.py diff --git a/tests/html/test_html.py b/tests/v1/html/test_html.py similarity index 98% rename from tests/html/test_html.py rename to tests/v1/html/test_html.py index cac5fca3e..e2fe8997a 100644 --- a/tests/html/test_html.py +++ b/tests/v1/html/test_html.py @@ -7,7 +7,8 @@ import pystac from pystac.html.jinja_env import get_jinja_env -from tests.utils import TestCases + +from ..utils import TestCases def parse_html(stac_html: str) -> None: diff --git a/tests/posix_paths/__init__.py b/tests/v1/posix_paths/__init__.py similarity index 100% rename from tests/posix_paths/__init__.py rename to tests/v1/posix_paths/__init__.py diff --git a/tests/posix_paths/test_posix_paths.py b/tests/v1/posix_paths/test_posix_paths.py similarity index 98% rename from tests/posix_paths/test_posix_paths.py rename to tests/v1/posix_paths/test_posix_paths.py index bf0218296..fe0e0e36d 100644 --- a/tests/posix_paths/test_posix_paths.py +++ b/tests/v1/posix_paths/test_posix_paths.py @@ -6,8 +6,9 @@ import pytest import pystac -from tests.conftest import get_data_file -from tests.utils import ARBITRARY_BBOX, ARBITRARY_EXTENT, ARBITRARY_GEOM + +from ..conftest import get_data_file +from ..utils import ARBITRARY_BBOX, ARBITRARY_EXTENT, ARBITRARY_GEOM def check_link(link: pystac.Link | None) -> None: diff --git a/tests/serialization/__init__.py b/tests/v1/serialization/__init__.py similarity index 100% rename from tests/serialization/__init__.py rename to tests/v1/serialization/__init__.py diff --git a/tests/serialization/test_identify.py b/tests/v1/serialization/test_identify.py similarity index 98% rename from tests/serialization/test_identify.py rename to tests/v1/serialization/test_identify.py index babc170f4..de156290d 100644 --- a/tests/serialization/test_identify.py +++ b/tests/v1/serialization/test_identify.py @@ -8,8 +8,9 @@ merge_common_properties, ) from pystac.serialization.identify import STACVersionID, STACVersionRange -from tests.utils import TestCases -from tests.utils.test_cases import ExampleInfo + +from ..utils import TestCases +from ..utils.test_cases import ExampleInfo class TestIdentify: diff --git a/tests/serialization/test_migrate.py b/tests/v1/serialization/test_migrate.py similarity index 98% rename from tests/serialization/test_migrate.py rename to tests/v1/serialization/test_migrate.py index ed1aae43b..d2d3d1be8 100644 --- a/tests/serialization/test_migrate.py +++ b/tests/v1/serialization/test_migrate.py @@ -12,8 +12,9 @@ migrate_to_latest, ) from pystac.utils import get_required, str_to_datetime -from tests.utils import TestCases -from tests.utils.test_cases import ExampleInfo + +from ..utils import TestCases +from ..utils.test_cases import ExampleInfo class TestMigrate: diff --git a/tests/test_asset.py b/tests/v1/test_asset.py similarity index 100% rename from tests/test_asset.py rename to tests/v1/test_asset.py diff --git a/tests/test_cache.py b/tests/v1/test_cache.py similarity index 98% rename from tests/test_cache.py rename to tests/v1/test_cache.py index 2d92554d3..64c61345a 100644 --- a/tests/test_cache.py +++ b/tests/v1/test_cache.py @@ -3,7 +3,8 @@ import pystac from pystac.cache import ResolvedObjectCache, ResolvedObjectCollectionCache from pystac.utils import get_opt -from tests.utils import TestCases + +from .utils import TestCases def create_catalog(suffix: Any, include_href: bool = True) -> pystac.Catalog: diff --git a/tests/v1/test_catalog.py b/tests/v1/test_catalog.py new file mode 100644 index 000000000..ddfcced12 --- /dev/null +++ b/tests/v1/test_catalog.py @@ -0,0 +1,2036 @@ +from __future__ import annotations + +import json +import os +import posixpath +import tempfile +from collections import defaultdict +from collections.abc import Iterator +from copy import deepcopy +from datetime import UTC, datetime +from pathlib import Path +from typing import Any, cast + +import pytest + +import pystac +from pystac import ( + HIERARCHICAL_LINKS, + Asset, + Catalog, + CatalogType, + Collection, + Item, + Link, + MediaType, +) +from pystac.errors import STACError +from pystac.layout import ( + APILayoutStrategy, + BestPracticesLayoutStrategy, + HrefLayoutStrategy, + TemplateLayoutStrategy, +) +from pystac.utils import ( + is_absolute_href, + make_absolute_href, + make_posix_style, + make_relative_href, +) + +from .utils import ( + ARBITRARY_BBOX, + ARBITRARY_EXTENT, + ARBITRARY_GEOM, + MockStacIO, + TestCases, +) + + +class TestCatalogType: + def test_determine_type_for_absolute_published(self) -> None: + cat = TestCases.case_1() + with tempfile.TemporaryDirectory() as tmp_dir: + cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + cat_json = pystac.StacIO.default().read_json( + os.path.join(tmp_dir, "catalog.json") + ) + + catalog_type = CatalogType.determine_type(cat_json) + assert catalog_type == CatalogType.ABSOLUTE_PUBLISHED + + def test_determine_type_for_relative_published(self) -> None: + cat = TestCases.case_2() + with tempfile.TemporaryDirectory() as tmp_dir: + cat.normalize_and_save(tmp_dir, catalog_type=CatalogType.RELATIVE_PUBLISHED) + cat_json = pystac.StacIO.default().read_json( + os.path.join(tmp_dir, "catalog.json") + ) + + catalog_type = CatalogType.determine_type(cat_json) + assert catalog_type == CatalogType.RELATIVE_PUBLISHED + + def test_determine_type_for_self_contained(self) -> None: + cat_json = pystac.StacIO.default().read_json( + TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + ) + catalog_type = CatalogType.determine_type(cat_json) + assert catalog_type == CatalogType.SELF_CONTAINED + + def test_determine_type_for_unknown(self) -> None: + catalog = Catalog(id="test", description="test desc") + subcat = Catalog(id="subcat", description="subcat desc") + catalog.add_child(subcat) + catalog.normalize_hrefs("http://example.com") + d = catalog.to_dict(include_self_link=False) + + assert CatalogType.determine_type(d) is None + + +class TestCatalog: + def test_create_and_read(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + cat_dir = os.path.join(tmp_dir, "catalog") + catalog = TestCases.case_1() + + catalog.normalize_and_save( + cat_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED + ) + + read_catalog = Catalog.from_file(f"{cat_dir}/catalog.json") + + collections = catalog.get_children() + assert len(list(collections)) == 2 + + items = read_catalog.get_items(recursive=True) + + assert len(list(items)) == 8 + + def test_from_dict_preserves_dict(self) -> None: + catalog_dict = TestCases.case_1().to_dict() + param_dict = deepcopy(catalog_dict) + + # test that the parameter is preserved + _ = Catalog.from_dict(param_dict) + assert param_dict == catalog_dict + + # assert that the parameter is not preserved with + # non-default parameter + _ = Catalog.from_dict(param_dict, preserve_dict=False, migrate=False) + assert param_dict != catalog_dict + + def test_from_file_bad_catalog(self) -> None: + with pytest.raises(pystac.errors.STACTypeError) as ctx: + _ = Catalog.from_file(TestCases.get_path(TestCases.bad_catalog_case)) + assert "(id = broken_cat) does not represent a STACObject" in ctx.value.args[0] + assert "is Catalog" in ctx.value.args[0] + + def test_from_dict_set_root(self) -> None: + path = TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + with open(path) as f: + cat_dict = json.load(f) + root_cat = pystac.Catalog(id="test", description="test desc") + collection = Catalog.from_dict(cat_dict, root=root_cat) + assert collection.get_root() is root_cat + + @pytest.mark.vcr() + def test_read_remote(self) -> None: + catalog_url = ( + "https://raw.githubusercontent.com/stac-extensions/label/main/" + "examples/multidataset/catalog.json" + ) + cat = Catalog.from_file(catalog_url) + + zanzibar = cat.get_child("zanzibar-collection") + assert zanzibar is not None + + assert len(list(zanzibar.get_items())) == 2 + + def test_clear_items_removes_from_cache(self) -> None: + catalog = Catalog(id="test", description="test") + subcat = Catalog(id="subcat", description="test") + catalog.add_child(subcat) + item = Item( + id="test-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={"key": "one"}, + ) + subcat.add_item(item) + + items = list(catalog.get_items(recursive=True)) + assert len(items) == 1 + assert items[0].properties["key"] == "one" + + subcat.clear_items() + item = Item( + id="test-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={"key": "two"}, + ) + subcat.add_item(item) + + items = list(catalog.get_items(recursive=True)) + assert len(items) == 1 + assert items[0].properties["key"] == "two" + + subcat.remove_item("test-item") + item = Item( + id="test-item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={"key": "three"}, + ) + subcat.add_item(item) + + items = list(catalog.get_items(recursive=True)) + assert len(items) == 1 + assert items[0].properties["key"] == "three" + + def test_clear_children_removes_from_cache(self) -> None: + catalog = Catalog(id="test", description="test") + subcat = Catalog(id="subcat", description="test") + catalog.add_child(subcat) + + children = list(catalog.get_children()) + assert len(children) == 1 + assert children[0].description == "test" + + catalog.clear_children() + subcat = Catalog(id="subcat", description="test2") + catalog.add_child(subcat) + + children = list(catalog.get_children()) + assert len(children) == 1 + assert children[0].description == "test2" + + catalog.remove_child("subcat") + subcat = Catalog(id="subcat", description="test3") + catalog.add_child(subcat) + + children = list(catalog.get_children()) + assert len(children) == 1 + assert children[0].description == "test3" + + def test_clear_children_sets_parent_and_root_to_None(self) -> None: + catalog = Catalog(id="test", description="test") + subcat1 = Catalog(id="subcat", description="test") + subcat2 = Catalog(id="subcat2", description="test2") + catalog.add_children([subcat1, subcat2]) + + assert subcat1.get_parent() is not None + assert subcat2.get_parent() is not None + assert subcat1.get_root() is not None + assert subcat2.get_root() is not None + + children = list(catalog.get_children()) + assert len(children) == 2 + + catalog.clear_children() + + assert subcat1.get_parent() is None + assert subcat2.get_parent() is None + assert subcat1.get_root() is None + assert subcat2.get_root() is None + + def test_add_child_throws_if_item(self) -> None: + cat = TestCases.case_1() + item = next(cat.get_items(recursive=True)) + with pytest.raises(pystac.STACError): + cat.add_child(item) # type:ignore + + def test_add_child_returns_link(self) -> None: + parent = Catalog(id="parent", description="test") + child = Catalog(id="child", description="test") + link = parent.add_child(child) + assert isinstance(link, pystac.Link) + link.extra_fields["foo"] = "bar" + link2 = parent.get_single_link("child") + assert link2 is not None + assert link2.extra_fields["foo"] == "bar" + + def test_add_children_returns_links(self) -> None: + parent = Catalog(id="parent", description="test") + child1 = Catalog(id="child", description="test") + child2 = Catalog(id="child2", description="test") + links = parent.add_children([child1, child2]) + assert isinstance(links, list) + assert len(links) == 2 + assert isinstance(links[0], pystac.Link) + assert isinstance(links[1], pystac.Link) + links2 = parent.get_links("child") + assert links[0] in links2 + assert links[1] in links2 + + def test_add_child_override_parent(self) -> None: + parent1 = Catalog(id="parent1", description="test1") + parent2 = Catalog(id="parent2", description="test2") + child = Catalog(id="child", description="test3") + assert child.get_parent() is None + + parent1.add_child(child) + assert child.get_parent() is parent1 + + parent2.add_child(child) + assert child.get_parent() is parent2 + + def test_add_child_set_parent_false(self) -> None: + parent1 = Catalog(id="parent1", description="test1") + parent2 = Catalog(id="parent2", description="test2") + child = Catalog(id="child", description="test3") + assert child.get_parent() is None + + parent1.add_child(child, set_parent=False) + assert child.get_parent() is not parent1 + + parent1.add_child(child) + parent2.add_child(child, set_parent=False) + assert child.get_parent() is parent1 + + def test_add_item_override_parent(self) -> None: + parent1 = Catalog(id="parent1", description="test1") + parent2 = Catalog(id="parent2", description="test2") + child = Item( + id="child", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + assert child.get_parent() is None + + parent1.add_item(child) + assert child.get_parent() is parent1 + + parent2.add_item(child) + assert child.get_parent() is parent2 + + def test_add_item_set_parent_false(self) -> None: + parent1 = Catalog(id="parent1", description="test1") + parent2 = Catalog(id="parent2", description="test2") + child = Item( + id="child", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + assert child.get_parent() is None + + parent1.add_item(child, set_parent=False) + assert child.get_parent() is not parent1 + + parent1.add_item(child) + parent2.add_item(child, set_parent=False) + assert child.get_parent() is parent1 + + def test_add_item_throws_if_child(self) -> None: + cat = TestCases.case_1() + child = next(iter(cat.get_children())) + with pytest.raises(pystac.STACError): + cat.add_item(child) # type:ignore + + def test_add_item_returns_link(self) -> None: + parent = Catalog(id="parent", description="test") + child = Item( + id="child", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + link = parent.add_item(child) + assert isinstance(link, pystac.Link) + link.extra_fields["foo"] = "bar" + link2 = parent.get_single_link("item") + assert link2 is not None + assert link2.extra_fields["foo"] == "bar" + + def test_add_items_returns_links(self) -> None: + parent = Catalog(id="parent", description="test") + child1 = Item( + id="child1", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + child2 = Item( + id="child2", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + links = parent.add_items([child1, child2]) + assert isinstance(links, list) + assert len(links) == 2 + assert isinstance(links[0], pystac.Link) + assert isinstance(links[1], pystac.Link) + links2 = parent.get_links("item") + assert links[0] in links2 + assert links[1] in links2 + + def test_get_child_returns_none_if_not_found(self) -> None: + cat = TestCases.case_1() + child = cat.get_child("thisshouldnotbeachildid", recursive=True) + assert child is None + + def test_get_item_is_deprecated_but_still_works(self) -> None: + cat = TestCases.case_1() + with pytest.warns(DeprecationWarning): + item = cat.get_item("area-2-1-imagery", recursive=True) + assert item is not None + + def test_get_item_returns_none_if_not_found(self) -> None: + cat = TestCases.case_1() + with pytest.warns(DeprecationWarning): + item = cat.get_item("thisshouldnotbeanitemid", recursive=True) + assert item is None + + def test_get_all_items_is_deprecated_but_still_works(self) -> None: + cat = TestCases.case_1() + with pytest.warns(DeprecationWarning): + all_items = cat.get_all_items() + items = cat.get_items(recursive=True) + assert all(a == i for a, i in zip(all_items, items)) + + def test_get_items_returns_empty_generator_if_not_found(self) -> None: + cat = TestCases.case_1() + items = cat.get_items("thisshouldnotbeanitemid", recursive=True) + assert next(items, None) is None + + def test_sets_catalog_type(self) -> None: + cat = TestCases.case_1() + + assert cat.catalog_type == CatalogType.SELF_CONTAINED + + @pytest.mark.parametrize("catalog", TestCases.all_test_catalogs()) + def test_walk_iterates_correctly(self, catalog: Catalog) -> None: + def test_catalog(cat: Catalog) -> None: + expected_catalog_iterations = 1 + actual_catalog_iterations = 0 + for root, children, items in cat.walk(): + actual_catalog_iterations += 1 + expected_catalog_iterations += len(list(root.get_children())) + + assert {c.id for c in root.get_children()} == { + c.id for c in children + }, "Children unequal" + + assert {c.id for c in root.get_items()} == {c.id for c in items}, ( + "Items unequal" + ) + + assert actual_catalog_iterations == expected_catalog_iterations + + test_catalog(catalog) + + @pytest.mark.parametrize("catalog", TestCases.all_test_catalogs()) + def test_clone_generates_correct_links(self, catalog: Catalog) -> None: + expected_link_types_to_counts: Any = {} + actual_link_types_to_counts: Any = {} + + for root, _, items in catalog.walk(): + expected_link_types_to_counts[root.id] = defaultdict(int) + actual_link_types_to_counts[root.id] = defaultdict(int) + + for link in root.get_links(): + expected_link_types_to_counts[root.id][link.rel] += 1 + + for link in root.clone().get_links(): + actual_link_types_to_counts[root.id][link.rel] += 1 + + for item in items: + expected_link_types_to_counts[item.id] = defaultdict(int) + actual_link_types_to_counts[item.id] = defaultdict(int) + for link in item.get_links(): + expected_link_types_to_counts[item.id][link.rel] += 1 + for link in item.get_links(): + actual_link_types_to_counts[item.id][link.rel] += 1 + + assert set(expected_link_types_to_counts.keys()) == set( + actual_link_types_to_counts.keys() + ) + + for obj_id in actual_link_types_to_counts: + expected_counts = expected_link_types_to_counts[obj_id] + actual_counts = actual_link_types_to_counts[obj_id] + assert set(expected_counts.keys()) == set(actual_counts.keys()) + for rel in expected_counts: + assert actual_counts[rel] == expected_counts[rel], ( + f"Clone of {obj_id} has {actual_counts[rel]} {rel} links, original has {expected_counts[rel]}" + ) + + def test_save_uses_previous_catalog_type(self) -> None: + catalog = TestCases.case_1() + assert catalog.catalog_type == CatalogType.SELF_CONTAINED + with tempfile.TemporaryDirectory() as tmp_dir: + catalog.normalize_hrefs(tmp_dir) + href = catalog.self_href + catalog.save() + + cat2 = pystac.Catalog.from_file(href) + assert cat2.catalog_type == CatalogType.SELF_CONTAINED + + def test_save_to_provided_href(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_1() + href = "https://stac.test" + folder = os.path.join(tmp_dir, "cat") + catalog.normalize_hrefs(href) + catalog.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED, dest_href=folder) + + catalog_path = os.path.join(folder, "catalog.json") + assert os.path.exists(catalog_path) + result_cat = Catalog.from_file(catalog_path) + for link in result_cat.get_child_links(): + assert cast(str, link.target).startswith(href) + + def test_save_relative_published_no_self_links(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_1() + href = "https://stac.test" + folder = os.path.join(tmp_dir, "cat") + catalog.normalize_hrefs(href) + catalog.save(catalog_type=CatalogType.RELATIVE_PUBLISHED, dest_href=folder) + + catalog_path = os.path.join(folder, "catalog.json") + assert os.path.exists(catalog_path) + result_cat = Catalog.from_file(catalog_path) + + # Check that Items do not have a self link + # Since Item.from_dict automatically adds a self link, we need to look at + # the JSON files themselves. + stac_io = pystac.StacIO.default() + + for current_cat, _, __ in result_cat.walk(): + for item_link in current_cat.get_item_links(): + item_dict = stac_io.read_json(item_link) + self_link = next( + ( + link + for link in item_dict.get("links", []) + if link["rel"] == "self" + ), + None, + ) + assert self_link is None + + def test_save_with_different_stac_io(self) -> None: + catalog = Catalog.from_file( + TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + ) + stac_io = MockStacIO() + with tempfile.TemporaryDirectory() as tmp_dir: + catalog.normalize_hrefs(tmp_dir) + catalog.save( + catalog_type=CatalogType.ABSOLUTE_PUBLISHED, + dest_href=tmp_dir, + stac_io=stac_io, + ) + + hrefs = [] + for root, _, items in catalog.walk(): + hrefs.append(root.get_self_href()) + for item in items: + hrefs.append(item.get_self_href()) + + assert len(hrefs) == stac_io.mock.write_text.call_count + for call_args_list in stac_io.mock.write_text.call_args_list: + assert call_args_list[0][0] in hrefs + + def test_subcatalogs_saved_to_correct_path(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_1() + href = "https://stac.test" + + catalog.normalize_hrefs(href) + catalog.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED, dest_href=tmp_dir) + + # Check the root catalog path + expected_root_catalog_path = os.path.join(tmp_dir, "catalog.json") + assert os.path.exists(expected_root_catalog_path), ( + f"{expected_root_catalog_path} does not exist." + ) + assert os.path.isfile(expected_root_catalog_path), ( + f"{expected_root_catalog_path} is not a file." + ) + + # Check each child catalog + for child_catalog in catalog.get_children(): + relative_path = make_relative_href( + child_catalog.self_href, catalog.self_href, start_is_dir=False + ) + expected_child_path = make_absolute_href( + relative_path, + expected_root_catalog_path, + start_is_dir=False, + ) + assert os.path.exists(expected_child_path), ( + f"{expected_child_path} does not exist." + ) + assert os.path.isfile(expected_child_path), ( + f"{expected_child_path} is not a file." + ) + + # Check each item + for item in catalog.get_items(recursive=True): + relative_path = make_relative_href( + item.self_href, catalog.self_href, start_is_dir=False + ) + expected_item_path = make_absolute_href( + relative_path, + expected_root_catalog_path, + start_is_dir=False, + ) + assert os.path.exists(expected_item_path), ( + f"{expected_item_path} does not exist." + ) + assert os.path.isfile(expected_item_path), ( + f"{expected_item_path} is not a file." + ) + + def test_clone_uses_previous_catalog_type(self) -> None: + catalog = TestCases.case_1() + assert catalog.catalog_type == CatalogType.SELF_CONTAINED + clone = catalog.clone() + assert clone.catalog_type == CatalogType.SELF_CONTAINED + + def test_normalize_hrefs_sets_all_hrefs(self) -> None: + catalog = TestCases.case_1() + catalog.normalize_hrefs("http://example.com") + for root, _, items in catalog.walk(): + self_href = root.get_self_href() + assert self_href is not None + assert self_href.startswith("http://example.com") + for link in root.links: + if link.is_resolved(): + target_href = cast(pystac.STACObject, link.target).self_href + else: + target_href = link.absolute_href + assert "http://example.com" in target_href, ( + '[{}] {} does not contain "{}"'.format( + link.rel, target_href, "http://example.com" + ) + ) + for item in items: + assert "http://example.com" in item.self_href + + def test_normalize_hrefs_makes_absolute_href(self) -> None: + catalog = TestCases.case_1() + catalog.normalize_hrefs("./relativepath") + abspath = make_posix_style(os.path.abspath("./relativepath")) + self_href = catalog.get_self_href() + assert self_href is not None + assert self_href.startswith(abspath) + + def test_normalize_hrefs_skip_unresolved(self) -> None: + catalog = TestCases.case_1() + catalog.normalize_hrefs("http://example.com", skip_unresolved=True) + assert catalog.self_href.startswith("http://example.com") + for link in catalog.links: + if link.rel == "child" or link.rel == "item": + assert not link.is_resolved() + item = Item( + "an-id", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + catalog.add_item(item, title="This is the test item") + catalog.normalize_hrefs("http://example.com", skip_unresolved=True) + for link in catalog.links: + if link.title == "This is the test item": + assert link.is_resolved() + assert isinstance(link.target, pystac.STACObject) + assert link.target.self_href.startswith("http://example.com") + elif link.rel == "child" or link.rel == "item": + assert not link.is_resolved() + + def test_save_unresolved(self) -> None: + catalog = Catalog("an-id", "a description") + item = Item( + "an-id", + geometry=None, + bbox=None, + datetime=datetime.now(), + properties={}, + ) + catalog.add_item(item) + catalog.add_link(pystac.Link("child", "/not/a/real/path/catalog.json")) + with tempfile.TemporaryDirectory() as temporary_directory: + catalog.set_self_href(os.path.join(temporary_directory, "catalog.json")) + item.set_self_href(os.path.join(temporary_directory, "item.json")) + catalog.save() + assert len(os.listdir(temporary_directory)) == 2 + + with tempfile.TemporaryDirectory() as temporary_directory: + catalog.normalize_and_save(temporary_directory, skip_unresolved=True) + assert len(os.listdir(temporary_directory)) == 2 + + with tempfile.TemporaryDirectory() as temporary_directory: + with pytest.raises(STACError, match="does not resolve to a STAC object"): + catalog.normalize_and_save(temporary_directory, skip_unresolved=False) + + def test_generate_subcatalogs_works_with_custom_properties(self) -> None: + catalog = TestCases.case_8() + defaults = {"pl:item_type": "PlanetScope"} + catalog.generate_subcatalogs( + "${year}/${month}/${pl:item_type}", defaults=defaults + ) + + month_cat = catalog.get_child("8", recursive=True) + assert month_cat is not None + type_cats = {cat.id for cat in month_cat.get_children()} + + assert type_cats == {"PSScene4Band", "SkySatScene", "PlanetScope"} + + def test_generate_subcatalogs_does_not_change_item_count(self) -> None: + catalog = TestCases.case_7() + + item_counts = { + cat.id: len(list(cat.get_items(recursive=True))) + for cat in catalog.get_children() + } + + catalog.generate_subcatalogs("${year}/${day}") + + with tempfile.TemporaryDirectory() as tmp_dir: + catalog.normalize_hrefs(tmp_dir) + catalog.save(pystac.CatalogType.SELF_CONTAINED) + + cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) + for child in cat2.get_children(): + actual = len(list(child.get_items(recursive=True))) + expected = item_counts[child.id] + assert actual == expected, f" for child '{child.id}'" + + def test_generate_subcatalogs_merge_template_elements(self) -> None: + catalog = Catalog(id="test", description="Test") + item_properties = [ + dict(property1=p1, property2=p2) for p1 in ("A", "B") for p2 in (1, 2) + ] + for ni, properties in enumerate(item_properties): + catalog.add_item( + Item( + id=f"item{ni}", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties=properties, + ) + ) + result = catalog.generate_subcatalogs("${property1}_${property2}") + + actual_subcats = {cat.id for cat in result} + expected_subcats = { + "{}_{}".format(d["property1"], d["property2"]) for d in item_properties + } + assert len(result) == len(expected_subcats) + assert actual_subcats == expected_subcats + + def test_generate_subcatalogs_can_be_applied_multiple_times(self) -> None: + catalog = TestCases.case_8() + + _ = catalog.generate_subcatalogs("${year}/${month}") + catalog.normalize_hrefs("/tmp") + expected_hrefs = { + item.id: item.get_self_href() for item in catalog.get_items(recursive=True) + } + + result = catalog.generate_subcatalogs("${year}/${month}") + assert len(result) == 0 + catalog.normalize_hrefs("/tmp") + for item in catalog.get_items(recursive=True): + assert item.get_self_href() == expected_hrefs[item.id], ( + f" for item '{item.id}'" + ) + + def test_generate_subcatalogs_works_after_adding_more_items(self) -> None: + catalog = Catalog(id="test", description="Test") + properties = dict(property1="A", property2=1) + catalog.add_item( + Item( + id="item1", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties=properties, + ) + ) + catalog.generate_subcatalogs("${property1}/${property2}") + catalog.add_item( + Item( + id="item2", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties=properties, + ) + ) + catalog.generate_subcatalogs("${property1}/${property2}") + + catalog.normalize_hrefs("/tmp") + item1 = next(catalog.get_items("item1", recursive=True)) + item1_parent = item1.get_parent() + assert item1_parent is not None + item2 = next(catalog.get_items("item2", recursive=True)) + item2_parent = item2.get_parent() + assert item2_parent is not None + assert item1_parent.get_self_href() == item2_parent.get_self_href() + + def test_generate_subcatalogs_works_for_branched_subcatalogs(self) -> None: + catalog = Catalog(id="test", description="Test") + item_properties = [ + dict(property1="A", property2=1, property3="i"), # add 3 subcats + dict(property1="A", property2=1, property3="j"), # add 1 more + dict(property1="A", property2=2, property3="i"), # add 2 more + dict(property1="B", property2=1, property3="i"), # add 3 more + ] + for ni, properties in enumerate(item_properties): + catalog.add_item( + Item( + id=f"item{ni}", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties=properties, + ) + ) + result = catalog.generate_subcatalogs("${property1}/${property2}/${property3}") + assert len(result) == 9 + + actual_subcats = {cat.id for cat in result} + expected_subcats = {"A", "B", "1", "2", "i", "j"} + assert actual_subcats == expected_subcats + + def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self) -> None: + catalog = Catalog(id="test", description="Test") + item_properties = [ + dict(property1=1, property2=1), # add 2 subcats + dict(property1=1, property2=2), # add 1 more + dict(property1=2, property2=1), # add 2 more + dict(property1=2, property2=2), # add 1 more + ] + for ni, properties in enumerate(item_properties): + catalog.add_item( + Item( + id=f"item{ni}", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties=properties, + ) + ) + + result = catalog.generate_subcatalogs("${property1}/${property2}") + assert len(result) == 6 + + catalog.normalize_hrefs("/") + + for item in catalog.get_items(recursive=True): + item_parent = item.get_parent() + assert item_parent is not None + parent_href = item_parent.self_href + path_to_parent, _ = os.path.split(parent_href) + subcats = list( + Path(path_to_parent).parts[1:] + ) # Skip drive letter if present (Windows) + + assert len(subcats) == 2, f" for item '{item.id}'" + + def test_map_items(self) -> None: + def item_mapper(item: pystac.Item) -> pystac.Item: + item.properties["ITEM_MAPPER"] = "YEP" + return item + + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_1() + + new_cat = catalog.map_items(item_mapper) + + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) + new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) + + for item in result_cat.get_items(recursive=True): + assert "ITEM_MAPPER" in item.properties + + for item in catalog.get_items(recursive=True): + assert "ITEM_MAPPER" not in item.properties + + def test_map_items_multiple(self) -> None: + def item_mapper(item: pystac.Item) -> list[pystac.Item]: + item2 = item.clone() + item2.id = item2.id + "_2" + item.properties["ITEM_MAPPER_1"] = "YEP" + item2.properties["ITEM_MAPPER_2"] = "YEP" + return [item, item2] + + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_1() + catalog_items = list(catalog.get_items(recursive=True)) + + new_cat = catalog.map_items(item_mapper) + + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) + new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) + result_items = list(result_cat.get_items(recursive=True)) + + assert len(catalog_items) * 2 == len(result_items) + + ones, twos = 0, 0 + for item in result_items: + assert ("ITEM_MAPPER_1" in item.properties) or ( + "ITEM_MAPPER_2" in item.properties + ) + if "ITEM_MAPPER_1" in item.properties: + ones += 1 + + if "ITEM_MAPPER_2" in item.properties: + twos += 1 + + assert ones == twos + + for item in catalog.get_items(recursive=True): + assert ("ITEM_MAPPER_1" not in item.properties) and ( + "ITEM_MAPPER_2" not in item.properties + ) + + def test_map_items_multiple_2(self) -> None: + catalog = Catalog(id="test-1", description="Test1") + item1 = Item( + id="item1", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={}, + ) + item1.add_asset("ortho", Asset(href="/some/ortho.tif")) + catalog.add_item(item1) + kitten = Catalog(id="test-kitten", description="A cuter version of catalog") + catalog.add_child(kitten) + item2 = Item( + id="item2", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={}, + ) + item2.add_asset("ortho", Asset(href="/some/other/ortho.tif")) + kitten.add_item(item2) + + def modify_item_title(item: pystac.Item) -> pystac.Item: + item.properties["title"] = "Some title" + return item + + def duplicate_item(item: pystac.Item) -> list[pystac.Item]: + duplicated_item = item.clone() + duplicated_item.id += "-duplicated" + return [item, duplicated_item] + + c = catalog.map_items(modify_item_title) + c = c.map_items(duplicate_item) + new_catalog = c + + items = new_catalog.get_items(recursive=True) + assert len(list(items)) == 4 + + def test_map_assets_single(self) -> None: + changed_asset = "d43bead8-e3f8-4c51-95d6-e24e750a402b" + + def asset_mapper(key: str, asset: pystac.Asset) -> pystac.Asset: + if key == changed_asset: + asset.title = "NEW TITLE" + + return asset + + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_2() + + new_cat = catalog.map_assets(asset_mapper) + + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) + new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) + + found = False + for item in result_cat.get_items(recursive=True): + for key, asset in item.assets.items(): + if key == changed_asset: + found = True + assert asset.title == "NEW TITLE" + else: + assert asset.title != "NEW TITLE" + assert found + + def test_map_assets_tup(self) -> None: + changed_assets: list[str] = [] + + def asset_mapper( + key: str, asset: pystac.Asset + ) -> pystac.Asset | tuple[str, pystac.Asset]: + if asset.media_type and "geotiff" in asset.media_type: + asset.title = "NEW TITLE" + changed_assets.append(key) + return (f"{key}-modified", asset) + else: + return asset + + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_2() + + new_cat = catalog.map_assets(asset_mapper) + + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) + new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) + + found = False + not_found = False + for item in result_cat.get_items(recursive=True): + for key, asset in item.assets.items(): + if key.replace("-modified", "") in changed_assets: + found = True + assert asset.title == "NEW TITLE" + else: + not_found = True + assert asset.title != "NEW TITLE" + + assert found + assert not_found + + def test_map_assets_multi(self) -> None: + changed_assets = [] + + def asset_mapper( + key: str, asset: pystac.Asset + ) -> pystac.Asset | dict[str, pystac.Asset]: + if asset.media_type and "geotiff" in asset.media_type: + changed_assets.append(key) + mod1 = asset.clone() + mod1.title = "NEW TITLE 1" + mod2 = asset.clone() + mod2.title = "NEW TITLE 2" + return {f"{key}-mod-1": mod1, f"{key}-mod-2": mod2} + else: + return asset + + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = TestCases.case_2() + + new_cat = catalog.map_assets(asset_mapper) + + new_cat.normalize_hrefs(os.path.join(tmp_dir, "cat")) + new_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + result_cat = Catalog.from_file(os.path.join(tmp_dir, "cat", "catalog.json")) + + found1 = False + found2 = False + not_found = False + for item in result_cat.get_items(recursive=True): + for key, asset in item.assets.items(): + if key.replace("-mod-1", "") in changed_assets: + found1 = True + assert asset.title == "NEW TITLE 1" + elif key.replace("-mod-2", "") in changed_assets: + found2 = True + assert asset.title == "NEW TITLE 2" + else: + not_found = True + assert asset.title != "NEW TITLE" + + assert found1 + assert found2 + assert not_found + + def test_make_all_asset_hrefs_absolute(self) -> None: + cat = TestCases.case_2() + cat.make_all_asset_hrefs_absolute() + ID = "cf73ec1a-d790-4b59-b077-e101738571ed" + item = next(cat.get_items(ID, recursive=True)) + href = item.assets[ID].href + assert is_absolute_href(href) + + def test_make_all_asset_hrefs_relative(self) -> None: + cat = TestCases.case_2() + ID = "cf73ec1a-d790-4b59-b077-e101738571ed" + item = next(cat.get_items(ID, recursive=True)) + asset = item.assets[ID] + original_href = asset.href + cat.make_all_asset_hrefs_absolute() + + assert is_absolute_href(asset.href) + + cat.make_all_asset_hrefs_relative() + + assert not is_absolute_href(asset.href) + assert asset.href == original_href + + @pytest.mark.parametrize("catalog", TestCases.all_test_catalogs()) + def test_make_all_links_relative_or_absolute(self, catalog: Catalog) -> None: + def check_all_relative(cat: Catalog) -> None: + for root, catalogs, items in cat.walk(): + for link in root.links: + if link.rel in HIERARCHICAL_LINKS: + assert not is_absolute_href(link.href) + for item in items: + for link in item.links: + if link.rel in HIERARCHICAL_LINKS: + assert not is_absolute_href(link.href) + + def check_all_absolute(cat: Catalog) -> None: + for root, catalogs, items in cat.walk(): + for link in root.links: + assert is_absolute_href(link.href) + for item in items: + for link in item.links: + assert is_absolute_href(link.href) + + with tempfile.TemporaryDirectory() as tmp_dir: + c2 = catalog.full_copy() + c2.normalize_hrefs(tmp_dir) + c2.catalog_type = CatalogType.RELATIVE_PUBLISHED + check_all_relative(c2) + c2.catalog_type = CatalogType.ABSOLUTE_PUBLISHED + check_all_absolute(c2) + + @pytest.mark.block_network() + def test_self_contained_catalog_collection_item_links(self) -> None: + """See issue https://github.com/stac-utils/pystac/issues/657""" + with tempfile.TemporaryDirectory() as tmp_dir: + catalog = pystac.Catalog( + id="catalog-issue-657", description="catalog-issue-657" + ) + collection = pystac.Collection( + "collection-issue-657", + "collection-issue-657", + pystac.Extent( + spatial=pystac.SpatialExtent([[-180.0, -90.0, 180.0, 90.0]]), + temporal=pystac.TemporalExtent([[datetime(2021, 11, 1), None]]), + ), + license="other", + ) + + item = pystac.Item( + id="item-issue-657", + stac_extensions=[], + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime(2021, 11, 1), + properties={}, + ) + + collection.add_item(item) + catalog.add_child(collection) + + catalog.normalize_hrefs(tmp_dir) + catalog.validate_all() + + catalog.save(catalog_type=CatalogType.SELF_CONTAINED) + with open( + f"{tmp_dir}/collection-issue-657/item-issue-657/item-issue-657.json" + ) as f: + item_json = json.load(f) + + for link in item_json["links"]: + # self links are always absolute + if link["rel"] == "self": + continue + + href = link["href"] + assert not is_absolute_href(href), ( + f"Link with rel={link['rel']} is absolute!" + ) + + def test_full_copy_and_normalize_works_with_created_stac(self) -> None: + cat = TestCases.case_3() + cat_copy = cat.full_copy() + cat_copy.normalize_hrefs("http://example.com") + for root, catalogs, items in cat_copy.walk(): + for link in root.links: + if link.rel != "self": + assert link.target is not None + for item in items: + for link in item.links: + if link.rel != "self": + assert link.get_href() is not None + + def test_extra_fields(self) -> None: + catalog = TestCases.case_1() + + catalog.extra_fields["custom_field"] = "Special content" + + with tempfile.TemporaryDirectory() as tmp_dir: + p = os.path.join(tmp_dir, "catalog.json") + catalog.save_object(include_self_link=False, dest_href=p) + with open(p) as f: + cat_json = json.load(f) + assert "custom_field" in cat_json + assert cat_json["custom_field"] == "Special content" + + read_cat = pystac.Catalog.from_file(p) + assert "custom_field" in read_cat.extra_fields + assert read_cat.extra_fields["custom_field"] == "Special content" + + @pytest.mark.parametrize("cat", TestCases.all_test_catalogs()) + @pytest.mark.vcr() + def test_validate_all(self, cat: Catalog) -> None: + # If hrefs are not set, it will fail validation. + if cat.get_self_href() is None: + cat.normalize_hrefs("/tmp") + cat.validate_all() + + @pytest.mark.vcr() + def test_validate_all_invalid(self) -> None: + # Make one invalid, write it off, read it in, ensure it throws + cat = TestCases.case_1() + item = next(cat.get_items("area-1-1-labels", recursive=True)) + item.geometry = {"type": "INVALID", "coordinates": "NONE"} + with tempfile.TemporaryDirectory() as tmp_dir: + cat.normalize_hrefs(tmp_dir) + cat.save(catalog_type=pystac.CatalogType.SELF_CONTAINED) + + cat2 = pystac.Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) + + with pytest.raises(pystac.STACValidationError): + cat2.validate_all() + + def test_set_hrefs_manually(self) -> None: + catalog = TestCases.case_1() + + # Modify the datetimes + year = 2004 + month = 2 + for item in catalog.get_items(recursive=True): + assert item.datetime is not None + item.datetime = item.datetime.replace(year=year, month=month) + year += 1 + month += 1 + + with tempfile.TemporaryDirectory() as tmp_dir: + for root, _, items in catalog.walk(): + # Set root's HREF based off the parent + parent = root.get_parent() + if parent is None: + root_dir = tmp_dir + else: + d = os.path.dirname(parent.self_href) + root_dir = os.path.join(d, root.id) + root_href = os.path.join(root_dir, root.DEFAULT_FILE_NAME) + root.set_self_href(root_href) + + # Set each item's HREF based on it's datetime + for item in items: + assert item.datetime is not None + item_href = f"{root_dir}/{item.datetime.year}-{item.datetime.month}/{item.id}.json" + item.set_self_href(item_href) + + catalog.save(catalog_type=CatalogType.SELF_CONTAINED) + + read_catalog = Catalog.from_file(os.path.join(tmp_dir, "catalog.json")) + + for root, _, items in read_catalog.walk(): + parent = root.get_parent() + if parent is None: + assert root.get_self_href() == make_posix_style( + os.path.join(tmp_dir, "catalog.json") + ) + else: + d = os.path.dirname(parent.self_href) + assert root.get_self_href() == make_posix_style( + os.path.join(d, root.id, root.DEFAULT_FILE_NAME) + ) + for item in items: + assert item.datetime is not None + end = posixpath.join( + f"{item.datetime.year}-{item.datetime.month}", + f"{item.id}.json", + ) + self_href = item.get_self_href() + assert self_href is not None + assert self_href.endswith(end), ( + f"{self_href} does not end with {end}" + ) + + @pytest.mark.parametrize("cat", TestCases.all_test_catalogs()) + def test_collections_cache_correctly(self, cat: Catalog) -> None: + mock_io = MockStacIO() + cat._stac_io = mock_io + expected_collection_reads = set() + for root, _, items in cat.walk(): + if isinstance(root, Collection) and root != cat: + expected_collection_reads.add(root.get_self_href()) + + # Iterate over items to make sure they are read + assert list(items) is not None + + call_uris: list[Any] = [ + call[0][0] + for call in mock_io.mock.read_text.call_args_list + if call[0][0] in expected_collection_reads + ] + + for collection_uri in expected_collection_reads: + calls = len([x for x in call_uris if x == collection_uri]) + assert calls == 1, ( + f"{collection_uri} was read {calls} times instead of once!" + ) + + def test_reading_iterating_and_writing_works_as_expected(self) -> None: + """Test case to cover issue #88""" + stac_uri = TestCases.get_path("data-files/catalogs/test-case-6/catalog.json") + cat = Catalog.from_file(stac_uri) + + # Iterate over the items. This was causing failure in + # the later iterations as per issue #88 + for item in cat.get_items(recursive=True): + pass + + with tempfile.TemporaryDirectory() as tmp_dir: + new_stac_uri = os.path.join(tmp_dir, "test-case-6") + cat.normalize_hrefs(new_stac_uri) + cat.save(catalog_type=CatalogType.SELF_CONTAINED) + + # Open the local copy and iterate over it. + cat2 = Catalog.from_file(os.path.join(new_stac_uri, "catalog.json")) + + for item in cat2.get_items(recursive=True): + # Iterate again over the items. This would fail in #88 + pass + + def test_get_children_cbers(self) -> None: + cat = TestCases.case_6() + assert len(list(cat.get_children())) == 4 + + def test_resolve_planet(self) -> None: + """Test against a bug that caused infinite recursion during link resolution""" + cat = TestCases.case_8() + for root, _, items in cat.walk(): + for item in items: + item.resolve_links() + root.resolve_links() + + def test_handles_children_with_same_id(self) -> None: + # This catalog has the root and child collection share an ID. + cat = pystac.Catalog.from_file( + TestCases.get_path("data-files/invalid/shared-id/catalog.json") + ) + items = list(cat.get_items(recursive=True)) + + assert len(items) == 1 + + def test_catalog_with_href_caches_by_href(self) -> None: + cat = TestCases.case_1() + cache = cat._resolved_objects + + # Since all of our STAC objects have HREFs, everything should be + # cached only by HREF + assert len(cache.id_keys_to_objects) == 0 + + def test_from_invalid_dict_raises_exception(self) -> None: + stac_io = pystac.StacIO.default() + collection_dict = stac_io.read_json( + TestCases.get_path("data-files/collections/multi-extent.json") + ) + with pytest.raises(pystac.STACTypeError): + _ = pystac.Catalog.from_dict(collection_dict) + + def test_get_collections(self) -> None: + catalog = TestCases.case_2() + collections = list(catalog.get_collections()) + + assert len(collections) > 0 + assert all(isinstance(c, pystac.Collection) for c in collections) + + def test_get_all_collections(self) -> None: + catalog = TestCases.case_1() + all_collections = list(catalog.get_all_collections()) + + assert len(all_collections) == 4 + assert all(isinstance(c, pystac.Collection) for c in all_collections) + + def test_get_single_links_media_type(self) -> None: + catalog = TestCases.case_1() + + catalog.links.append( + pystac.Link(rel="search", target="./search.html", media_type="text/html") + ) + catalog.links.append( + pystac.Link( + rel="search", target="./search.json", media_type="application/geo+json" + ) + ) + + html_link = catalog.get_single_link(rel="search") + assert html_link is not None + assert html_link.href == "./search.html" + html_link = catalog.get_single_link(media_type="text/html") + assert html_link is not None + assert html_link.href == "./search.html" + json_link = catalog.get_single_link( + rel="search", media_type="application/geo+json" + ) + assert json_link is not None + assert json_link.href == "./search.json" + no_link = catalog.get_single_link(rel="via") + assert no_link is None + first_link = catalog.get_single_link() + assert first_link is not None + assert first_link.rel == "self" + + def test_get_links(self) -> None: + catalog = TestCases.case_1() + + catalog.links.append( + pystac.Link(rel="search", target="./search.html", media_type="text/html") + ) + catalog.links.append( + pystac.Link( + rel="search", target="./search.json", media_type="application/geo+json" + ) + ) + assert ( + len(catalog.get_links(rel="search", media_type="application/geo+json")) == 1 + ) + assert len(catalog.get_links(media_type="text/html")) == 1 + assert ( + len(catalog.get_links(media_type=["text/html", "application/geo+json"])) + == 2 + ) + assert len(catalog.get_links(rel="search")) == 2 + assert len(catalog.get_links(rel="via")) == 0 + assert len(catalog.get_links()) == 6 + + def test_to_dict_no_self_href(self) -> None: + catalog = Catalog(id="an-id", description="A test Catalog") + d = catalog.to_dict(include_self_link=False) + Catalog.from_dict(d) + + +class TestFullCopy: + def check_link(self, link: pystac.Link, tag: str) -> None: + if link.is_resolved(): + target_href: str = cast(pystac.STACObject, link.target).self_href + else: + target_href = str(link.target) + assert tag in target_href, ( + f'[{link.rel}] {target_href} does not contain "{tag}"' + ) + + def check_item(self, item: Item, tag: str) -> None: + for link in item.links: + self.check_link(link, tag) + + def check_catalog(self, c: Catalog, tag: str) -> None: + assert len(c.get_links("root")) == 1, f"Failure for catalog: {c}" + + for link in c.links: + self.check_link(link, tag) + + for child in c.get_children(): + self.check_catalog(child, tag) + + for item in c.get_items(): + self.check_item(item, tag) + + def test_full_copy_1(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + cat = Catalog(id="test", description="test catalog") + + item = Item( + id="test_item", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={}, + ) + + cat.add_item(item) + + cat.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-1-source")) + cat2 = cat.full_copy() + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-1-dest")) + + self.check_catalog(cat, "source") + self.check_catalog(cat2, "dest") + + def test_full_copy_2(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + cat = Catalog(id="test", description="test catalog") + image_item = Item( + id="Imagery", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={}, + ) + for key in ["ortho", "dsm"]: + image_item.add_asset( + key, + Asset(href=f"some/{key}.tif", media_type=MediaType.GEOTIFF), + ) + + label_item = Item( + id="Labels", + geometry=ARBITRARY_GEOM, + bbox=ARBITRARY_BBOX, + datetime=datetime.now(UTC), + properties={}, + ) + cat.add_items([image_item, label_item]) + + cat.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-2-source")) + cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + cat2 = cat.full_copy() + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-2-dest")) + cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + self.check_catalog(cat, "source") + self.check_catalog(cat2, "dest") + + def test_full_copy_3(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root_cat = TestCases.case_1() + root_cat.normalize_hrefs( + os.path.join(tmp_dir, "catalog-full-copy-3-source") + ) + root_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + cat2 = root_cat.full_copy() + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-3-dest")) + cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + self.check_catalog(root_cat, "source") + self.check_catalog(cat2, "dest") + + def test_full_copy_4(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root_cat = TestCases.case_2() + root_cat.normalize_hrefs( + os.path.join(tmp_dir, "catalog-full-copy-4-source") + ) + root_cat.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + cat2 = root_cat.full_copy() + cat2.normalize_hrefs(os.path.join(tmp_dir, "catalog-full-copy-4-dest")) + cat2.save(catalog_type=CatalogType.ABSOLUTE_PUBLISHED) + + self.check_catalog(root_cat, "source") + self.check_catalog(cat2, "dest") + + # Check that the relative asset link was saved correctly in the copy. + ID = "cf73ec1a-d790-4b59-b077-e101738571ed" + item = next(cat2.get_items(ID, recursive=True)) + href = item.assets[ID].get_absolute_href() + assert href is not None + assert os.path.exists(href) + + +class TestCatalogSubClass: + """This tests cases related to creating classes inheriting from pystac.Catalog to + ensure that inheritance, class methods, etc. function as expected.""" + + case_1 = TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + + class BasicCustomCatalog(pystac.Catalog): + def get_items(self) -> Iterator[Item]: # type: ignore + # This get_items does not have the `recursive` kwarg. This mimics + # the current state of pystac-client and is intended to test + # backwards compatibility of inherited classes + return super().get_items() + + def test_from_dict_returns_subclass(self) -> None: + self.stac_io = pystac.StacIO.default() + catalog_dict = self.stac_io.read_json(self.case_1) + custom_catalog = self.BasicCustomCatalog.from_dict(catalog_dict) + assert isinstance(custom_catalog, self.BasicCustomCatalog) + + def test_from_file_returns_subclass(self) -> None: + custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) + assert isinstance(custom_catalog, self.BasicCustomCatalog) + + def test_clone(self) -> None: + custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) + cloned_catalog = custom_catalog.clone() + assert isinstance(cloned_catalog, self.BasicCustomCatalog) + + def test_get_all_items_works(self) -> None: + custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) + cloned_catalog = custom_catalog.clone() + with pytest.warns(DeprecationWarning): + cloned_catalog.get_all_items() + + def test_get_item_works(self) -> None: + custom_catalog = self.BasicCustomCatalog.from_file(self.case_1) + cloned_catalog = custom_catalog.clone() + with pytest.warns(DeprecationWarning): + cloned_catalog.get_item("area-1-1-imagery") + + +def test_custom_catalog_from_dict(catalog: Catalog) -> None: + # https://github.com/stac-utils/pystac/issues/862 + class CustomCatalog(Catalog): + @classmethod + def from_dict( + cls, + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, + migrate: bool = True, + preserve_dict: bool = True, + ) -> CustomCatalog: + return super().from_dict(d) + + _ = CustomCatalog.from_dict(catalog.to_dict()) + + +@pytest.mark.parametrize("add_canonical", (True, False)) +def test_remove_hierarchical_links( + test_case_1_catalog: Catalog, add_canonical: bool +) -> None: + test_case_1_catalog.remove_hierarchical_links(add_canonical=add_canonical) + for link in test_case_1_catalog.links: + assert not link.is_hierarchical() + assert bool(test_case_1_catalog.get_single_link("canonical")) == add_canonical + + +def test_fully_resolve(tmp_path: Path, test_case_1_catalog: Catalog) -> None: + test_case_1_catalog.save(dest_href=str(tmp_path / "before")) + assert len(list((tmp_path / "before").glob("**/*.json"))) == 1 + test_case_1_catalog.fully_resolve() + test_case_1_catalog.save(dest_href=str(tmp_path / "after")) + assert len(list((tmp_path / "after").glob("**/*.json"))) == 15 + + +def test_get_items_with_multiple_ids(test_case_1_catalog: Catalog) -> None: + cat = test_case_1_catalog + items = cat.get_items("area-2-1-imagery", "area-1-1-labels", recursive=True) + assert len(list(items)) == 2 + + +@pytest.mark.vcr() +def test_validate_all_with_max_n(test_case_1_catalog: Catalog) -> None: + cat = test_case_1_catalog + assert cat.validate_all() == 8 + assert cat.validate_all(max_items=6) == 6 + assert cat.validate_all(max_items=1) == 1 + + +@pytest.mark.vcr() +def test_validate_all_with_recusive_off(test_case_1_catalog: Catalog) -> None: + cat = test_case_1_catalog + assert cat.validate_all() == 8 + assert cat.validate_all(recursive=False) == 0 + + +@pytest.fixture +def nested_catalog() -> pystac.Catalog: + """ + Structure: + + ├── catalog.json + ├── products + │ ├── catalog.json + │ └── product_a + │ └── catalog.json + └── variables + ├── catalog.json + └── variable_a + └── catalog.json + └── variable_a_1 + └── collection.json + """ + root = pystac.Catalog("root", "root") + variables = pystac.Catalog("variables", "variables") + products = pystac.Catalog("products", "products") + + root.add_child(variables) + root.add_child(products) + + variable_a = pystac.Catalog("variable_a", "variable_a") + product_a = pystac.Catalog("product_a", "product_a") + + variables.add_child(variable_a) + products.add_child(product_a) + + variable_a_1 = pystac.Collection( + "variable_a_1", "variable_a_1", extent=ARBITRARY_EXTENT + ) + variable_a.add_child(variable_a_1) + + return root + + +def test_get_all_collections_deeply_nested(nested_catalog: pystac.Catalog) -> None: + catalog = nested_catalog + all_collections = list(catalog.get_all_collections()) + + assert len(all_collections) == 1 + assert all(isinstance(c, pystac.Collection) for c in all_collections) + + +def test_set_parent_false_stores_in_proper_place_on_normalize_and_save( + nested_catalog: pystac.Catalog, tmp_path: Path +) -> None: + root = nested_catalog + product_a = next(root.get_child("products").get_children()) # type: ignore + variable_a = next(root.get_child("variables").get_children()) # type: ignore + + variable_a.add_child(product_a, set_parent=False) + + root.normalize_and_save( + root_href=str(tmp_path), catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED + ) + assert (tmp_path / "products" / "product_a").exists() + assert not (tmp_path / "variables" / "variable_a" / "product_a").exists() + + +def test_set_parent_false_stores_in_proper_place_on_save( + nested_catalog: pystac.Catalog, tmp_path: Path +) -> None: + nested_catalog.normalize_and_save( + root_href=str(tmp_path), catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED + ) + root = pystac.Catalog.from_file(tmp_path / "catalog.json") + product_a = next(root.get_child("products").get_children()) # type: ignore + variable_a = next(root.get_child("variables").get_children()) # type: ignore + + variable_a.add_child(product_a, set_parent=False) + + root.save(pystac.CatalogType.SELF_CONTAINED, dest_href=str(tmp_path)) + + assert (tmp_path / "products" / "product_a").exists() + assert not (tmp_path / "variables" / "variable_a" / "product_a").exists() + + +BEST_PRACTICE_CATALOG_TEMPLATE = "{id}" +BEST_PRACTICE_ITEM_TEMPLATE = "{id}" +TEST_CATALOG_TEMPLATE = "cat/${id}/${description}" +TEST_ITEM_TEMPLATE = "cat/items/${id}" +STRATEGY = TemplateLayoutStrategy( + catalog_template=TEST_CATALOG_TEMPLATE, item_template=TEST_ITEM_TEMPLATE +) + + +@pytest.mark.parametrize( + "root_strategy,sub_strategy,provided_root_strategy,provided_sub_strategy,root_template,sub_template", + [ + ( + None, + None, + None, + None, + BEST_PRACTICE_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + ), + (STRATEGY, None, None, None, TEST_CATALOG_TEMPLATE, TEST_CATALOG_TEMPLATE), + ( + STRATEGY, + BestPracticesLayoutStrategy(), + None, + None, + TEST_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + ), + ( + STRATEGY, + None, + BestPracticesLayoutStrategy(), + None, + BEST_PRACTICE_CATALOG_TEMPLATE, + TEST_CATALOG_TEMPLATE, + ), + ( + STRATEGY, + None, + None, + BestPracticesLayoutStrategy(), + TEST_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + ), + ], +) +def test_add_child_layout_strategy( + root_strategy: HrefLayoutStrategy, + sub_strategy: HrefLayoutStrategy, + provided_root_strategy: HrefLayoutStrategy, + provided_sub_strategy: HrefLayoutStrategy, + root_template: str, + sub_template: str, +) -> None: + """Test for layout strategy when adding a child. + + If no layout strategy is specified, children HREF should + always follow BestPracticesLayoutStrategy. + If only root strategy is set, all children HREFs should + follow than strategy. + If root and child strategy are set, root and child children + may follow different strategies. + Strategy provided to `add_child` always overrides other settings. + """ + + base_url = "http://example.com" + catalog = Catalog( + id="test", + description="test desc", + href=f"{base_url}/catalog.json", + strategy=root_strategy, + ) + subcat = Catalog(id="subcat", description="subcat desc", strategy=sub_strategy) + subsubcat = Catalog(id="subsubcat", description="subsubcat desc") + + catalog.add_child(subcat, strategy=provided_root_strategy) + subcat.add_child(subsubcat, strategy=provided_sub_strategy) + + root_template = root_template.format( + id=subcat.id, description=subcat.description + ).replace("$", "") + sub_template = sub_template.format( + id=subsubcat.id, description=subsubcat.description + ).replace("$", "") + + assert subcat.self_href == f"{base_url}/{root_template}/catalog.json" + assert ( + subsubcat.self_href == f"{base_url}/{root_template}/{sub_template}/catalog.json" + ) + + +@pytest.mark.parametrize( + "root_strategy,sub_strategy,provided_root_strategy," + "root_template,sub_template,norm_template", + [ + ( + None, + None, + None, + BEST_PRACTICE_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + ), + ( + STRATEGY, + None, + None, + TEST_CATALOG_TEMPLATE, + TEST_CATALOG_TEMPLATE, + TEST_CATALOG_TEMPLATE, + ), + ( + STRATEGY, + BestPracticesLayoutStrategy(), + None, + TEST_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + TEST_CATALOG_TEMPLATE, + ), + ( + STRATEGY, + None, + BestPracticesLayoutStrategy(), + TEST_CATALOG_TEMPLATE, + TEST_CATALOG_TEMPLATE, + BEST_PRACTICE_CATALOG_TEMPLATE, + ), + ], +) +def test_add_child_layout_strategy_normalize( + root_strategy: HrefLayoutStrategy, + sub_strategy: HrefLayoutStrategy, + provided_root_strategy: HrefLayoutStrategy, + root_template: str, + sub_template: str, + norm_template: str, +) -> None: + """Test for layout strategy when adding a child and normalizing HREFs. + + If no layout strategy is specified, children HREF + should always follow BestPracticesLayoutStrategy. + If only root strategy is set, all children HREFs + should follow than strategy. + If root and child strategy are set, root strategy + overrides child strategy. + Strategy provided to `normalize_href` always + overrides other settings. + """ + + base_url = "http://example.com" + catalog = Catalog( + id="test", + description="test desc", + href=f"{base_url}/catalog.json", + strategy=root_strategy, + ) + subcat = Catalog(id="subcat", description="subcat desc", strategy=sub_strategy) + subsubcat = Catalog(id="subsubcat", description="subsubcat desc") + catalog.add_child(subcat) + subcat.add_child(subsubcat) + + _root_template = root_template.format( + id=subcat.id, description=subcat.description + ).replace("$", "") + _sub_template = sub_template.format( + id=subsubcat.id, description=subsubcat.description + ).replace("$", "") + + assert subcat.self_href == f"{base_url}/{_root_template}/catalog.json" + assert ( + subsubcat.self_href + == f"{base_url}/{_root_template}/{_sub_template}/catalog.json" + ) + + catalog.normalize_hrefs(base_url, strategy=provided_root_strategy) + + _root_template = norm_template.format( + id=subcat.id, description=subcat.description + ).replace("$", "") + _sub_template = norm_template.format( + id=subsubcat.id, description=subsubcat.description + ).replace("$", "") + + assert subcat.self_href == f"{base_url}/{_root_template}/catalog.json" + assert ( + subsubcat.self_href + == f"{base_url}/{_root_template}/{_sub_template}/catalog.json" + ) + + +@pytest.mark.parametrize( + "root_strategy,provided_strategy,template", + [ + (None, None, BEST_PRACTICE_ITEM_TEMPLATE), + (STRATEGY, None, TEST_ITEM_TEMPLATE), + (STRATEGY, BestPracticesLayoutStrategy(), BEST_PRACTICE_ITEM_TEMPLATE), + ], +) +def test_add_item_layout_strategy( + root_strategy: HrefLayoutStrategy, + provided_strategy: HrefLayoutStrategy, + template: str, +) -> None: + """Test for layout strategy when adding an item. + + If no layout strategy is specified, item HREF + should always follow BestPracticesLayoutStrategy. + If only root strategy is set, all item HREFs + should follow than strategy. + Strategy provided to `add_item` always overrides other settings. + """ + + base_url = "http://example.com" + item_id = "item_id" + catalog = Catalog( + id="test", + description="test desc", + href=f"{base_url}/catalog.json", + strategy=root_strategy, + ) + item = Item( + id=item_id, + geometry={ + "type": "Polygon", + "coordinates": [ + [ + [180.0, -90.0], + [180.0, 90.0], + [-180.0, 90.0], + [-180.0, -90.0], + [180.0, -90.0], + ] + ], + }, + bbox=[-180, -90, 180, 90], + datetime=datetime(2023, 1, 1), + properties={}, + assets={ + "data": Asset( + href="http://example.com/assets/data.tif", + roles=["data"], + title="DATA", + ) + }, + ) + + catalog.add_item(item, strategy=provided_strategy) + + template = template.format(id=item.id).replace("$", "") + + assert item.self_href == f"{base_url}/{template}/{item_id}.json" + + +def test_APILayoutStrategy_requires_root_to_be_url( + catalog: Catalog, collection: Collection, item: Item +) -> None: + collection.add_item(item) + catalog.add_child(collection) + with pytest.raises( + pystac.errors.STACError, + match="When using APILayoutStrategy the root_href must be a URL", + ): + catalog.normalize_hrefs(root_href="issues-1486", strategy=APILayoutStrategy()) + + +def test_get_child_links_cares_about_media_type(catalog: pystac.Catalog) -> None: + catalog.links.extend( + [ + pystac.Link( + rel="child", target="./child-1.json", media_type="application/json" + ), + pystac.Link( + rel="child", target="./child-2.json", media_type="application/geo+json" + ), + pystac.Link(rel="child", target="./child-3.json"), + # this one won't get counted since it's the wrong media_type + pystac.Link(rel="child", target="./child.html", media_type="text/html"), + ] + ) + + assert len(catalog.get_child_links()) == 3 + + +def test_get_item_links_cares_about_media_type(catalog: pystac.Catalog) -> None: + catalog.links.extend( + [ + pystac.Link( + rel="item", target="./item-1.json", media_type="application/json" + ), + pystac.Link( + rel="item", target="./item-2.json", media_type="application/geo+json" + ), + pystac.Link(rel="item", target="./item-3.json"), + # this one won't get counted since it's the wrong media_type + pystac.Link(rel="item", target="./item.html", media_type="text/html"), + ] + ) + + assert len(catalog.get_item_links()) == 3 + + +def test_get_root_link_cares_about_media_type(catalog: pystac.Catalog) -> None: + catalog.links.insert( + 0, pystac.Link(rel="root", target="./self.json", media_type="text/html") + ) + root_link = catalog.get_root_link() + assert root_link and root_link.target != "./self.json" + + +def test_clone_extra_fields(catalog: Catalog) -> None: + catalog.extra_fields["foo"] = "bar" + cloned = catalog.clone() + assert cloned.extra_fields["foo"] == "bar" + + +def test_warn_if_next_link_present(catalog: Catalog) -> None: + catalog.links.append(Link(rel="next", target="./next.json")) + with pytest.warns(UserWarning): + _ = list(catalog.get_children()) diff --git a/tests/v1/test_collection.py b/tests/v1/test_collection.py new file mode 100644 index 000000000..28ad7bb2d --- /dev/null +++ b/tests/v1/test_collection.py @@ -0,0 +1,845 @@ +from __future__ import annotations + +import json +import os +import tempfile +from collections.abc import Iterator +from copy import deepcopy +from datetime import datetime +from typing import Any + +import pytest +from dateutil import tz + +import pystac +from pystac import ( + Asset, + Catalog, + CatalogType, + Collection, + Extent, + Item, + ItemCollection, + Provider, + SpatialExtent, + TemporalExtent, +) +from pystac.extensions.eo import EOExtension +from pystac.utils import datetime_to_str, get_required, str_to_datetime +from pystac.validation import validate_dict + +from .utils import ARBITRARY_BBOX, ARBITRARY_GEOM, TestCases + +TEST_DATETIME = datetime(2020, 3, 14, 16, 32) + + +def test_provider_to_from_dict() -> None: + provider_dict = { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": ["producer", "processor"], + "url": "http://remotedata.io", + "extension:field": "some value", + } + expected_extra_fields = {"extension:field": provider_dict["extension:field"]} + + provider = Provider.from_dict(provider_dict) + + assert ( + provider_dict["name"], + provider_dict["description"], + provider_dict["roles"], + provider_dict["url"], + expected_extra_fields, + provider_dict, + ) == ( + provider.name, + provider.description, + provider.roles, + provider.url, + provider.extra_fields, + provider.to_dict(), + ) + + +def test_spatial_extent_from_coordinates() -> None: + extent = SpatialExtent.from_coordinates(ARBITRARY_GEOM["coordinates"]) + + assert len(extent.bboxes) == 1 + bbox = extent.bboxes[0] + assert len(bbox) == 4 + for x in bbox: + assert isinstance(x, float) + + +def test_read_eo_items_are_heritable() -> None: + cat = TestCases.case_5() + item = next(cat.get_items(recursive=True)) + + assert EOExtension.has_extension(item) + + +def test_save_uses_previous_catalog_type() -> None: + collection = TestCases.case_8() + assert collection.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION + assert collection.catalog_type == CatalogType.SELF_CONTAINED + with tempfile.TemporaryDirectory() as tmp_dir: + collection.normalize_hrefs(tmp_dir) + href = collection.self_href + collection.save() + + collection2 = pystac.Collection.from_file(href) + assert collection2.catalog_type == CatalogType.SELF_CONTAINED + + +def test_clone_uses_previous_catalog_type() -> None: + catalog = TestCases.case_8() + assert catalog.catalog_type == CatalogType.SELF_CONTAINED + clone = catalog.clone() + assert clone.catalog_type == CatalogType.SELF_CONTAINED + + +def test_clone_cant_mutate_original() -> None: + collection = TestCases.case_8() + assert collection.keywords == ["disaster", "open"] + clone = collection.clone() + clone.extra_fields["test"] = "extra" + assert "test" not in collection.extra_fields + assert clone.keywords is not None + clone.keywords.append("clone") + assert clone.keywords == ["disaster", "open", "clone"] + assert collection.keywords == ["disaster", "open"] + assert id(collection.summaries) != id(clone.summaries) + + +def test_multiple_extents() -> None: + cat1 = TestCases.case_1() + country = cat1.get_child("country-1") + assert country is not None + col1 = country.get_child("area-1-1") + assert col1 is not None + col1.validate() + assert isinstance(col1, Collection) + validate_dict(col1.to_dict(), pystac.STACObjectType.COLLECTION) + + multi_ext_uri = TestCases.get_path("data-files/collections/multi-extent.json") + with open(multi_ext_uri) as f: + multi_ext_dict = json.load(f) + validate_dict(multi_ext_dict, pystac.STACObjectType.COLLECTION) + assert isinstance(Collection.from_dict(multi_ext_dict), Collection) + + multi_ext_col = Collection.from_file(multi_ext_uri) + multi_ext_col.validate() + ext = multi_ext_col.extent + extent_dict = multi_ext_dict["extent"] + assert isinstance(ext, Extent) + assert isinstance(ext.spatial.bboxes[0], list) + assert len(ext.spatial.bboxes) == 3 + assert ext.to_dict() == extent_dict + + cloned_ext = ext.clone() + assert cloned_ext.to_dict() == multi_ext_dict["extent"] + + +def test_extra_fields() -> None: + catalog = TestCases.case_2() + collection = catalog.get_child("1a8c1632-fa91-4a62-b33e-3a87c2ebdf16") + assert collection is not None + + collection.extra_fields["test"] = "extra" + + with tempfile.TemporaryDirectory() as tmp_dir: + p = os.path.join(tmp_dir, "collection.json") + collection.save_object(include_self_link=False, dest_href=p) + with open(p) as f: + col_json = json.load(f) + assert "test" in col_json + assert col_json["test"] == "extra" + + read_col = pystac.Collection.from_file(p) + assert "test" in read_col.extra_fields + assert read_col.extra_fields["test"] == "extra" + + +def test_update_extents() -> None: + catalog = TestCases.case_2() + base_collection = catalog.get_child("1a8c1632-fa91-4a62-b33e-3a87c2ebdf16") + assert isinstance(base_collection, Collection) + base_extent = base_collection.extent + collection = base_collection.clone() + + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-180, -90, 180, 90], + datetime=TEST_DATETIME, + properties={"key": "one"}, + stac_extensions=["eo", "commons"], + ) + + item2 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-180, -90, 180, 90], + datetime=None, + properties={ + "start_datetime": datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0)), + "end_datetime": datetime_to_str(datetime(2000, 2, 1, 12, 0, 0, 0)), + }, + stac_extensions=["eo", "commons"], + ) + + collection.add_item(item1) + + collection.update_extent_from_items() + assert [[-180, -90, 180, 90]] == collection.extent.spatial.bboxes + assert len(base_extent.spatial.bboxes[0]) == len( + collection.extent.spatial.bboxes[0] + ) + assert base_extent.temporal.intervals != collection.extent.temporal.intervals + + collection.remove_item("test-item-1") + collection.update_extent_from_items() + assert [[-180, -90, 180, 90]] != collection.extent.spatial.bboxes + collection.add_item(item2) + + collection.update_extent_from_items() + + assert [ + [ + item2.common_metadata.start_datetime, + base_extent.temporal.intervals[0][1], + ] + ] == collection.extent.temporal.intervals + + +def test_supplying_href_in_init_does_not_fail() -> None: + test_href = "http://example.com/collection.json" + spatial_extent = SpatialExtent(bboxes=[ARBITRARY_BBOX]) + temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) + + collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) + collection = Collection( + id="test", description="test desc", extent=collection_extent, href=test_href + ) + + assert collection.get_self_href() == test_href + + +def test_collection_with_href_caches_by_href() -> None: + collection = pystac.Collection.from_file( + TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") + ) + cache = collection._resolved_objects + + # Since all of our STAC objects have HREFs, everything should be + # cached only by HREF + assert len(cache.id_keys_to_objects) == 0 + + +@pytest.mark.block_network +def test_assets() -> None: + path = TestCases.get_path("data-files/collections/with-assets.json") + with open(path) as f: + data = json.load(f) + collection = pystac.Collection.from_dict(data) + collection.validate() + + +def test_get_assets() -> None: + collection = pystac.Collection.from_file( + TestCases.get_path("data-files/collections/with-assets.json") + ) + + media_type_filter = collection.get_assets(media_type=pystac.MediaType.PNG) + assert list(media_type_filter.keys()) == ["thumbnail"] + role_filter = collection.get_assets(role="thumbnail") + assert list(role_filter.keys()) == ["thumbnail"] + multi_filter = collection.get_assets( + media_type=pystac.MediaType.PNG, role="thumbnail" + ) + assert list(multi_filter.keys()) == ["thumbnail"] + + no_filter = collection.get_assets() + assert no_filter is not collection.assets + assert list(no_filter.keys()) == ["thumbnail"] + no_filter["thumbnail"].description = "foo" + assert collection.assets["thumbnail"].description != "foo" + + no_assets = collection.get_assets(media_type=pystac.MediaType.HDF) + assert no_assets == {} + + +def test_removing_optional_attributes() -> None: + path = TestCases.get_path("data-files/collections/with-assets.json") + with open(path) as file: + data = json.load(file) + data["title"] = "dummy title" + data["stac_extensions"] = ["dummy extension"] + data["keywords"] = ["key", "word"] + data["providers"] = [{"name": "pystac"}] + collection = pystac.Collection.from_dict(data) + + # Assert we have everything set + assert collection.title + assert collection.stac_extensions + assert collection.keywords + assert collection.providers + assert collection.summaries + assert collection.assets + + # Remove all of the optional stuff + collection.title = None + collection.stac_extensions = [] + collection.keywords = [] + collection.providers = [] + collection.summaries = pystac.Summaries({}) + collection.assets = {} + + collection_as_dict = collection.to_dict() + for key in ( + "title", + "stac_extensions", + "keywords", + "providers", + "summaries", + "assets", + ): + assert key not in collection_as_dict + + +def test_from_dict_preserves_dict() -> None: + path = TestCases.get_path("data-files/collections/with-assets.json") + with open(path) as f: + collection_dict = json.load(f) + param_dict = deepcopy(collection_dict) + + # test that the parameter is preserved + _ = Collection.from_dict(param_dict) + assert param_dict == collection_dict + + # assert that the parameter is not preserved with + # non-default parameter + _ = Collection.from_dict(param_dict, preserve_dict=False, migrate=False) + assert param_dict != collection_dict + + +def test_from_dict_set_root() -> None: + path = TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") + with open(path) as f: + collection_dict = json.load(f) + catalog = pystac.Catalog(id="test", description="test desc") + collection = Collection.from_dict(collection_dict, root=catalog) + assert collection.get_root() is catalog + + +def test_schema_summary() -> None: + collection = pystac.Collection.from_file( + TestCases.get_path( + "data-files/examples/1.0.0/collection-only/collection-with-schemas.json" + ) + ) + instruments_schema = get_required( + collection.summaries.get_schema("instruments"), + collection.summaries, + "instruments", + ) + + assert isinstance(instruments_schema, dict) + + +def test_from_invalid_dict_raises_exception() -> None: + stac_io = pystac.StacIO.default() + catalog_dict = stac_io.read_json( + TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + ) + with pytest.raises(pystac.STACTypeError): + _ = pystac.Collection.from_dict(catalog_dict) + + +def test_clone_preserves_assets() -> None: + path = TestCases.get_path("data-files/collections/with-assets.json") + original_collection = Collection.from_file(path) + assert len(original_collection.assets) > 0 + assert all( + asset.owner is original_collection + for asset in original_collection.assets.values() + ) + + cloned_collection = original_collection.clone() + + for key in original_collection.assets: + assert key in cloned_collection.assets, f"Failed to Preserve {key} asset" + cloned_asset = cloned_collection.assets.get(key) + if cloned_asset is not None: + assert cloned_asset.owner is cloned_collection, ( + f"Failed to set owner for {key}" + ) + + +def test_to_dict_no_self_href() -> None: + temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) + spatial_extent = SpatialExtent(bboxes=ARBITRARY_BBOX) + extent = Extent(spatial=spatial_extent, temporal=temporal_extent) + collection = Collection(id="an-id", description="A test Collection", extent=extent) + d = collection.to_dict(include_self_link=False) + Collection.from_dict(d) + + +def test_temporal_extent_init_typing() -> None: + # This test exists purely to test the typing of the intervals argument to + # TemporalExtent + start_datetime = str_to_datetime("2022-01-01T00:00:00Z") + end_datetime = str_to_datetime("2022-01-31T23:59:59Z") + + _ = TemporalExtent([[start_datetime, end_datetime]]) + + +@pytest.mark.block_network() +def test_temporal_extent_allows_single_interval() -> None: + start_datetime = str_to_datetime("2022-01-01T00:00:00Z") + end_datetime = str_to_datetime("2022-01-31T23:59:59Z") + + interval = [start_datetime, end_datetime] + temporal_extent = TemporalExtent(intervals=interval) + + assert temporal_extent.intervals == [interval] + + +@pytest.mark.block_network() +def test_temporal_extent_allows_single_interval_open_start() -> None: + end_datetime = str_to_datetime("2022-01-31T23:59:59Z") + + interval = [None, end_datetime] + temporal_extent = TemporalExtent(intervals=interval) + + assert temporal_extent.intervals == [interval] + + +@pytest.mark.block_network() +def test_temporal_extent_non_list_intervals_fails() -> None: + with pytest.raises(TypeError): + # Pass in non-list intervals + _ = TemporalExtent(intervals=1) # type: ignore + + +@pytest.mark.block_network() +def test_spatial_allows_single_bbox() -> None: + temporal_extent = TemporalExtent(intervals=[[TEST_DATETIME, None]]) + + # Pass in a single BBOX + spatial_extent = SpatialExtent(bboxes=ARBITRARY_BBOX) + + collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) + + collection = Collection( + id="test", description="test desc", extent=collection_extent + ) + + # HREF required by validation + collection.set_self_href("https://example.com/collection.json") + + collection.validate() + + +@pytest.mark.block_network() +def test_spatial_extent_non_list_bboxes_fails() -> None: + with pytest.raises(TypeError): + # Pass in non-list bboxes + _ = SpatialExtent(bboxes=1) # type: ignore + + +def test_extent_from_items() -> None: + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-10, -20, 0, -10], + datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + properties={}, + ) + + item2 = Item( + id="test-item-2", + geometry=ARBITRARY_GEOM, + bbox=[0, -9, 10, 1], + datetime=None, + properties={ + "start_datetime": datetime_to_str( + datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + ), + "end_datetime": datetime_to_str( + datetime(2000, 7, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + ), + }, + ) + + item3 = Item( + id="test-item-2", + geometry=ARBITRARY_GEOM, + bbox=[-5, -20, 5, 0], + datetime=None, + properties={ + "start_datetime": datetime_to_str( + datetime(2000, 12, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + ), + "end_datetime": datetime_to_str( + datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + ), + }, + ) + + extent = Extent.from_items([item1, item2, item3]) + assert len(extent.spatial.bboxes) == 1 + assert extent.spatial.bboxes[0] == [-10, -20, 10, 1] + assert len(extent.temporal.intervals) == 1 + + interval = extent.temporal.intervals[0] + assert interval[0] == datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + assert interval[1] == datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + + +def test_extent_to_from_dict() -> None: + spatial_dict = { + "bbox": [ + [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975, + ] + ], + "extension:field": "spatial value", + } + temporal_dict = { + "interval": [["2020-12-11T22:38:32.125000Z", "2020-12-14T18:02:31.437000Z"]], + "extension:field": "temporal value", + } + extent_dict = { + "spatial": spatial_dict, + "temporal": temporal_dict, + "extension:field": "extent value", + } + expected_extent_extra_fields = { + "extension:field": extent_dict["extension:field"], + } + expected_spatial_extra_fields = { + "extension:field": spatial_dict["extension:field"], + } + expected_temporal_extra_fields = { + "extension:field": temporal_dict["extension:field"], + } + + extent = Extent.from_dict(extent_dict) + + assert expected_extent_extra_fields == extent.extra_fields + assert expected_spatial_extra_fields == extent.spatial.extra_fields + assert expected_temporal_extra_fields == extent.temporal.extra_fields + + assert extent_dict == extent.to_dict() + + +class TestCollectionSubClass: + """This tests cases related to creating classes inheriting from pystac.Catalog to + ensure that inheritance, class methods, etc. function as expected.""" + + MULTI_EXTENT = TestCases.get_path("data-files/collections/multi-extent.json") + + class BasicCustomCollection(pystac.Collection): + def get_items(self) -> Iterator[Item]: # type: ignore + # This get_items does not have the `recursive` kwarg. This mimics + # the current state of pystac-client and is intended to test + # backwards compatibility of inherited classes + return super().get_items() + + def test_from_dict_returns_subclass(self) -> None: + stac_io = pystac.StacIO.default() + collection_dict = stac_io.read_json(self.MULTI_EXTENT) + custom_collection = self.BasicCustomCollection.from_dict(collection_dict) + + assert isinstance(custom_collection, self.BasicCustomCollection) + + def test_from_file_returns_subclass(self) -> None: + custom_collection = self.BasicCustomCollection.from_file(self.MULTI_EXTENT) + + assert isinstance(custom_collection, self.BasicCustomCollection) + + def test_clone(self) -> None: + custom_collection = self.BasicCustomCollection.from_file(self.MULTI_EXTENT) + cloned_collection = custom_collection.clone() + + assert isinstance(cloned_collection, self.BasicCustomCollection) + + def test_collection_get_item_works(self) -> None: + path = TestCases.get_path( + "data-files/catalogs/test-case-1/country-1/area-1-1/collection.json" + ) + custom_collection = self.BasicCustomCollection.from_file(path) + collection = custom_collection.clone() + with pytest.warns(DeprecationWarning): + collection.get_item("area-1-1-imagery") + + +def test_collection_get_item_raises_type_error() -> None: + class BasicCustomCollection(pystac.Collection): + def get_items( # type: ignore + self, *, recursive: bool = False + ) -> Iterator[Item]: + # This get_items does not allow ids as args. + return super().get_items(recursive=recursive) + + path = TestCases.get_path( + "data-files/catalogs/test-case-1/country-1/area-1-1/collection.json" + ) + custom_collection = BasicCustomCollection.from_file(path) + collection = custom_collection.clone() + with pytest.raises(TypeError, match="takes 1 positional argument"): + collection.get_item("area-1-1-imagery") + + +def test_custom_collection_from_dict(collection: Collection) -> None: + # https://github.com/stac-utils/pystac/issues/862 + class CustomCollection(Collection): + @classmethod + def from_dict( + cls, + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, + migrate: bool = True, + preserve_dict: bool = True, + ) -> CustomCollection: + return super().from_dict(d) + + _ = CustomCollection.from_dict(collection.to_dict()) + + +@pytest.mark.parametrize("add_canonical", (True, False)) +def test_remove_hierarchical_links( + test_case_1_catalog: Catalog, add_canonical: bool +) -> None: + collection = list(test_case_1_catalog.get_all_collections())[0] + collection.remove_hierarchical_links(add_canonical=add_canonical) + for link in collection.links: + assert not link.is_hierarchical() + assert bool(collection.get_single_link("canonical")) == add_canonical + + +@pytest.mark.parametrize("child", ["country-1", "country-2"]) +def test_get_child_checks_links_where_hrefs_contains_id_first( + test_case_1_catalog: Catalog, child: str +) -> None: + cat1 = test_case_1_catalog + country = cat1.get_child(child) + assert country is not None + child_links = [link for link in cat1.links if link.rel == pystac.RelType.CHILD] + for link in child_links: + if country.id not in link.href: + assert not link.is_resolved() + + +def test_get_child_sort_links_by_id_is_configurable( + test_case_1_catalog: Catalog, +) -> None: + cat1 = test_case_1_catalog + country = cat1.get_child("country-2", sort_links_by_id=False) + assert country is not None + child_links = [link for link in cat1.links if link.rel == pystac.RelType.CHILD] + for link in child_links: + assert link.is_resolved() + + +def test_get_item_returns_none_if_not_found( + test_case_8_collection: Collection, +) -> None: + col8 = test_case_8_collection + item = col8.get_item("notarealitem") + assert item is None + + +def test_get_item_is_not_recursive_by_default( + test_case_8_collection: Collection, +) -> None: + col8 = test_case_8_collection + item = col8.get_item("20170831_162740_ssc1d1") + assert item is None + + item = col8.get_item("20170831_162740_ssc1d1", recursive=True) + assert item is not None + + +def test_delete_asset(tmp_asset: Asset, collection: Collection) -> None: + asset = tmp_asset + href = asset.get_absolute_href() + item = asset.owner + name = "foo" + + assert href is not None + assert item is not None + + collection.add_asset(name, asset) + + # steal the href from the owner and use it as the collection's + collection.set_self_href(item.get_self_href()) + + collection.delete_asset(name) + assert name not in collection.assets + assert not os.path.exists(href) + + +def test_delete_asset_relative_no_self_link_fails( + tmp_asset: Asset, collection: Collection +) -> None: + asset = tmp_asset + href = asset.get_absolute_href() + name = "foo" + + assert href is not None + + collection.add_asset(name, asset) + + with pytest.raises(ValueError, match="Cannot delete file") as e: + collection.delete_asset(name) + + assert asset.href in str(e.value) + assert name in collection.assets + assert os.path.exists(href) + + +def test_permissive_temporal_extent_deserialization(collection: Collection) -> None: + # https://github.com/stac-utils/pystac/issues/1221 + collection_dict = collection.to_dict(include_self_link=False, transform_hrefs=False) + collection_dict["extent"]["temporal"]["interval"] = collection_dict["extent"][ + "temporal" + ]["interval"][0] + with pytest.warns(UserWarning): + Collection.from_dict(collection_dict) + + +@pytest.mark.parametrize("fixture_name", ("sample_item_collection", "sample_items")) +def test_from_items(fixture_name: str, request: pytest.FixtureRequest) -> None: + items = request.getfixturevalue(fixture_name) + collection = Collection.from_items(items) + + for item in items: + assert collection.id == item.collection_id + assert collection.extent.spatial.bboxes[0][0] <= item.bbox[0] + assert collection.extent.spatial.bboxes[0][1] <= item.bbox[1] + assert collection.extent.spatial.bboxes[0][2] >= item.bbox[2] + assert collection.extent.spatial.bboxes[0][3] >= item.bbox[3] + + start = collection.extent.temporal.intervals[0][0] + end = collection.extent.temporal.intervals[0][1] + assert start and start <= str_to_datetime(item.properties["start_datetime"]) + assert end and end >= str_to_datetime(item.properties["end_datetime"]) + + if isinstance(items, ItemCollection): + expected = {(link["rel"], link["href"]) for link in items.extra_fields["links"]} + actual = {(link.rel, link.href) for link in collection.links} + assert expected.issubset(actual) + + +def test_from_items_pulls_from_properties() -> None: + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-10, -20, 0, -10], + datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + collection="test-collection-1", + properties={"title": "Test Item", "description": "Extra words describing"}, + ) + collection = Collection.from_items([item1]) + assert collection.id == item1.collection_id + assert collection.title == item1.properties["title"] + assert collection.description == item1.properties["description"] + + +def test_from_items_without_collection_id() -> None: + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-10, -20, 0, -10], + datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + properties={}, + ) + with pytest.raises(ValueError, match="Collection id must be defined."): + Collection.from_items([item1]) + + collection = Collection.from_items([item1], id="test-collection") + assert collection.id == "test-collection" + + +def test_from_items_with_collection_ids() -> None: + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-10, -20, 0, -10], + datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + collection="test-collection-1", + properties={}, + ) + item2 = Item( + id="test-item-2", + geometry=ARBITRARY_GEOM, + bbox=[-15, -20, 0, -10], + datetime=datetime(2000, 2, 1, 13, 0, 0, 0, tzinfo=tz.UTC), + collection="test-collection-2", + properties={}, + ) + + with pytest.raises(ValueError, match="Collection id must be defined."): + Collection.from_items([item1, item2]) + + collection = Collection.from_items([item1, item2], id="test-collection") + assert collection.id == "test-collection" + + +def test_from_items_with_different_values() -> None: + item1 = Item( + id="test-item-1", + geometry=ARBITRARY_GEOM, + bbox=[-10, -20, 0, -10], + datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC), + properties={"title": "Test Item 1"}, + ) + item2 = Item( + id="test-item-2", + geometry=ARBITRARY_GEOM, + bbox=[-15, -20, 0, -10], + datetime=datetime(2000, 2, 1, 13, 0, 0, 0, tzinfo=tz.UTC), + properties={"title": "Test Item 2"}, + ) + + collection = Collection.from_items([item1, item2], id="test_collection") + assert collection.title is None + + +def test_from_items_with_providers(sample_item_collection: ItemCollection) -> None: + sample_item_collection.extra_fields["providers"] = [{"name": "pystac"}] + + collection = Collection.from_items(sample_item_collection) + assert collection.providers and len(collection.providers) == 1 + + provider = collection.providers[0] + assert provider and provider.name == "pystac" + + +def test_from_dict_null_extent(collection: Collection) -> None: + # https://github.com/stac-utils/pystac/issues/1558 + # https://github.com/EOPF-Sample-Service/eopf-stac/issues/18 + d = collection.to_dict() + d["extent"] = None + with pytest.warns(UserWarning): + c = Collection.from_dict(d) + + assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] + assert c.extent.temporal.to_dict()["interval"] == [[None, None]] + + +def test_from_dict_missing_extent(collection: Collection) -> None: + d = collection.to_dict() + del d["extent"] + with pytest.warns(UserWarning): + c = Collection.from_dict(d) + + assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] + assert c.extent.temporal.to_dict()["interval"] == [[None, None]] diff --git a/tests/test_common_metadata.py b/tests/v1/test_common_metadata.py similarity index 99% rename from tests/test_common_metadata.py rename to tests/v1/test_common_metadata.py index 166e530b2..579d6c1ab 100644 --- a/tests/test_common_metadata.py +++ b/tests/v1/test_common_metadata.py @@ -4,7 +4,8 @@ import pytest from pystac import CommonMetadata, Item, Provider, ProviderRole, utils -from tests.utils import TestCases + +from .utils import TestCases @pytest.fixture diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py new file mode 100644 index 000000000..6ce2ff3cb --- /dev/null +++ b/tests/v1/test_item.py @@ -0,0 +1,695 @@ +from __future__ import annotations + +import copy +import json +import os +import pickle +import tempfile +from copy import deepcopy +from pathlib import Path +from typing import Any, cast + +import dateutil.relativedelta +import pytest + +import pystac +import pystac.serialization.common_properties +from pystac import Asset, Catalog, Collection, Item, Link, STACValidationError +from pystac.utils import ( + datetime_to_str, + get_opt, + is_absolute_href, + make_posix_style, + str_to_datetime, +) +from pystac.validation import validate_dict + +from .utils import TestCases, assert_to_from_dict + + +def test_to_from_dict(sample_item_dict: dict[str, Any]) -> None: + param_dict = deepcopy(sample_item_dict) + + assert_to_from_dict(Item, param_dict) + item = Item.from_dict(param_dict) + assert item.id == "CS3-20160503_132131_05" + + # test asset creation additional field(s) + assert ( + item.assets["analytic"].extra_fields["product"] + == "http://cool-sat.com/catalog/products/analytic.json" + ) + assert len(item.assets["thumbnail"].extra_fields) == 0 + + # test that the parameter is preserved + assert param_dict == sample_item_dict + + # assert that the parameter is preserved regardless of preserve_dict + Item.from_dict(param_dict, preserve_dict=False) + assert param_dict == sample_item_dict + + +def test_from_dict_set_root(sample_item_dict: dict[str, Any]) -> None: + catalog = pystac.Catalog(id="test", description="test desc") + item = Item.from_dict(sample_item_dict, root=catalog) + assert item.get_root() is catalog + + +def test_set_self_href_does_not_break_asset_hrefs() -> None: + cat = TestCases.case_2() + for item in cat.get_items(recursive=True): + for asset in item.assets.values(): + if is_absolute_href(asset.href): + asset.href = f"./{os.path.basename(asset.href)}" + item.set_self_href("http://example.com/item.json") + for asset in item.assets.values(): + assert is_absolute_href(asset.href) + + +def test_set_self_href_none_ignores_relative_asset_hrefs() -> None: + cat = TestCases.case_2() + for item in cat.get_items(recursive=True): + for asset in item.assets.values(): + if is_absolute_href(asset.href): + asset.href = f"./{os.path.basename(asset.href)}" + item.set_self_href(None) + for asset in item.assets.values(): + assert not is_absolute_href(asset.href) + + +def test_asset_absolute_href(sample_item: Item) -> None: + item_path = TestCases.get_path("data-files/item/sample-item.json") + sample_item.set_self_href(item_path) + rel_asset = Asset("./data.geojson") + rel_asset.set_owner(sample_item) + expected_href = make_posix_style( + os.path.abspath(os.path.join(os.path.dirname(item_path), "./data.geojson")) + ) + actual_href = rel_asset.get_absolute_href() + assert expected_href == actual_href + + +def test_asset_absolute_href_no_item_self(sample_item_dict: dict[str, Any]) -> None: + item = Item.from_dict(sample_item_dict) + assert item.get_self_href() is None + + rel_asset = Asset("./data.geojson") + rel_asset.set_owner(item) + actual_href = rel_asset.get_absolute_href() + assert actual_href is None + + +def test_item_field_order() -> None: + item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + item_dict = item.to_dict(include_self_link=False) + expected_order = [ + "type", + "stac_version", + "stac_extensions", + "id", + "geometry", + "bbox", + "properties", + "links", + "assets", + "collection", + ] + actual_order = list(item_dict.keys()) + assert actual_order == expected_order + + +def test_extra_fields() -> None: + item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + + item.extra_fields["test"] = "extra" + + with tempfile.TemporaryDirectory() as tmp_dir: + p = os.path.join(tmp_dir, "item.json") + item.save_object(include_self_link=False, dest_href=p) + with open(p) as f: + item_json = json.load(f) + assert "test" in item_json + assert item_json["test"] == "extra" + + read_item = pystac.Item.from_file(p) + assert "test" in read_item.extra_fields + assert read_item.extra_fields["test"] == "extra" + + +def test_clearing_collection() -> None: + collection = TestCases.case_4().get_child("acc") + assert isinstance(collection, pystac.Collection) + item = next(collection.get_items(recursive=True)) + assert item.collection_id == collection.id + item.set_collection(None) + assert item.collection_id is None + assert item.get_collection() is None + item.set_collection(collection) + assert item.collection_id == collection.id + assert item.get_collection() is collection + + +def test_datetime_ISO8601_format(sample_item: Item) -> None: + formatted_time = sample_item.to_dict()["properties"]["datetime"] + assert "2016-05-03T13:22:30.040000Z" == formatted_time + + +@pytest.mark.vcr() +def test_null_datetime() -> None: + item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + + with pytest.raises(pystac.STACError): + Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + properties={}, + ) + + null_dt_item = Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + properties={ + "start_datetime": datetime_to_str(get_opt(item.datetime)), + "end_datetime": datetime_to_str(get_opt(item.datetime)), + }, + ) + + null_dt_item.validate() + + +def test_get_assets() -> None: + item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + + media_type_filter = item.get_assets(media_type=pystac.MediaType.COG) + assert set(media_type_filter.keys()) == {"analytic"} + role_filter = item.get_assets(role="data") + assert set(role_filter.keys()) == {"analytic"} + multi_filter = item.get_assets(media_type=pystac.MediaType.PNG, role="thumbnail") + assert set(multi_filter.keys()) == {"thumbnail"} + multi_filter["thumbnail"].description = "foo" + assert item.assets["thumbnail"].description != "foo" + + no_filter = item.get_assets() + assert set(no_filter.keys()) == {"analytic", "thumbnail"} + no_assets = item.get_assets(media_type=pystac.MediaType.HDF) + assert no_assets == {} + + +@pytest.mark.vcr() +def test_null_datetime_constructor() -> None: + item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + with pytest.raises(pystac.STACError): + Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + end_datetime=item.datetime, + properties={}, + ) + with pytest.raises(pystac.STACError): + Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + start_datetime=item.datetime, + properties={}, + ) + assert item.datetime + null_dt_item = Item( + "test", + geometry=item.geometry, + bbox=item.bbox, + datetime=None, + start_datetime=item.datetime, + end_datetime=item.datetime + dateutil.relativedelta.relativedelta(days=1), + properties={}, + ) + null_dt_item.validate() + + +def test_get_set_asset_datetime() -> None: + item = pystac.Item.from_file( + TestCases.get_path("data-files/item/sample-item-asset-properties.json") + ) + item_datetime = item.datetime + + # No property on asset + assert item.get_datetime(item.assets["thumbnail"]) == item.datetime + + # Property on asset + assert item.get_datetime(item.assets["analytic"]) != item.datetime + assert item.get_datetime(item.assets["analytic"]) == str_to_datetime( + "2017-05-03T13:22:30.040Z" + ) + + item.set_datetime( + str_to_datetime("2018-05-03T13:22:30.040Z"), item.assets["thumbnail"] + ) + assert item.get_datetime() == item_datetime + assert item.get_datetime(item.assets["thumbnail"]) == str_to_datetime( + "2018-05-03T13:22:30.040Z" + ) + + +def test_read_eo_item_owns_asset() -> None: + item = next(TestCases.case_1().get_items(recursive=True)) + assert len(item.assets) > 0 + for asset_key in item.assets: + assert item.assets[asset_key].owner == item + + +@pytest.mark.vcr() +def test_null_geometry() -> None: + m = TestCases.get_path( + "data-files/examples/1.0.0-beta.2/item-spec/examples/null-geom-item.json" + ) + with open(m) as f: + item_dict = json.load(f) + + validate_dict(item_dict, pystac.STACObjectType.ITEM) + + item = Item.from_dict(item_dict) + assert isinstance(item, Item) + item.validate() + + item_dict = item.to_dict() + assert item_dict["geometry"] is None + assert "bbox" not in item_dict + + +def test_0_9_item_with_no_extensions_does_not_read_collection_data() -> None: + item_json = pystac.StacIO.default().read_json( + TestCases.get_path("data-files/examples/hand-0.9.0/010100/010100.json") + ) + assert item_json.get("stac_extensions") is None + assert item_json.get("stac_version") == "0.9.0" + + did_merge = pystac.serialization.common_properties.merge_common_properties( + item_json + ) + assert not did_merge + + +def test_clone_preserves_assets() -> None: + cat = TestCases.case_2() + original_item = next(cat.get_items(recursive=True)) + assert len(original_item.assets) > 0 + assert all(asset.owner is original_item for asset in original_item.assets.values()) + + cloned_item = original_item.clone() + + for key in original_item.assets: + assert key in cloned_item.assets, f"Failed to preserve asset {key}" + cloned_asset = cloned_item.assets.get(key) + if cloned_asset is not None: + assert cloned_asset.owner is cloned_item, f"Failed set owner for {key}" + + +def test_make_asset_href_relative_is_noop_on_relative_hrefs() -> None: + cat = TestCases.case_2() + item = next(cat.get_items(recursive=True)) + asset = list(item.assets.values())[0] + assert not is_absolute_href(asset.href) + original_href = asset.get_absolute_href() + + item.make_asset_hrefs_relative() + assert asset.get_absolute_href() == original_href + + +def test_from_invalid_dict_raises_exception() -> None: + stac_io = pystac.StacIO.default() + catalog_dict = stac_io.read_json( + TestCases.get_path("data-files/catalogs/test-case-1/catalog.json") + ) + with pytest.raises(pystac.STACTypeError): + _ = pystac.Item.from_dict(catalog_dict) + + +@pytest.mark.vcr() +def test_relative_extension_path() -> None: + item = pystac.Item.from_file( + TestCases.get_path( + "data-files/item/sample-item-with-relative-extension-path.json" + ) + ) + item.validate() + + +class TestItemSubClass: + """This tests cases related to creating classes inheriting from pystac.Catalog to + ensure that inheritance, class methods, etc. function as expected.""" + + SAMPLE_ITEM = TestCases.get_path("data-files/item/sample-item.json") + + class BasicCustomItem(pystac.Item): + pass + + def test_from_dict_returns_subclass(self) -> None: + stac_io = pystac.StacIO.default() + item_dict = stac_io.read_json(self.SAMPLE_ITEM) + custom_item = self.BasicCustomItem.from_dict(item_dict) + + assert isinstance(custom_item, self.BasicCustomItem) + + def test_from_file_returns_subclass(self) -> None: + custom_item = self.BasicCustomItem.from_file(self.SAMPLE_ITEM) + + assert isinstance(custom_item, self.BasicCustomItem) + + def test_clone(self) -> None: + custom_item = self.BasicCustomItem.from_file(self.SAMPLE_ITEM) + cloned_item = custom_item.clone() + + assert isinstance(cloned_item, self.BasicCustomItem) + + +def test_asset_clone() -> None: + with open(TestCases.get_path("data-files/item/sample-item.json")) as src: + item_dict = json.load(src) + asset_dict = item_dict["assets"]["analytic"] + original_asset = Asset.from_dict(asset_dict) + + cloned_asset = original_asset.clone() + + assert original_asset.to_dict() == asset_dict + assert cloned_asset.to_dict() == asset_dict + + # Changes to original asset should not affect cloned Asset + original_asset.description = "Some new description" + original_asset.href = "/path/to/new/href" + original_asset.title = "New Title" + original_asset.roles = ["new role"] + original_asset.roles.append("new role") + original_asset.extra_fields["new_field"] = "new_value" + assert cloned_asset.to_dict() == asset_dict + + +class TestAssetSubClass: + class CustomAsset(Asset): + pass + + AssetDict = dict[str, str | list[str]] + + @pytest.fixture + def asset_dict(self) -> AssetDict: + with open(TestCases.get_path("data-files/item/sample-item.json")) as src: + item_dict = json.load(src) + return cast(TestAssetSubClass.AssetDict, item_dict["assets"]["analytic"]) + + def test_from_dict(self, asset_dict: AssetDict) -> None: + asset = self.CustomAsset.from_dict(asset_dict) + assert isinstance(asset, self.CustomAsset) + + def test_clone(self, asset_dict: AssetDict) -> None: + asset = self.CustomAsset.from_dict(asset_dict) + cloned_asset = asset.clone() + assert isinstance(cloned_asset, self.CustomAsset) + + +def test_custom_item_from_dict(item: Item) -> None: + # https://github.com/stac-utils/pystac/issues/862 + class CustomItem(Item): + @classmethod + def from_dict( + cls, + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, + migrate: bool = True, + preserve_dict: bool = True, + ) -> CustomItem: + return super().from_dict(d) + + _ = CustomItem.from_dict(item.to_dict()) + + +def test_item_from_dict_raises_useful_error() -> None: + item_dict = {"type": "Feature", "stac_version": "1.1.0", "id": "lalalalala"} + with pytest.raises(pystac.STACError, match="Invalid Item: "): + Item.from_dict(item_dict) + + +def test_item_from_dict_with_missing_stac_version_raises_useful_error() -> None: + item_dict = {"type": "Feature", "id": "lalalalala"} + with pytest.raises(pystac.STACTypeError, match="'stac_version' is missing"): + Item.from_dict(item_dict, migrate=False) + + +def test_item_from_dict_with_missing_type_raises_useful_error() -> None: + item_dict = {"stac_version": "0.8.0", "id": "lalalalala"} + with pytest.raises(pystac.STACTypeError, match="'type' is missing"): + Item.from_dict(item_dict, migrate=False) + + +@pytest.mark.parametrize("add_canonical", (True, False)) +def test_remove_hierarchical_links( + test_case_1_catalog: Catalog, add_canonical: bool +) -> None: + item = next(test_case_1_catalog.get_items(recursive=True)) + item.remove_hierarchical_links(add_canonical=add_canonical) + for link in item.links: + assert not link.is_hierarchical() + assert bool(item.get_single_link("canonical")) == add_canonical + + +def test_geo_interface() -> None: + item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) + assert ( + item.to_dict(include_self_link=False, transform_hrefs=False) + == item.__geo_interface__ + ) + + +def test_duplicate_self_links(tmp_path: Path, sample_item: pystac.Item) -> None: + # https://github.com/stac-utils/pystac/issues/1102 + assert len(sample_item.get_links(rel="self")) == 1 + path = tmp_path / "item.json" + sample_item.save_object(include_self_link=True, dest_href=str(path)) + sample_item = Item.from_file(path) + assert len(sample_item.get_links(rel="self")) == 1 + + +def test_get_derived_from_when_none_exists(test_case_1_catalog: Catalog) -> None: + item = next(test_case_1_catalog.get_items(recursive=True)) + assert item.get_derived_from() == [] + for link in item.links: + assert link.rel != pystac.RelType.DERIVED_FROM + assert item.get_single_link(pystac.RelType.DERIVED_FROM) is None + + +def test_add_derived_from(test_case_1_catalog: Catalog) -> None: + items = list(test_case_1_catalog.get_items(recursive=True)) + item_0 = items[0] + item_1 = items[1] + item_2 = items[2] + item_0.add_derived_from(item_1, item_2.self_href) + derived_from = item_0.get_derived_from() + assert len(derived_from) == 2 + assert derived_from[0].id == item_1.id + assert derived_from[1].id == item_2.id + filtered = [ + link for link in item_0.links if link.rel == pystac.RelType.DERIVED_FROM + ] + assert len(filtered) == 2 + assert filtered[0].to_dict(transform_href=False)["href"] == item_1.self_href + assert filtered[1].to_dict(transform_href=False)["href"] == item_2.self_href + + +def test_get_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: + item = next(test_case_1_catalog.get_items(recursive=True)) + item.add_derived_from("foo") + with pytest.raises( + pystac.STACError, match="Link failed to resolve. Use get_links instead" + ): + item.get_derived_from() + + links = item.get_links(pystac.RelType.DERIVED_FROM) + assert len(links) == 1 + + +def test_remove_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: + item = next(test_case_1_catalog.get_items(recursive=True)) + item.add_derived_from("foo") + with pytest.raises( + pystac.STACError, match="Link failed to resolve. Use remove_links instead" + ): + item.remove_derived_from("foo") + + item.remove_links(pystac.RelType.DERIVED_FROM) + assert item.get_derived_from() == [] + + +def test_remove_derived_from(test_case_1_catalog: Catalog) -> None: + items = list(test_case_1_catalog.get_items(recursive=True)) + item_0 = items[0] + item_1 = items[1] + item_0.add_derived_from(item_1) + item_0.remove_derived_from(item_1.id) + assert item_0.get_derived_from() == [] + for link in item_0.links: + assert link.rel != pystac.RelType.DERIVED_FROM + assert item_0.get_single_link(pystac.RelType.DERIVED_FROM) is None + + +def test_delete_asset(tmp_asset: Asset) -> None: + asset = tmp_asset + href = asset.get_absolute_href() + item = asset.owner + + assert href is not None + assert item is not None + + name = next(k for k in item.assets.keys() if item.assets[k] == asset) + item.delete_asset(name) + + assert name not in item.assets + assert not os.path.exists(href) + + +def test_delete_asset_relative_no_self_link_fails(tmp_asset: pystac.Asset) -> None: + asset = tmp_asset + href = asset.get_absolute_href() + item = asset.owner + + assert href is not None + assert item is not None + assert isinstance(item, pystac.Item) + + item.set_self_href(None) + + name = next(k for k in item.assets.keys() if item.assets[k] == asset) + with pytest.raises(ValueError, match="Cannot delete file") as e: + item.delete_asset(name) + + assert asset.href in str(e.value) + assert name in item.assets + assert os.path.exists(href) + + +def test_resolve_collection_with_root( + tmp_path: Path, item: Item, collection: Collection +) -> None: + # Motivated by https://github.com/stac-utils/pystac-client/issues/548 + catalog = Catalog("root", "the description") + item.set_root(catalog) + + collection_path = str(tmp_path / "collection.json") + collection.save_object( + include_self_link=False, + dest_href=collection_path, + ) + item.add_link(Link(rel="collection", target=collection_path)) + + read_collection = item.get_collection() + assert read_collection + root = read_collection.get_root() + assert root + assert root.id == "root" + + +@pytest.mark.vcr() +def test_non_hierarchical_relative_link() -> None: + root = pystac.Catalog("root", "root") + a = pystac.Catalog("a", "a") + b = pystac.Catalog("b", "b") + + root.add_child(a) + root.add_child(b) + a.add_link(pystac.Link("related", b)) + b.add_link( + pystac.Link("item", TestCases.get_path("data-files/item/sample-item.json")) + ) + + root.catalog_type = pystac.catalog.CatalogType.SELF_CONTAINED + root.normalize_hrefs("test_output") + related_href = [link for link in a.links if link.rel == "related"][0].get_href() + + assert related_href is not None and not is_absolute_href(related_href) + assert a.target_in_hierarchy(b) + assert root.target_in_hierarchy(next(b.get_items())) + assert root.target_in_hierarchy(root) + + +def test_pathlib() -> None: + # This works, but breaks mypy until we fix + # https://github.com/stac-utils/pystac/issues/1216 + Item.from_file(Path(TestCases.get_path("data-files/item/sample-item.json"))) + + +def test_invalid_error_message(item: Item) -> None: + item.extra_fields["collection"] = "can't have a collection" + with pytest.raises(STACValidationError) as error: + item.validate() + assert "can't have a collection" in str(error.value) + + +def test_pickle_with_no_links(item: Item) -> None: + roundtripped = pickle.loads(pickle.dumps(item)) + for attr in ["id", "geometry", "bbox", "datetime", "links"]: + assert getattr(roundtripped, attr) == getattr(item, attr) + + +def test_pickle_with_hrefless_links(item: Item) -> None: + root = pystac.Catalog("root", "root") + a = pystac.Catalog("a", "a") + + item.add_link(pystac.Link("related", a)) + item.add_link( + pystac.Link("item", TestCases.get_path("data-files/item/sample-item.json")) + ) + item.set_root(root) + + roundtripped = pickle.loads(pickle.dumps(item)) + for original, new in zip(item.links, roundtripped.links): + assert original.rel == new.rel + assert original.media_type == new.media_type + assert str(original.owner) == str(new.owner) + assert str(original.target) == str(new.target) + + +def test_pickle_with_only_href_links(item: Item) -> None: + item.add_link( + pystac.Link("item", TestCases.get_path("data-files/item/sample-item.json")) + ) + + roundtripped = pickle.loads(pickle.dumps(item)) + for original, new in zip(item.links, roundtripped.links): + assert original.rel == new.rel + assert original.media_type == new.media_type + assert str(original.owner) == str(new.owner) + assert str(original.target) == str(new.target) + + +def test_copy_with_unresolveable_root(item: Item) -> None: + item.add_link( + pystac.Link( + "root", "s3://naip-visualization/this-is-a-non-existent-catalog.json" + ) + ) + copy.deepcopy(item) + + +def test_no_collection(item: Item) -> None: + # https://github.com/stac-utils/stac-api-validator/issues/527 + assert item.collection is None + + +def test_migrate_by_default() -> None: + with open( + TestCases.get_path("data-files/projection/example-with-version-1.1.json") + ) as f: + data = json.load(f) + item = pystac.Item.from_dict(data) # default used to be migrate=False + assert item.ext.proj.code == "EPSG:32614" + + +def test_clone_extra_fields(item: Item) -> None: + item.extra_fields["foo"] = "bar" + cloned = item.clone() + assert cloned.extra_fields["foo"] == "bar" diff --git a/tests/test_item_assets.py b/tests/v1/test_item_assets.py similarity index 99% rename from tests/test_item_assets.py rename to tests/v1/test_item_assets.py index 167f1bd26..8f2528bb3 100644 --- a/tests/test_item_assets.py +++ b/tests/v1/test_item_assets.py @@ -4,7 +4,8 @@ from pystac.errors import DeprecatedWarning from pystac.extensions.item_assets import AssetDefinition, ItemAssetsExtension from pystac.item_assets import ItemAssetDefinition -from tests.utils import TestCases + +from .utils import TestCases CLASSIFICATION_COLLECTION_RASTER_URI = TestCases.get_path( "data-files/classification/collection-item-assets-raster-bands.json" diff --git a/tests/test_item_collection.py b/tests/v1/test_item_collection.py similarity index 98% rename from tests/test_item_collection.py rename to tests/v1/test_item_collection.py index e4c0758ee..28af1c9e6 100644 --- a/tests/test_item_collection.py +++ b/tests/v1/test_item_collection.py @@ -8,8 +8,9 @@ import pystac from pystac import Item, StacIO from pystac.item_collection import ItemCollection -from tests.utils import TestCases -from tests.utils.stac_io_mock import MockDefaultStacIO + +from .utils import TestCases +from .utils.stac_io_mock import MockDefaultStacIO SIMPLE_ITEM = TestCases.get_path("data-files/examples/1.0.0-RC1/simple-item.json") CORE_ITEM = TestCases.get_path("data-files/examples/1.0.0-RC1/core-item.json") diff --git a/tests/test_layout.py b/tests/v1/test_layout.py similarity index 98% rename from tests/test_layout.py rename to tests/v1/test_layout.py index b92d9dee7..7f3544c45 100644 --- a/tests/test_layout.py +++ b/tests/v1/test_layout.py @@ -14,7 +14,8 @@ LayoutTemplate, TemplateLayoutStrategy, ) -from tests.utils import ( + +from .utils import ( ARBITRARY_BBOX, ARBITRARY_GEOM, TestCases, @@ -311,8 +312,9 @@ def test_produces_layout_for_collection(self) -> None: ) collection = self._get_collection() href = strategy.get_href(collection, parent_dir="http://example.com") - assert href == "http://example.com/col/{}/{}/collection.json".format( - collection.id, collection.license + assert ( + href + == f"http://example.com/col/{collection.id}/{collection.license}/collection.json" ) def test_produces_layout_for_collection_with_filename(self) -> None: @@ -320,8 +322,9 @@ def test_produces_layout_for_collection_with_filename(self) -> None: strategy = TemplateLayoutStrategy(collection_template=template) collection = self._get_collection() href = strategy.get_href(collection, parent_dir="http://example.com") - assert href == "http://example.com/col/{}/{}/col.json".format( - collection.id, collection.license + assert ( + href + == f"http://example.com/col/{collection.id}/{collection.license}/col.json" ) def test_produces_fallback_layout_for_collection(self) -> None: diff --git a/tests/test_link.py b/tests/v1/test_link.py similarity index 99% rename from tests/test_link.py rename to tests/v1/test_link.py index 4379d1f80..3938d13e1 100644 --- a/tests/test_link.py +++ b/tests/v1/test_link.py @@ -12,7 +12,8 @@ from pystac.errors import STACError from pystac.link import HIERARCHICAL_LINKS from pystac.utils import make_posix_style -from tests.utils.test_cases import ARBITRARY_EXTENT + +from .utils.test_cases import ARBITRARY_EXTENT TEST_DATETIME: datetime = datetime(2020, 3, 14, 16, 32) diff --git a/tests/test_pystac_client.py b/tests/v1/test_pystac_client.py similarity index 100% rename from tests/test_pystac_client.py rename to tests/v1/test_pystac_client.py diff --git a/tests/test_stac_io.py b/tests/v1/test_stac_io.py similarity index 99% rename from tests/test_stac_io.py rename to tests/v1/test_stac_io.py index 7a826feb9..f7a201fa7 100644 --- a/tests/test_stac_io.py +++ b/tests/v1/test_stac_io.py @@ -9,7 +9,8 @@ import pystac import pystac.errors from pystac.stac_io import DefaultStacIO, DuplicateKeyReportingMixin, StacIO -from tests.utils import TestCases + +from .utils import TestCases def test_read_write_collection() -> None: diff --git a/tests/test_summaries.py b/tests/v1/test_summaries.py similarity index 98% rename from tests/test_summaries.py rename to tests/v1/test_summaries.py index 90b8704f1..fe15afadc 100644 --- a/tests/test_summaries.py +++ b/tests/v1/test_summaries.py @@ -4,7 +4,8 @@ import pytest from pystac.summaries import RangeSummary, Summaries, Summarizer, SummaryStrategy -from tests.utils import TestCases + +from .utils import TestCases def test_summary() -> None: diff --git a/tests/test_utils.py b/tests/v1/test_utils.py similarity index 98% rename from tests/test_utils.py rename to tests/v1/test_utils.py index c9f0407f2..f3d4aca45 100644 --- a/tests/test_utils.py +++ b/tests/v1/test_utils.py @@ -1,7 +1,7 @@ import json import os import time -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta, timezone import pytest from dateutil import tz @@ -20,7 +20,8 @@ safe_urlparse, str_to_datetime, ) -from tests.utils import TestCases, path_includes_drive_letter + +from .utils import TestCases, path_includes_drive_letter @pytest.mark.parametrize( @@ -254,7 +255,7 @@ def test_datetime_to_str() -> None: ), ( "timezone aware, utc", - datetime(2000, 1, 1, tzinfo=timezone.utc), + datetime(2000, 1, 1, tzinfo=UTC), "2000-01-01T00:00:00Z", ), ( @@ -278,7 +279,7 @@ def test_datetime_to_str_with_microseconds_timespec() -> None: ), ( "timezone aware, utc", - datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc), + datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=UTC), "2000-01-01T00:00:00.000000Z", ), ( @@ -410,7 +411,7 @@ def test_now_functions() -> None: now2 = now_in_utc() assert now1 < now2 - assert now1.tzinfo == timezone.utc + assert now1.tzinfo == UTC assert str_to_datetime(now_to_rfc3339_str()) diff --git a/tests/test_version.py b/tests/v1/test_version.py similarity index 96% rename from tests/test_version.py rename to tests/v1/test_version.py index 58b0e6cd2..cb15f0371 100644 --- a/tests/test_version.py +++ b/tests/v1/test_version.py @@ -5,7 +5,8 @@ import pytest import pystac -from tests.utils import TestCases + +from .utils import TestCases def test_override_stac_version_with_environ() -> None: diff --git a/tests/test_writing.py b/tests/v1/test_writing.py similarity index 99% rename from tests/test_writing.py rename to tests/v1/test_writing.py index 90a7b4f72..05d01aee3 100644 --- a/tests/test_writing.py +++ b/tests/v1/test_writing.py @@ -7,7 +7,8 @@ from pystac import HIERARCHICAL_LINKS, Catalog, CatalogType, Collection from pystac.utils import is_absolute_href, make_absolute_href from pystac.validation import validate_dict -from tests.utils import TestCases + +from .utils import TestCases CTYPES = [ CatalogType.ABSOLUTE_PUBLISHED, diff --git a/tests/utils/__init__.py b/tests/v1/utils/__init__.py similarity index 88% rename from tests/utils/__init__.py rename to tests/v1/utils/__init__.py index 03e5fbfd6..8d62846f9 100644 --- a/tests/utils/__init__.py +++ b/tests/v1/utils/__init__.py @@ -13,9 +13,10 @@ from dateutil.parser import parse import pystac -from tests.utils.os_utils import path_includes_drive_letter -from tests.utils.stac_io_mock import MockStacIO -from tests.utils.test_cases import ( + +from .os_utils import path_includes_drive_letter +from .stac_io_mock import MockStacIO +from .test_cases import ( ARBITRARY_BBOX, ARBITRARY_EXTENT, ARBITRARY_GEOM, diff --git a/tests/utils/os_utils.py b/tests/v1/utils/os_utils.py similarity index 100% rename from tests/utils/os_utils.py rename to tests/v1/utils/os_utils.py diff --git a/tests/utils/stac_io_mock.py b/tests/v1/utils/stac_io_mock.py similarity index 100% rename from tests/utils/stac_io_mock.py rename to tests/v1/utils/stac_io_mock.py diff --git a/tests/utils/test_cases.py b/tests/v1/utils/test_cases.py similarity index 97% rename from tests/utils/test_cases.py rename to tests/v1/utils/test_cases.py index 97184d836..19324673b 100644 --- a/tests/utils/test_cases.py +++ b/tests/v1/utils/test_cases.py @@ -1,6 +1,6 @@ import csv import os -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Any import pystac @@ -156,7 +156,7 @@ def case_3() -> Catalog: id="imagery-item", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), + datetime=datetime.now(UTC), properties={}, ) @@ -168,7 +168,7 @@ def case_3() -> Catalog: id="label-items", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, - datetime=datetime.now(timezone.utc), + datetime=datetime.now(UTC), properties={}, ) diff --git a/tests/validation/__init__.py b/tests/v1/validation/__init__.py similarity index 100% rename from tests/validation/__init__.py rename to tests/v1/validation/__init__.py diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all.yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all.yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all.yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all.yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_deprecated_dict_arg.yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_deprecated_dict_arg.yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_deprecated_dict_arg.yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_deprecated_dict_arg.yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case0].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case0].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case0].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case0].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case1].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case1].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case1].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case1].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case2].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case2].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case2].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case2].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case3].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case3].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case3].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case3].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case4].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case4].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case4].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case4].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case5].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case5].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case5].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case5].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case6].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case6].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case6].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_all_dict[test_case6].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_custom_validator.yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_custom_validator.yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_custom_validator.yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_custom_validator.yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example0].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example0].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example0].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example0].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example100].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example100].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example100].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example100].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example101].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example101].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example101].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example101].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example102].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example102].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example102].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example102].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example103].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example103].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example103].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example103].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example104].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example104].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example104].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example104].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example105].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example105].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example105].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example105].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example106].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example106].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example106].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example106].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example107].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example107].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example107].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example107].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example108].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example108].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example108].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example108].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example109].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example109].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example109].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example109].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example10].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example10].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example10].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example10].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example110].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example110].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example110].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example110].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example111].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example111].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example111].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example111].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example112].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example112].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example112].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example112].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example113].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example113].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example113].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example113].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example114].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example114].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example114].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example114].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example115].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example115].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example115].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example115].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example116].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example116].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example116].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example116].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example117].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example117].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example117].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example117].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example118].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example118].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example118].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example118].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example119].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example119].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example119].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example119].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example11].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example11].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example11].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example11].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example120].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example120].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example120].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example120].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example121].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example121].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example121].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example121].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example122].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example122].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example122].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example122].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example123].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example123].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example123].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example123].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example124].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example124].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example124].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example124].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example12].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example12].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example12].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example12].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example13].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example13].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example13].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example13].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example14].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example14].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example14].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example14].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example15].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example15].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example15].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example15].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example16].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example16].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example16].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example16].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example17].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example17].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example17].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example17].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example18].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example18].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example18].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example18].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example19].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example19].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example19].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example19].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example1].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example1].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example1].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example1].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example20].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example20].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example20].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example20].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example21].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example21].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example21].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example21].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example22].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example22].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example22].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example22].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example23].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example23].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example23].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example23].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example24].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example24].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example24].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example24].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example25].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example25].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example25].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example25].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example26].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example26].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example26].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example26].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example27].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example27].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example27].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example27].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example28].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example28].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example28].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example28].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example29].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example29].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example29].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example29].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example2].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example2].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example2].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example2].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example30].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example30].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example30].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example30].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example31].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example31].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example31].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example31].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example32].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example32].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example32].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example32].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example33].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example33].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example33].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example33].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example34].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example34].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example34].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example34].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example35].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example35].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example35].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example35].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example36].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example36].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example36].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example36].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example37].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example37].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example37].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example37].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example38].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example38].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example38].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example38].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example39].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example39].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example39].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example39].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example3].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example3].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example3].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example3].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example40].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example40].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example40].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example40].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example41].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example41].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example41].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example41].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example42].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example42].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example42].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example42].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example43].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example43].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example43].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example43].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example44].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example44].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example44].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example44].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example45].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example45].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example45].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example45].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example46].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example46].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example46].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example46].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example47].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example47].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example47].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example47].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example48].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example48].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example48].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example48].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example49].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example49].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example49].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example49].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example4].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example4].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example4].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example4].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example50].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example50].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example50].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example50].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example51].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example51].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example51].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example51].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example52].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example52].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example52].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example52].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example53].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example53].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example53].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example53].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example54].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example54].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example54].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example54].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example55].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example55].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example55].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example55].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example56].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example56].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example56].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example56].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example57].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example57].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example57].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example57].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example58].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example58].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example58].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example58].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example59].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example59].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example59].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example59].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example5].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example5].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example5].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example5].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example60].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example60].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example60].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example60].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example61].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example61].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example61].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example61].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example62].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example62].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example62].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example62].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example63].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example63].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example63].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example63].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example64].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example64].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example64].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example64].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example65].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example65].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example65].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example65].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example66].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example66].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example66].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example66].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example67].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example67].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example67].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example67].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example68].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example68].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example68].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example68].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example69].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example69].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example69].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example69].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example6].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example6].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example6].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example6].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example70].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example70].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example70].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example70].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example71].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example71].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example71].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example71].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example72].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example72].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example72].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example72].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example73].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example73].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example73].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example73].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example74].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example74].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example74].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example74].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example75].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example75].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example75].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example75].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example76].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example76].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example76].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example76].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example77].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example77].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example77].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example77].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example78].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example78].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example78].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example78].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example79].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example79].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example79].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example79].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example7].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example7].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example7].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example7].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example80].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example80].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example80].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example80].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example81].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example81].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example81].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example81].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example82].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example82].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example82].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example82].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example83].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example83].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example83].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example83].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example84].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example84].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example84].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example84].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example85].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example85].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example85].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example85].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example86].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example86].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example86].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example86].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example87].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example87].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example87].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example87].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example88].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example88].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example88].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example88].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example89].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example89].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example89].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example89].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example8].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example8].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example8].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example8].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example90].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example90].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example90].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example90].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example91].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example91].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example91].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example91].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example92].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example92].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example92].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example92].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example93].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example93].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example93].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example93].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example94].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example94].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example94].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example94].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example95].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example95].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example95].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example95].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example96].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example96].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example96].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example96].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example97].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example97].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example97].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example97].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example98].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example98].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example98].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example98].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example99].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example99].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example99].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example99].yaml diff --git a/tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example9].yaml b/tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example9].yaml similarity index 100% rename from tests/validation/cassettes/test_validate/TestValidate.test_validate_examples[example9].yaml rename to tests/v1/validation/cassettes/test_validate/TestValidate.test_validate_examples[example9].yaml diff --git a/tests/validation/test_schema_uri_map.py b/tests/v1/validation/test_schema_uri_map.py similarity index 100% rename from tests/validation/test_schema_uri_map.py rename to tests/v1/validation/test_schema_uri_map.py diff --git a/tests/validation/test_validate.py b/tests/v1/validation/test_validate.py similarity index 98% rename from tests/validation/test_validate.py rename to tests/v1/validation/test_validate.py index 2c42c6750..0d443293e 100644 --- a/tests/validation/test_validate.py +++ b/tests/v1/validation/test_validate.py @@ -2,7 +2,7 @@ import os import shutil import tempfile -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Any, cast import jsonschema @@ -15,8 +15,9 @@ from pystac.serialization.common_properties import merge_common_properties from pystac.utils import get_opt from pystac.validation import GetSchemaError, JsonSchemaSTACValidator -from tests.utils import TestCases -from tests.utils.test_cases import ExampleInfo + +from ..utils import TestCases +from ..utils.test_cases import ExampleInfo class TestValidate: @@ -174,7 +175,7 @@ def test_validates_geojson_with_tuple_coordinates(self) -> None: id="test-item", geometry=geom, bbox=[-115.308, 36.126, -115.305, 36.129], - datetime=datetime.now(timezone.utc), + datetime=datetime.now(UTC), properties={}, ) diff --git a/uv.lock b/uv.lock index c895e2d8c..493758700 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4290 +1,1144 @@ version = 1 revision = 3 -requires-python = ">=3.10" +requires-python = ">=3.12" resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", + "platform_python_implementation != 'PyPy'", + "platform_python_implementation == 'PyPy'", ] [[package]] -name = "accessible-pygments" -version = "0.0.5" +name = "asv" +version = "0.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + { name = "asv-runner" }, + { name = "build" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "importlib-metadata" }, + { name = "json5" }, + { name = "packaging" }, + { name = "pympler", marker = "platform_python_implementation != 'PyPy'" }, + { name = "pyyaml", marker = "platform_python_implementation != 'PyPy'" }, + { name = "tabulate" }, + { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/50/f762be4ee8632d88aff4ba9e62e7c156a0684ef52db629c22bae24fda449/asv-0.6.5.tar.gz", hash = "sha256:a8eeb7c5037cd78c146bd727d27203132438d4d62f36e669eb0cd5d63da0cf39", size = 402650, upload-time = "2025-09-13T16:25:48.141Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, + { url = "https://files.pythonhosted.org/packages/e7/58/3c94d6043f2d815480b873f302ecda3b9debb0f202556fd822b118e87415/asv-0.6.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:375da7109fa160d41e4b86a5de7783e8c9bc9f1c930a1c02c29b652b15d46835", size = 180231, upload-time = "2025-09-13T16:24:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/f2/75/2a23346aabb19698e97b7ee8f57eb905dabf7460b226a2070c6cdd3e8c17/asv-0.6.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:980cb8e9c3be5350621c85201bfb25f70c26695f69bd4e91b19f1b3c97f00ff3", size = 180531, upload-time = "2025-09-13T16:24:28.693Z" }, + { url = "https://files.pythonhosted.org/packages/27/d3/22bc22619266bced0a53ff07ee94a41bb09fed2bbe505e07a146fc4c1a58/asv-0.6.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f503d45077ba275d357a9712fe1506b98e507eb276ca01e981c9e5baf30b43", size = 254851, upload-time = "2025-09-13T16:24:30.035Z" }, + { url = "https://files.pythonhosted.org/packages/de/70/9991371db795a84b4fb0774ff050ed48730c14568953f9f59ca35170541b/asv-0.6.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:440aca773d254f6590f7a459bdc388441027bc2745eae644675665acc3809c2e", size = 256342, upload-time = "2025-09-13T16:24:31.309Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/7cb7f02091908201c45c609d29ced5bbc1101a057edc0e1fb153f7592b4e/asv-0.6.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf63f7ee3d35ec8191543d82ca3a3be3a3c0cde8eb2d45a672f09f32ba5fbb37", size = 807867, upload-time = "2025-09-13T16:24:33.298Z" }, + { url = "https://files.pythonhosted.org/packages/ff/02/946c53292fe551b3bb977260f29b5bfa9f284bf17088ccef5c8ddf28f06c/asv-0.6.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:58a0d2de09ebc67642b661d904d2e686e0f32bb5e8b4867523a15ff7561f061a", size = 1384841, upload-time = "2025-09-13T16:24:34.857Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f7/b43efa62f20af7e9035f6fd6dadb7109feddfb4e0b1e2ff791656ae55606/asv-0.6.5-cp312-cp312-win_amd64.whl", hash = "sha256:8df71cd3c656680051e0d0b2834521f7ab6da3d4804c48354c0e5ca341a0a39e", size = 182804, upload-time = "2025-09-13T16:24:36.135Z" }, + { url = "https://files.pythonhosted.org/packages/25/70/a00673c98d30de3377b036cd85247274330ade931c0e4909c3e2d269c0c6/asv-0.6.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bedfc7f0138ab136ccd67f42575dd4b2471c811239ad8c7b7aabc83f5eba79c3", size = 180236, upload-time = "2025-09-13T16:24:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fd/d2f5c43dcb614cfedd1f339feb2f711b0a69e7c2ad96d68b547d44227c80/asv-0.6.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f062e0658e568b98154afe11d91b5fb631f884678b7ad4a00ebcc0d6aa6b41f", size = 180537, upload-time = "2025-09-13T16:24:39.396Z" }, + { url = "https://files.pythonhosted.org/packages/6d/00/a8dba759f554e6aedc5a7caf2a8efd00e5c69536560c670b1f41e7b53d83/asv-0.6.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6caedcca3ec60602b907caeab54d1706abb12e088ce96650862c6fc117831cc0", size = 254761, upload-time = "2025-09-13T16:24:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bd/89c4483e1e781e39957547d7bda0a7a7af338911ea159cd66a9415cc811d/asv-0.6.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:861ae69bfdd8659f95a058891c98757d3a5f857bf0ddc5d810e0dabf3405042e", size = 256267, upload-time = "2025-09-13T16:24:43.033Z" }, + { url = "https://files.pythonhosted.org/packages/35/f1/752a51a07be0c153281f04795433524c76e6926063da005b78e2053117c5/asv-0.6.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7344c0020c1ab1cb33af9626cf24e05c346b4fd521d4f866f35a7a9a276f8e16", size = 806346, upload-time = "2025-09-13T16:24:45.123Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ce/c6d9f2dc42462db7897178e21e38c925d8efb75813dde8e7a0a2e5126387/asv-0.6.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1279a92cd8a601d2be5430afe3dd9f942ab0e6003f33ff1914dd9f638b595a3d", size = 1384779, upload-time = "2025-09-13T16:24:46.758Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4f/48af74f756f1c67b71074c99f265d382927de742288cf416e212c1808c7d/asv-0.6.5-cp313-cp313-win_amd64.whl", hash = "sha256:dbc3269464ec27d025d3b25e0e1f3d616035e05e01bcfceb2f4e965278d72197", size = 182807, upload-time = "2025-09-13T16:24:47.978Z" }, ] [[package]] -name = "adlfs" -version = "2024.12.0" +name = "asv-runner" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohttp" }, - { name = "azure-core" }, - { name = "azure-datalake-store" }, - { name = "azure-identity" }, - { name = "azure-storage-blob" }, - { name = "fsspec" }, + { name = "importlib-metadata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/4b/da5ae9c35e0b9f793d07d4939ad99e1d2ba7c9c502fd6074af5ff4554b03/asv_runner-0.2.1.tar.gz", hash = "sha256:945dd301a06fa9102f221b1e9ddd048f5ecd863796d4c8cd487f5577fe0db66d", size = 39518, upload-time = "2024-02-17T14:11:48.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/9a/6872af94fc8e8072723946651e65f66e16a0ca0efec7806bce8c2e2483d1/asv_runner-0.2.1-py3-none-any.whl", hash = "sha256:655d466208ce311768071f5003a61611481b24b3ad5ac41fb8a6374197e647e9", size = 47660, upload-time = "2024-02-11T21:50:07.026Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/82/e30891af574fb358449fb9436aac53569814452cb88b0cba4f488171b8dc/adlfs-2024.12.0.tar.gz", hash = "sha256:04582bf7461a57365766d01a295a0a88b2b8c42c4fea06e2d673f62675cac5c6", size = 49189, upload-time = "2024-12-15T19:06:30.939Z" } + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/ed/d1bf75c089857d38332cf45416e419b47382b345ba5dfc4fae69397830d9/adlfs-2024.12.0-py3-none-any.whl", hash = "sha256:00aab061ddec0413b2039487e656b62e01ece8ef1ca0493f76034a596cf069e3", size = 41792, upload-time = "2024-12-15T19:06:27.718Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] [[package]] -name = "affine" -version = "2.4.0" +name = "babel" +version = "2.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/98/d2f0bb06385069e799fc7d2870d9e078cfa0fa396dc8a2b81227d0da08b9/affine-2.4.0.tar.gz", hash = "sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea", size = 17132, upload-time = "2023-01-19T23:44:30.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/f7/85273299ab57117850cc0a936c64151171fac4da49bc6fba0dad984a7c5f/affine-2.4.0-py3-none-any.whl", hash = "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92", size = 15662, upload-time = "2023-01-19T23:44:28.833Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] -name = "aiohappyeyeballs" -version = "2.6.1" +name = "backrefs" +version = "6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, + { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, ] [[package]] -name = "aiohttp" -version = "3.12.15" +name = "basedpyright" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "async-timeout", marker = "python_full_version < '3.11'" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, + { name = "nodejs-wheel-binaries" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/d7/9476af6f45a70e8d23045ec59d99c2698513b7395283cadc75caeeea2b83/basedpyright-1.37.0.tar.gz", hash = "sha256:affbffced97a04a08bfc44aef2da43951a5ab5e2e55921a144ed786c4fd2c6ad", size = 22837441, upload-time = "2026-01-04T09:59:32.652Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/dc/ef9394bde9080128ad401ac7ede185267ed637df03b51f05d14d1c99ad67/aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc", size = 703921, upload-time = "2025-07-29T05:49:43.584Z" }, - { url = "https://files.pythonhosted.org/packages/8f/42/63fccfc3a7ed97eb6e1a71722396f409c46b60a0552d8a56d7aad74e0df5/aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af", size = 480288, upload-time = "2025-07-29T05:49:47.851Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a2/7b8a020549f66ea2a68129db6960a762d2393248f1994499f8ba9728bbed/aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421", size = 468063, upload-time = "2025-07-29T05:49:49.789Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f5/d11e088da9176e2ad8220338ae0000ed5429a15f3c9dfd983f39105399cd/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79", size = 1650122, upload-time = "2025-07-29T05:49:51.874Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6b/b60ce2757e2faed3d70ed45dafee48cee7bfb878785a9423f7e883f0639c/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77", size = 1624176, upload-time = "2025-07-29T05:49:53.805Z" }, - { url = "https://files.pythonhosted.org/packages/dd/de/8c9fde2072a1b72c4fadecf4f7d4be7a85b1d9a4ab333d8245694057b4c6/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c", size = 1696583, upload-time = "2025-07-29T05:49:55.338Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ad/07f863ca3d895a1ad958a54006c6dafb4f9310f8c2fdb5f961b8529029d3/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4", size = 1738896, upload-time = "2025-07-29T05:49:57.045Z" }, - { url = "https://files.pythonhosted.org/packages/20/43/2bd482ebe2b126533e8755a49b128ec4e58f1a3af56879a3abdb7b42c54f/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6", size = 1643561, upload-time = "2025-07-29T05:49:58.762Z" }, - { url = "https://files.pythonhosted.org/packages/23/40/2fa9f514c4cf4cbae8d7911927f81a1901838baf5e09a8b2c299de1acfe5/aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2", size = 1583685, upload-time = "2025-07-29T05:50:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c3/94dc7357bc421f4fb978ca72a201a6c604ee90148f1181790c129396ceeb/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d", size = 1627533, upload-time = "2025-07-29T05:50:02.306Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3f/1f8911fe1844a07001e26593b5c255a685318943864b27b4e0267e840f95/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb", size = 1638319, upload-time = "2025-07-29T05:50:04.282Z" }, - { url = "https://files.pythonhosted.org/packages/4e/46/27bf57a99168c4e145ffee6b63d0458b9c66e58bb70687c23ad3d2f0bd17/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5", size = 1613776, upload-time = "2025-07-29T05:50:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7e/1d2d9061a574584bb4ad3dbdba0da90a27fdc795bc227def3a46186a8bc1/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b", size = 1693359, upload-time = "2025-07-29T05:50:07.563Z" }, - { url = "https://files.pythonhosted.org/packages/08/98/bee429b52233c4a391980a5b3b196b060872a13eadd41c3a34be9b1469ed/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065", size = 1716598, upload-time = "2025-07-29T05:50:09.33Z" }, - { url = "https://files.pythonhosted.org/packages/57/39/b0314c1ea774df3392751b686104a3938c63ece2b7ce0ba1ed7c0b4a934f/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1", size = 1644940, upload-time = "2025-07-29T05:50:11.334Z" }, - { url = "https://files.pythonhosted.org/packages/1b/83/3dacb8d3f8f512c8ca43e3fa8a68b20583bd25636ffa4e56ee841ffd79ae/aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a", size = 429239, upload-time = "2025-07-29T05:50:12.803Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f9/470b5daba04d558c9673ca2034f28d067f3202a40e17804425f0c331c89f/aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830", size = 452297, upload-time = "2025-07-29T05:50:14.266Z" }, - { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, - { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, - { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, - { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, - { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, - { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, - { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, - { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, - { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, - { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, - { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, - { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, - { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, - { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, - { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, - { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, - { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, - { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, - { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, - { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, - { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, - { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, - { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/753918f0bad07a1b24755a540b64bca1388322615025d4c954e3740fcdbe/basedpyright-1.37.0-py3-none-any.whl", hash = "sha256:261a02a8732a19f3f585e2940582147560058626a062a2320724de84fb2dc41b", size = 11884509, upload-time = "2026-01-04T09:59:35.997Z" }, ] [[package]] -name = "aiosignal" -version = "1.4.0" +name = "build" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "frozenlist" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1c/23e33405a7c9eac261dff640926b8b5adaed6a6eb3e1767d441ed611d0c0/build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", size = 48544, upload-time = "2025-08-01T21:27:09.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, + { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, ] [[package]] -name = "alabaster" -version = "1.0.0" +name = "certifi" +version = "2025.7.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, ] [[package]] -name = "annotated-types" -version = "0.7.0" +name = "charset-normalizer" +version = "3.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] -name = "anyio" -version = "4.9.0" +name = "click" +version = "8.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] -name = "appnope" -version = "0.1.4" +name = "colorama" +version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] -name = "argon2-cffi" -version = "25.1.0" +name = "distlib" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "argon2-cffi-bindings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] -name = "argon2-cffi-bindings" -version = "21.2.0" +name = "filelock" +version = "3.20.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" }, - { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" }, - { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" }, - { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" }, - { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" }, - { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" }, - { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" }, + { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, ] [[package]] -name = "arrow" -version = "1.3.0" +name = "ghp-import" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, - { name = "types-python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, -] - -[[package]] -name = "asciitree" -version = "0.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/885bc91484e1aa8f618f6f0228d76d0e67000b0fdd6090673b777e311913/asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e", size = 3951, upload-time = "2016-09-05T19:10:42.681Z" } - -[[package]] -name = "asttokens" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] [[package]] -name = "asv" -version = "0.6.4" +name = "griffe" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asv-runner" }, - { name = "build" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "json5" }, - { name = "pympler", marker = "platform_python_implementation != 'PyPy'" }, - { name = "pyyaml", marker = "platform_python_implementation != 'PyPy'" }, - { name = "tabulate" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "virtualenv" }, + { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/0c/31fe4135b378ee17131a804b11380a1ec1406c3925cb24ecdff5b86e673c/asv-0.6.4.tar.gz", hash = "sha256:1d124184171cfe106e3e57ac04e3221b8d4571c9bd6ca2c6498a8c7407339df1", size = 389611, upload-time = "2024-08-12T23:00:14.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/54/de33292ea7ce15613bdd9a6e51fac47878ce80f293157c9116c23387a1e5/asv-0.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e32b4cc435bdb6f2ef83d8092e977962f6fa20471542d6341e596324d350cbea", size = 185448, upload-time = "2024-08-12T22:58:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/a9/14/fc30a590333a1021b28f9f2aaab009d3bb7c277cc1decadfcc86b74108de/asv-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fdfb9090623fc45cbeb77ab40b394779794083c155128e3d320fa06af2e0fdf5", size = 185245, upload-time = "2024-08-12T22:58:15.386Z" }, - { url = "https://files.pythonhosted.org/packages/c3/72/87296f54fbf876fd8e0ad744e0c9322dee6f6f631a8d9f14b9b48008b51c/asv-0.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dfee8a415f4b5da0be4bedf4c9cb3b041c2148d28d2327cf3b54f9cb565cefd", size = 259330, upload-time = "2024-08-12T22:58:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/bf/77/26cb95b1f1751706352e9924388732dc9fe4cc9e60153098407974d72b89/asv-0.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abc13331bb8bb1880dbc33e75175ae90bca439038a1f7e246528481ecebd15dd", size = 261036, upload-time = "2024-08-12T22:58:19.447Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c6/c6f40902933b87f41e1ded3065eae526d29a40fbb84a69e2e61e151b217e/asv-0.6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b67eec004f8218bba25dcdbdda2e6676dd6c4ac3e97a80b691b27dcfbfbda38d", size = 866515, upload-time = "2024-08-12T22:58:22.934Z" }, - { url = "https://files.pythonhosted.org/packages/ae/00/7fa5a7c695ee49e11765956123407f868cefbf481b7c4177feba6e0646e9/asv-0.6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aef14496a34552308d054db71181bfb1ca45d7ef29028747d388be9f00a5b45c", size = 812157, upload-time = "2024-08-12T22:58:25.684Z" }, - { url = "https://files.pythonhosted.org/packages/4f/97/f09925683128f9bce9a7bfbcecb22334cec988fdb139a9959c2d22f39f19/asv-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:0c8931e7a8aeda75f90b3ac422cbb7c46a5ce50d8c0a8e821cdf3e4d0705dd76", size = 188022, upload-time = "2024-08-12T22:58:27.774Z" }, - { url = "https://files.pythonhosted.org/packages/b0/39/8532a88cde13dca8d9dcfeb2ba48d92beaef8919fbfc2d428cbfa5a1bbb9/asv-0.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74666c5896b4aec92b4a12cf9aa7494dec3398bb9ea602a9f8dc1656b53e8e10", size = 185462, upload-time = "2024-08-12T22:58:30.299Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a6/b0133d083ac4c979f16e9bd887427c141306e3779505941ccf25341c0384/asv-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26166a7bd7fe05b5a8507247d1a7ab1dfc4256414b0505d124a7b9d46a618a1c", size = 185242, upload-time = "2024-08-12T22:58:32.138Z" }, - { url = "https://files.pythonhosted.org/packages/ba/5c/0726b4925163c12e842e306267c0e702fb694b85f34b62240f687208091c/asv-0.6.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe6161c5616f5aed936947866b6376e09c937d628aa81115b3c72e90a151c1f9", size = 259959, upload-time = "2024-08-12T22:58:33.877Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/944b4fc935b6c6874a17413159aa19701b80052d29c80efe6a9afbcec3b5/asv-0.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d6122b5e86bf9071b9ff7136672d50da0d460dfc958f43429843f7a3cd3e86a", size = 261620, upload-time = "2024-08-12T22:58:36.129Z" }, - { url = "https://files.pythonhosted.org/packages/eb/24/b6169229108e1a1d6506fbd2bfad5ecede34df5e03020b16c627e44411b8/asv-0.6.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:79554f125033ecbcb599cd704b4b5b525d254e5e05b1dd24bab3bbd83ae5502e", size = 867864, upload-time = "2024-08-12T22:58:38.865Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a2/b9e5be144fd0c7072637f50c98bdf615d83165244849473e2b9f262ea24b/asv-0.6.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2e80f39501628fd4cac972f08fa4c9b8e211a86fc43dd6e58c95d106cbaf54e7", size = 813539, upload-time = "2024-08-12T22:58:41.184Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d7/ea92fc7155a5cd7aa1903dd74096a91a8851355423bf8f09d6e3a96ccc21/asv-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:363dfdee98cc072e6a1468137eed640985e48ccbb11c175d04ee420f05459872", size = 188025, upload-time = "2024-08-12T22:58:42.72Z" }, - { url = "https://files.pythonhosted.org/packages/42/5f/f6ac7c787cde901d694b9355d9cd9227b907b313b3f1ed2b006668104e5f/asv-0.6.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:244b71778f91aa6672e1f16feb9eecac78ef7cee95228ef8f0315a2e2deecfed", size = 185442, upload-time = "2024-08-12T22:58:44.839Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c0/339bf20ad132b85376aa4ddc6d17e0ee704846a856a84eb59609df6ca4ef/asv-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3e798b275de2889748d43d42305bfce68c015a3e38ae935d231835cb836fef73", size = 185225, upload-time = "2024-08-12T22:58:48.309Z" }, - { url = "https://files.pythonhosted.org/packages/3d/3e/1019cb8cb54bc6cf28799c49b86a67b4713082d1e857eac8ec9e34d8ed83/asv-0.6.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d064c5ac1ab18efc62467f65ed4989a2e2ac1a4d21886119fa0ef0f91d548438", size = 260431, upload-time = "2024-08-12T22:58:49.663Z" }, - { url = "https://files.pythonhosted.org/packages/a5/80/fd70dba7f524a95a2d9e8f976cfc891d570f3ccdd9201c1f0c3bc2dfea74/asv-0.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51e5862bdac0f1fe11886bdd40b30a9691a65cb7feac40f0676fe9206d5bb43", size = 261970, upload-time = "2024-08-12T22:58:51.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/bf/5af517bef47eb92eca0016b33334f77760a4f4f7acd3022b1d00dfe576fa/asv-0.6.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46a7ca838e8c49109c43b1cda0eb64abc5e0a045538da718abe981d115ed47aa", size = 867717, upload-time = "2024-08-12T22:58:54.508Z" }, - { url = "https://files.pythonhosted.org/packages/98/71/f205a3122f8aa7889a4691384c6b01bea4805b5982d47ccdaa189296d53a/asv-0.6.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f5f722178c7e36b797f764c837fc03c462f68c8f2cba5145b2e64119e46231ff", size = 813322, upload-time = "2024-08-12T22:58:57.074Z" }, - { url = "https://files.pythonhosted.org/packages/1f/7b/a2001b35bc1fbaa7cbf763f8cff4ad64af849fd59ef26a3f4e32ee211e63/asv-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:f972ca71316d46a0242eb69e53dadfeab1e4d0546773b0f722462f97b3e5fbd9", size = 188022, upload-time = "2024-08-12T22:58:58.824Z" }, - { url = "https://files.pythonhosted.org/packages/67/c2/0808a237f90189f8fcfd6be4b77a8e1f19d0b8813d947a816f2bf9514809/asv-0.6.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0305e9eee21f71c3d3f8b046beb35e571f6dd7ed2fcd0e8405f8a208bcd3228a", size = 184577, upload-time = "2024-08-12T22:59:45.167Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c7/171341cc046f570b32fc4da70e80f800470301df3d67301e71b9c6f68a43/asv-0.6.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6cd23fa20edf8cb30354fda3388a8835a15158e21559c86f0d997e5d30dbf91", size = 185004, upload-time = "2024-08-12T22:59:46.378Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c6/df037423144a902cd2cbcdb9cbcdee567f7ba35be4f470f2a09ffba1e2fd/asv-0.6.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7424d2dbfcb98aa3c099311100ceb9aabfd83fed0b41420f70f142852ed392a", size = 185767, upload-time = "2024-08-12T22:59:48.423Z" }, - { url = "https://files.pythonhosted.org/packages/36/09/8ec4eb06432ccd13a346fb4db1e9ee67589c65731bc7a5c59c347eab5581/asv-0.6.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7f4b95583cf379015d35b747a1bb4df99c05dd4107d6081b2cf4a577f4caeca", size = 188103, upload-time = "2024-08-12T22:59:50.475Z" }, + { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] [[package]] -name = "asv-runner" -version = "0.2.1" +name = "html5lib" +version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, + { name = "six" }, + { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/4b/da5ae9c35e0b9f793d07d4939ad99e1d2ba7c9c502fd6074af5ff4554b03/asv_runner-0.2.1.tar.gz", hash = "sha256:945dd301a06fa9102f221b1e9ddd048f5ecd863796d4c8cd487f5577fe0db66d", size = 39518, upload-time = "2024-02-17T14:11:48.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/9a/6872af94fc8e8072723946651e65f66e16a0ca0efec7806bce8c2e2483d1/asv_runner-0.2.1-py3-none-any.whl", hash = "sha256:655d466208ce311768071f5003a61611481b24b3ad5ac41fb8a6374197e647e9", size = 47660, upload-time = "2024-02-11T21:50:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, ] [[package]] -name = "async-lru" -version = "2.0.5" +name = "idna" +version = "3.10" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] -name = "async-timeout" -version = "5.0.1" +name = "importlib-metadata" +version = "8.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] -name = "attrs" -version = "25.3.0" +name = "iniconfig" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] -name = "azure-core" -version = "1.35.0" +name = "jinja2" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "requests" }, - { name = "six" }, - { name = "typing-extensions" }, + { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/89/f53968635b1b2e53e4aad2dd641488929fef4ca9dfb0b97927fa7697ddf3/azure_core-1.35.0.tar.gz", hash = "sha256:c0be528489485e9ede59b6971eb63c1eaacf83ef53001bfe3904e475e972be5c", size = 339689, upload-time = "2025-07-03T00:55:23.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/78/bf94897361fdd650850f0f2e405b2293e2f12808239046232bdedf554301/azure_core-1.35.0-py3-none-any.whl", hash = "sha256:8db78c72868a58f3de8991eb4d22c4d368fae226dac1002998d6c50437e7dad1", size = 210708, upload-time = "2025-07-03T00:55:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] -name = "azure-datalake-store" -version = "0.0.53" +name = "json5" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, - { name = "msal" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/ff/61369d06422b5ac48067215ff404841342651b14a89b46c8d8e1507c8f17/azure-datalake-store-0.0.53.tar.gz", hash = "sha256:05b6de62ee3f2a0a6e6941e6933b792b800c3e7f6ffce2fc324bc19875757393", size = 71430, upload-time = "2023-05-10T21:17:05.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/2a/75f56b14f115189155cf12e46b366ad1fe3357af5a1a7c09f7446662d617/azure_datalake_store-0.0.53-py2.py3-none-any.whl", hash = "sha256:a30c902a6e360aa47d7f69f086b426729784e71c536f330b691647a51dc42b2b", size = 55308, upload-time = "2023-05-10T21:17:02.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, ] [[package]] -name = "azure-identity" -version = "1.24.0" +name = "jsonschema" +version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "msal" }, - { name = "msal-extensions" }, - { name = "typing-extensions" }, + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/44/f3ee20bacb220b6b4a2b0a6cf7e742eecb383a5ccf604dd79ec27c286b7e/azure_identity-1.24.0.tar.gz", hash = "sha256:6c3a40b2a70af831e920b89e6421e8dcd4af78a0cb38b9642d86c67643d4930c", size = 271630, upload-time = "2025-08-07T22:27:36.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/74/17428cb429e8d52f6d0d69ed685f4760a545cb0156594963a9337b53b6c9/azure_identity-1.24.0-py3-none-any.whl", hash = "sha256:9e04997cde0ab02ed66422c74748548e620b7b29361c72ce622acab0267ff7c4", size = 187890, upload-time = "2025-08-07T22:27:38.033Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [[package]] -name = "azure-storage-blob" -version = "12.26.0" +name = "jsonschema-specifications" +version = "2025.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "isodate" }, - { name = "typing-extensions" }, + { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/95/3e3414491ce45025a1cde107b6ae72bf72049e6021597c201cd6a3029b9a/azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f", size = 583332, upload-time = "2025-07-16T21:34:07.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/64/63dbfdd83b31200ac58820a7951ddfdeed1fbee9285b0f3eae12d1357155/azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe", size = 412907, upload-time = "2025-07-16T21:34:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] [[package]] -name = "babel" -version = "2.17.0" +name = "markdown" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] [[package]] -name = "beautifulsoup4" -version = "4.13.4" +name = "markupsafe" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] -name = "bleach" -version = "6.2.0" +name = "mergedeep" +version = "1.3.4" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083, upload-time = "2024-10-29T18:30:40.477Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406, upload-time = "2024-10-29T18:30:38.186Z" }, -] - -[package.optional-dependencies] -css = [ - { name = "tinycss2" }, + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] [[package]] -name = "boto3" -version = "1.39.15" +name = "mkdocs" +version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/65/ddd4f52d138e52c1345c2d2421281a98449a6e4365290477befe06fa649a/boto3-1.39.15.tar.gz", hash = "sha256:b4483625f0d8c35045254dee46cd3c851bbc0450814f20b9b25bee1b5c0d8409", size = 111856, upload-time = "2025-07-28T19:56:49.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/c5/27f50a31317041dc3ad79d62f37d5fcfb3f349c2fba8ea3e81de169db870/boto3-1.39.15-py3-none-any.whl", hash = "sha256:38fc54576b925af0075636752de9974e172c8a2cf7133400e3e09b150d20fb6a", size = 139901, upload-time = "2025-07-28T19:56:47.381Z" }, + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] [[package]] -name = "botocore" -version = "1.39.15" +name = "mkdocs-autorefs" +version = "1.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/e2/8cd9560e7e44cf977dc0cc2e48da7634e78b7104ae6e47f4e1dfc1093965/botocore-1.39.15.tar.gz", hash = "sha256:2aa29a717f14f8c7ca058c2e297aaed0aa10ecea24b91514eee802814d1b7600", size = 14237556, upload-time = "2025-07-28T19:56:39.397Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/6e/f25b8633e7ab2008de4c27466c9bc39e32dc73816619ffebbea12936135a/botocore-1.39.15-py3-none-any.whl", hash = "sha256:eb9cfe918ebfbfb8654e1b153b29f0c129d586d2c0d7fb4032731d49baf04cff", size = 13894884, upload-time = "2025-07-28T19:56:33.715Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, ] [[package]] -name = "build" -version = "1.2.2.post1" +name = "mkdocs-get-deps" +version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] [[package]] -name = "cachetools" -version = "6.1.0" +name = "mkdocs-material" +version = "9.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/89/817ad5d0411f136c484d535952aef74af9b25e0d99e90cdffbe121e6d628/cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587", size = 30714, upload-time = "2025-06-16T18:51:03.07Z" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/f0/2ef431fe4141f5e334759d73e81120492b23b2824336883a91ac04ba710b/cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", size = 11189, upload-time = "2025-06-16T18:51:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" }, ] [[package]] -name = "certifi" -version = "2025.7.14" +name = "mkdocs-material-extensions" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, ] [[package]] -name = "cffi" -version = "1.17.1" +name = "mkdocstrings" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086, upload-time = "2025-11-27T15:39:40.534Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135, upload-time = "2025-11-27T15:39:39.301Z" }, ] [[package]] -name = "cfgv" -version = "3.4.0" +name = "mkdocstrings-python" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, ] [[package]] -name = "charset-normalizer" -version = "3.4.2" +name = "multidict" +version = "6.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, - { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, - { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, - { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, - { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, -] - -[[package]] -name = "click" -version = "8.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, -] - -[[package]] -name = "click-plugins" -version = "1.1.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, -] - -[[package]] -name = "cligj" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ea/0d/837dbd5d8430fd0f01ed72c4cfb2f548180f4c68c635df84ce87956cff32/cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27", size = 9803, upload-time = "2021-05-28T21:23:27.935Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/86/43fa9f15c5b9fb6e82620428827cd3c284aa933431405d1bcf5231ae3d3e/cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df", size = 7069, upload-time = "2021-05-28T21:23:26.877Z" }, -] - -[[package]] -name = "cloudpickle" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, -] - -[[package]] -name = "codespell" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/97/df3e00b4d795c96233e35d269c211131c5572503d2270afb6fed7d859cc2/codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9", size = 300968, upload-time = "2023-10-02T19:35:46.513Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/e0/5437cc96b74467c4df6e13b7128cc482c48bb43146fb4c11cf2bcd604e1f/codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07", size = 301382, upload-time = "2023-10-02T19:35:44.262Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "comm" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, -] - -[[package]] -name = "coverage" -version = "7.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/0e/66dbd4c6a7f0758a8d18044c048779ba21fb94856e1edcf764bd5403e710/coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57", size = 819938, upload-time = "2025-07-27T14:13:39.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e7/0f4e35a15361337529df88151bddcac8e8f6d6fd01da94a4b7588901c2fe/coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372", size = 214627, upload-time = "2025-07-27T14:11:01.211Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fd/17872e762c408362072c936dbf3ca28c67c609a1f5af434b1355edcb7e12/coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b", size = 215015, upload-time = "2025-07-27T14:11:03.988Z" }, - { url = "https://files.pythonhosted.org/packages/54/50/c9d445ba38ee5f685f03876c0f8223469e2e46c5d3599594dca972b470c8/coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a", size = 241995, upload-time = "2025-07-27T14:11:05.983Z" }, - { url = "https://files.pythonhosted.org/packages/cc/83/4ae6e0f60376af33de543368394d21b9ac370dc86434039062ef171eebf8/coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f", size = 243253, upload-time = "2025-07-27T14:11:07.424Z" }, - { url = "https://files.pythonhosted.org/packages/49/90/17a4d9ac7171be364ce8c0bb2b6da05e618ebfe1f11238ad4f26c99f5467/coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440", size = 245110, upload-time = "2025-07-27T14:11:09.152Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f7/edc3f485d536ed417f3af2b4969582bcb5fab456241721825fa09354161e/coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8", size = 243056, upload-time = "2025-07-27T14:11:10.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/2c/c4c316a57718556b8d0cc8304437741c31b54a62934e7c8c551a7915c2f4/coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c", size = 241731, upload-time = "2025-07-27T14:11:12.145Z" }, - { url = "https://files.pythonhosted.org/packages/f7/93/c78e144c6f086043d0d7d9237c5b880e71ac672ed2712c6f8cca5544481f/coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc", size = 242023, upload-time = "2025-07-27T14:11:13.573Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e1/34e8505ca81fc144a612e1cc79fadd4a78f42e96723875f4e9f1f470437e/coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef", size = 217130, upload-time = "2025-07-27T14:11:15.11Z" }, - { url = "https://files.pythonhosted.org/packages/75/2b/82adfce6edffc13d804aee414e64c0469044234af9296e75f6d13f92f6a2/coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed", size = 218015, upload-time = "2025-07-27T14:11:16.836Z" }, - { url = "https://files.pythonhosted.org/packages/20/8e/ef088112bd1b26e2aa931ee186992b3e42c222c64f33e381432c8ee52aae/coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f", size = 214747, upload-time = "2025-07-27T14:11:18.217Z" }, - { url = "https://files.pythonhosted.org/packages/2d/76/a1e46f3c6e0897758eb43af88bb3c763cb005f4950769f7b553e22aa5f89/coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1", size = 215128, upload-time = "2025-07-27T14:11:19.706Z" }, - { url = "https://files.pythonhosted.org/packages/78/4d/903bafb371a8c887826ecc30d3977b65dfad0e1e66aa61b7e173de0828b0/coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437", size = 245140, upload-time = "2025-07-27T14:11:21.261Z" }, - { url = "https://files.pythonhosted.org/packages/55/f1/1f8f09536f38394a8698dd08a0e9608a512eacee1d3b771e2d06397f77bf/coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7", size = 246977, upload-time = "2025-07-27T14:11:23.15Z" }, - { url = "https://files.pythonhosted.org/packages/57/cc/ed6bbc5a3bdb36ae1bca900bbbfdcb23b260ef2767a7b2dab38b92f61adf/coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770", size = 249140, upload-time = "2025-07-27T14:11:24.743Z" }, - { url = "https://files.pythonhosted.org/packages/10/f5/e881ade2d8e291b60fa1d93d6d736107e940144d80d21a0d4999cff3642f/coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262", size = 246869, upload-time = "2025-07-27T14:11:26.156Z" }, - { url = "https://files.pythonhosted.org/packages/53/b9/6a5665cb8996e3cd341d184bb11e2a8edf01d8dadcf44eb1e742186cf243/coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3", size = 244899, upload-time = "2025-07-27T14:11:27.622Z" }, - { url = "https://files.pythonhosted.org/packages/27/11/24156776709c4e25bf8a33d6bb2ece9a9067186ddac19990f6560a7f8130/coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0", size = 245507, upload-time = "2025-07-27T14:11:29.544Z" }, - { url = "https://files.pythonhosted.org/packages/43/db/a6f0340b7d6802a79928659c9a32bc778ea420e87a61b568d68ac36d45a8/coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be", size = 217167, upload-time = "2025-07-27T14:11:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/1990eb4fd05cea4cfabdf1d587a997ac5f9a8bee883443a1d519a2a848c9/coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c", size = 218054, upload-time = "2025-07-27T14:11:33.202Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4d/5e061d6020251b20e9b4303bb0b7900083a1a384ec4e5db326336c1c4abd/coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293", size = 216483, upload-time = "2025-07-27T14:11:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3f/b051feeb292400bd22d071fdf933b3ad389a8cef5c80c7866ed0c7414b9e/coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4", size = 214934, upload-time = "2025-07-27T14:11:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e4/a61b27d5c4c2d185bdfb0bfe9d15ab4ac4f0073032665544507429ae60eb/coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e", size = 215173, upload-time = "2025-07-27T14:11:38.005Z" }, - { url = "https://files.pythonhosted.org/packages/8a/01/40a6ee05b60d02d0bc53742ad4966e39dccd450aafb48c535a64390a3552/coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4", size = 246190, upload-time = "2025-07-27T14:11:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/11/ef/a28d64d702eb583c377255047281305dc5a5cfbfb0ee36e721f78255adb6/coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a", size = 248618, upload-time = "2025-07-27T14:11:41.841Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ad/73d018bb0c8317725370c79d69b5c6e0257df84a3b9b781bda27a438a3be/coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe", size = 250081, upload-time = "2025-07-27T14:11:43.705Z" }, - { url = "https://files.pythonhosted.org/packages/2d/dd/496adfbbb4503ebca5d5b2de8bed5ec00c0a76558ffc5b834fd404166bc9/coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386", size = 247990, upload-time = "2025-07-27T14:11:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/18/3c/a9331a7982facfac0d98a4a87b36ae666fe4257d0f00961a3a9ef73e015d/coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6", size = 246191, upload-time = "2025-07-27T14:11:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/62/0c/75345895013b83f7afe92ec595e15a9a525ede17491677ceebb2ba5c3d85/coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f", size = 247400, upload-time = "2025-07-27T14:11:48.643Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/98b268cfc5619ef9df1d5d34fee408ecb1542d9fd43d467e5c2f28668cd4/coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca", size = 217338, upload-time = "2025-07-27T14:11:50.258Z" }, - { url = "https://files.pythonhosted.org/packages/fe/31/22a5440e4d1451f253c5cd69fdcead65e92ef08cd4ec237b8756dc0b20a7/coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3", size = 218125, upload-time = "2025-07-27T14:11:52.034Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2b/40d9f0ce7ee839f08a43c5bfc9d05cec28aaa7c9785837247f96cbe490b9/coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4", size = 216523, upload-time = "2025-07-27T14:11:53.965Z" }, - { url = "https://files.pythonhosted.org/packages/ef/72/135ff5fef09b1ffe78dbe6fcf1e16b2e564cd35faeacf3d63d60d887f12d/coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39", size = 214960, upload-time = "2025-07-27T14:11:55.959Z" }, - { url = "https://files.pythonhosted.org/packages/b1/aa/73a5d1a6fc08ca709a8177825616aa95ee6bf34d522517c2595484a3e6c9/coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7", size = 215220, upload-time = "2025-07-27T14:11:57.899Z" }, - { url = "https://files.pythonhosted.org/packages/8d/40/3124fdd45ed3772a42fc73ca41c091699b38a2c3bd4f9cb564162378e8b6/coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892", size = 245772, upload-time = "2025-07-27T14:12:00.422Z" }, - { url = "https://files.pythonhosted.org/packages/42/62/a77b254822efa8c12ad59e8039f2bc3df56dc162ebda55e1943e35ba31a5/coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7", size = 248116, upload-time = "2025-07-27T14:12:03.099Z" }, - { url = "https://files.pythonhosted.org/packages/1d/01/8101f062f472a3a6205b458d18ef0444a63ae5d36a8a5ed5dd0f6167f4db/coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994", size = 249554, upload-time = "2025-07-27T14:12:04.668Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7b/e51bc61573e71ff7275a4f167aecbd16cb010aefdf54bcd8b0a133391263/coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0", size = 247766, upload-time = "2025-07-27T14:12:06.234Z" }, - { url = "https://files.pythonhosted.org/packages/4b/71/1c96d66a51d4204a9d6d12df53c4071d87e110941a2a1fe94693192262f5/coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7", size = 245735, upload-time = "2025-07-27T14:12:08.305Z" }, - { url = "https://files.pythonhosted.org/packages/13/d5/efbc2ac4d35ae2f22ef6df2ca084c60e13bd9378be68655e3268c80349ab/coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7", size = 247118, upload-time = "2025-07-27T14:12:09.903Z" }, - { url = "https://files.pythonhosted.org/packages/d1/22/073848352bec28ca65f2b6816b892fcf9a31abbef07b868487ad15dd55f1/coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7", size = 217381, upload-time = "2025-07-27T14:12:11.535Z" }, - { url = "https://files.pythonhosted.org/packages/b7/df/df6a0ff33b042f000089bd11b6bb034bab073e2ab64a56e78ed882cba55d/coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e", size = 218152, upload-time = "2025-07-27T14:12:13.182Z" }, - { url = "https://files.pythonhosted.org/packages/30/e3/5085ca849a40ed6b47cdb8f65471c2f754e19390b5a12fa8abd25cbfaa8f/coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4", size = 216559, upload-time = "2025-07-27T14:12:14.807Z" }, - { url = "https://files.pythonhosted.org/packages/cc/93/58714efbfdeb547909feaabe1d67b2bdd59f0597060271b9c548d5efb529/coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72", size = 215677, upload-time = "2025-07-27T14:12:16.68Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0c/18eaa5897e7e8cb3f8c45e563e23e8a85686b4585e29d53cacb6bc9cb340/coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af", size = 215899, upload-time = "2025-07-27T14:12:18.758Z" }, - { url = "https://files.pythonhosted.org/packages/84/c1/9d1affacc3c75b5a184c140377701bbf14fc94619367f07a269cd9e4fed6/coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7", size = 257140, upload-time = "2025-07-27T14:12:20.357Z" }, - { url = "https://files.pythonhosted.org/packages/3d/0f/339bc6b8fa968c346df346068cca1f24bdea2ddfa93bb3dc2e7749730962/coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759", size = 259005, upload-time = "2025-07-27T14:12:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/c8/22/89390864b92ea7c909079939b71baba7e5b42a76bf327c1d615bd829ba57/coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324", size = 261143, upload-time = "2025-07-27T14:12:23.746Z" }, - { url = "https://files.pythonhosted.org/packages/2c/56/3d04d89017c0c41c7a71bd69b29699d919b6bbf2649b8b2091240b97dd6a/coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53", size = 258735, upload-time = "2025-07-27T14:12:25.73Z" }, - { url = "https://files.pythonhosted.org/packages/cb/40/312252c8afa5ca781063a09d931f4b9409dc91526cd0b5a2b84143ffafa2/coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f", size = 256871, upload-time = "2025-07-27T14:12:27.767Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2b/564947d5dede068215aaddb9e05638aeac079685101462218229ddea9113/coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd", size = 257692, upload-time = "2025-07-27T14:12:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/93/1b/c8a867ade85cb26d802aea2209b9c2c80613b9c122baa8c8ecea6799648f/coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c", size = 218059, upload-time = "2025-07-27T14:12:31.076Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fe/cd4ab40570ae83a516bf5e754ea4388aeedd48e660e40c50b7713ed4f930/coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18", size = 219150, upload-time = "2025-07-27T14:12:32.746Z" }, - { url = "https://files.pythonhosted.org/packages/8d/16/6e5ed5854be6d70d0c39e9cb9dd2449f2c8c34455534c32c1a508c7dbdb5/coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4", size = 217014, upload-time = "2025-07-27T14:12:34.406Z" }, - { url = "https://files.pythonhosted.org/packages/54/8e/6d0bfe9c3d7121cf936c5f8b03e8c3da1484fb801703127dba20fb8bd3c7/coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c", size = 214951, upload-time = "2025-07-27T14:12:36.069Z" }, - { url = "https://files.pythonhosted.org/packages/f2/29/e3e51a8c653cf2174c60532aafeb5065cea0911403fa144c9abe39790308/coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e", size = 215229, upload-time = "2025-07-27T14:12:37.759Z" }, - { url = "https://files.pythonhosted.org/packages/e0/59/3c972080b2fa18b6c4510201f6d4dc87159d450627d062cd9ad051134062/coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b", size = 245738, upload-time = "2025-07-27T14:12:39.453Z" }, - { url = "https://files.pythonhosted.org/packages/2e/04/fc0d99d3f809452654e958e1788454f6e27b34e43f8f8598191c8ad13537/coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41", size = 248045, upload-time = "2025-07-27T14:12:41.387Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2e/afcbf599e77e0dfbf4c97197747250d13d397d27e185b93987d9eaac053d/coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f", size = 249666, upload-time = "2025-07-27T14:12:43.056Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ae/bc47f7f8ecb7a06cbae2bf86a6fa20f479dd902bc80f57cff7730438059d/coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1", size = 247692, upload-time = "2025-07-27T14:12:44.83Z" }, - { url = "https://files.pythonhosted.org/packages/b6/26/cbfa3092d31ccba8ba7647e4d25753263e818b4547eba446b113d7d1efdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2", size = 245536, upload-time = "2025-07-27T14:12:46.527Z" }, - { url = "https://files.pythonhosted.org/packages/56/77/9c68e92500e6a1c83d024a70eadcc9a173f21aadd73c4675fe64c9c43fdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4", size = 246954, upload-time = "2025-07-27T14:12:49.279Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a5/ba96671c5a669672aacd9877a5987c8551501b602827b4e84256da2a30a7/coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613", size = 217616, upload-time = "2025-07-27T14:12:51.214Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/e1e1eb95fc1585f15a410208c4795db24a948e04d9bde818fe4eb893bc85/coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e", size = 218412, upload-time = "2025-07-27T14:12:53.429Z" }, - { url = "https://files.pythonhosted.org/packages/b0/85/7e1e5be2cb966cba95566ba702b13a572ca744fbb3779df9888213762d67/coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652", size = 216776, upload-time = "2025-07-27T14:12:55.482Z" }, - { url = "https://files.pythonhosted.org/packages/62/0f/5bb8f29923141cca8560fe2217679caf4e0db643872c1945ac7d8748c2a7/coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894", size = 215698, upload-time = "2025-07-27T14:12:57.225Z" }, - { url = "https://files.pythonhosted.org/packages/80/29/547038ffa4e8e4d9e82f7dfc6d152f75fcdc0af146913f0ba03875211f03/coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5", size = 215902, upload-time = "2025-07-27T14:12:59.071Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8a/7aaa8fbfaed900147987a424e112af2e7790e1ac9cd92601e5bd4e1ba60a/coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2", size = 257230, upload-time = "2025-07-27T14:13:01.248Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1d/c252b5ffac44294e23a0d79dd5acf51749b39795ccc898faeabf7bee903f/coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb", size = 259194, upload-time = "2025-07-27T14:13:03.247Z" }, - { url = "https://files.pythonhosted.org/packages/16/ad/6c8d9f83d08f3bac2e7507534d0c48d1a4f52c18e6f94919d364edbdfa8f/coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b", size = 261316, upload-time = "2025-07-27T14:13:04.957Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4e/f9bbf3a36c061e2e0e0f78369c006d66416561a33d2bee63345aee8ee65e/coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea", size = 258794, upload-time = "2025-07-27T14:13:06.715Z" }, - { url = "https://files.pythonhosted.org/packages/87/82/e600bbe78eb2cb0541751d03cef9314bcd0897e8eea156219c39b685f869/coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd", size = 256869, upload-time = "2025-07-27T14:13:08.933Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/2fc9a9236c5268f68ac011d97cd3a5ad16cc420535369bedbda659fdd9b7/coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d", size = 257765, upload-time = "2025-07-27T14:13:10.778Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/b4e00b2bd48a2dc8e1c7d2aea7455f40af2e36484ab2ef06deb85883e9fe/coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47", size = 218420, upload-time = "2025-07-27T14:13:12.882Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/d21d05f33ea27ece327422240e69654b5932b0b29e7fbc40fbab3cf199bf/coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651", size = 219536, upload-time = "2025-07-27T14:13:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/a6/68/7fea94b141281ed8be3d1d5c4319a97f2befc3e487ce33657fc64db2c45e/coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab", size = 217190, upload-time = "2025-07-27T14:13:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/0f/64/922899cff2c0fd3496be83fa8b81230f5a8d82a2ad30f98370b133c2c83b/coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7", size = 206597, upload-time = "2025-07-27T14:13:37.221Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - -[[package]] -name = "crc32c" -version = "2.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/4c/4e40cc26347ac8254d3f25b9f94710b8e8df24ee4dddc1ba41907a88a94d/crc32c-2.7.1.tar.gz", hash = "sha256:f91b144a21eef834d64178e01982bb9179c354b3e9e5f4c803b0e5096384968c", size = 45712, upload-time = "2024-09-24T06:20:17.553Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/f8/2c5cc5b8d16c76a66548283d74d1f4979c8970c2a274e63f76fbfaa0cf4e/crc32c-2.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fd1f9c6b50d7357736676278a1b8c8986737b8a1c76d7eab4baa71d0b6af67f", size = 49668, upload-time = "2024-09-24T06:18:02.204Z" }, - { url = "https://files.pythonhosted.org/packages/35/52/cdebceaed37a5657ee4864881da0f29f4036867dfb79bb058d38d4d737f3/crc32c-2.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:805c2be1bc0e251c48439a62b0422385899c15289483692bc70e78473c1039f1", size = 37151, upload-time = "2024-09-24T06:18:04.173Z" }, - { url = "https://files.pythonhosted.org/packages/1e/33/6476918b4cac85a18e32dc754e9d653b0dcd96518d661cbbf91bf8aec8cc/crc32c-2.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f4333e62b7844dfde112dbb8489fd2970358eddc3310db21e943a9f6994df749", size = 35370, upload-time = "2024-09-24T06:18:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fd/8972a70d7c39f37240f554c348fd9e15a4d8d0a548b1bc3139cd4e1cfb66/crc32c-2.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f0fadc741e79dc705e2d9ee967473e8a061d26b04310ed739f1ee292f33674f", size = 54110, upload-time = "2024-09-24T06:18:06.758Z" }, - { url = "https://files.pythonhosted.org/packages/35/be/0b045f84c7acc36312a91211190bf84e73a0bbd30f21cbaf3670c4dba9b2/crc32c-2.7.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91ced31055d26d59385d708bbd36689e1a1d604d4b0ceb26767eb5a83156f85d", size = 51792, upload-time = "2024-09-24T06:18:07.767Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e2/acaabbc172b7c45ec62f273cd2e214f626e2b4324eca9152dea6095a26f4/crc32c-2.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36ffa999b72e3c17f6a066ae9e970b40f8c65f38716e436c39a33b809bc6ed9f", size = 52884, upload-time = "2024-09-24T06:18:09.449Z" }, - { url = "https://files.pythonhosted.org/packages/60/40/963ba3d2ec0d8e4a2ceaf90e8f9cb10911a926fe75d4329e013a51343122/crc32c-2.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e80114dd7f462297e54d5da1b9ff472e5249c5a2b406aa51c371bb0edcbf76bd", size = 53888, upload-time = "2024-09-24T06:18:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b8/8a093b9dc1792b2cec9805e1428e97be0338a45ac9fae2fd5911613eacb1/crc32c-2.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:676f5b46da268b5190f9fb91b3f037a00d114b411313664438525db876adc71f", size = 52098, upload-time = "2024-09-24T06:18:12.522Z" }, - { url = "https://files.pythonhosted.org/packages/26/76/a254ddb4ae83b545f6e08af384d62268a99d00f5c58a468754f8416468ce/crc32c-2.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8d0e660c9ed269e90692993a4457a932fc22c9cc96caf79dd1f1a84da85bb312", size = 52716, upload-time = "2024-09-24T06:18:14.118Z" }, - { url = "https://files.pythonhosted.org/packages/b6/cb/6062806e5b6cb8d9af3c62945a5a07fa22c3b4dc59084d2fa2e533f9aaa1/crc32c-2.7.1-cp310-cp310-win32.whl", hash = "sha256:17a2c3f8c6d85b04b5511af827b5dbbda4e672d188c0b9f20a8156e93a1aa7b6", size = 38363, upload-time = "2024-09-24T06:18:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a9/dc935e26c8d7bd4722bc1312ed88f443e6e36816b46835b4464baa3f7c6d/crc32c-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3208764c29688f91a35392073229975dd7687b6cb9f76b919dae442cabcd5126", size = 39795, upload-time = "2024-09-24T06:18:17.182Z" }, - { url = "https://files.pythonhosted.org/packages/45/8e/2f37f46368bbfd50edfc11b96f0aa135699034b1b020966c70ebaff3463b/crc32c-2.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19e03a50545a3ef400bd41667d5525f71030488629c57d819e2dd45064f16192", size = 49672, upload-time = "2024-09-24T06:18:18.032Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b8/e52f7c4b045b871c2984d70f37c31d4861b533a8082912dfd107a96cf7c1/crc32c-2.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c03286b1e5ce9bed7090084f206aacd87c5146b4b10de56fe9e86cbbbf851cf", size = 37155, upload-time = "2024-09-24T06:18:19.373Z" }, - { url = "https://files.pythonhosted.org/packages/25/ee/0cfa82a68736697f3c7e435ba658c2ef8c997f42b89f6ab4545efe1b2649/crc32c-2.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ebbf144a1a56a532b353e81fa0f3edca4f4baa1bf92b1dde2c663a32bb6a15", size = 35372, upload-time = "2024-09-24T06:18:20.983Z" }, - { url = "https://files.pythonhosted.org/packages/aa/92/c878aaba81c431fcd93a059e9f6c90db397c585742793f0bf6e0c531cc67/crc32c-2.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96b794fd11945298fdd5eb1290a812efb497c14bc42592c5c992ca077458eeba", size = 54879, upload-time = "2024-09-24T06:18:23.085Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f5/ab828ab3907095e06b18918408748950a9f726ee2b37be1b0839fb925ee1/crc32c-2.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df7194dd3c0efb5a21f5d70595b7a8b4fd9921fbbd597d6d8e7a11eca3e2d27", size = 52588, upload-time = "2024-09-24T06:18:24.463Z" }, - { url = "https://files.pythonhosted.org/packages/6a/2b/9e29e9ac4c4213d60491db09487125db358cd9263490fbadbd55e48fbe03/crc32c-2.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d698eec444b18e296a104d0b9bb6c596c38bdcb79d24eba49604636e9d747305", size = 53674, upload-time = "2024-09-24T06:18:25.624Z" }, - { url = "https://files.pythonhosted.org/packages/79/ed/df3c4c14bf1b29f5c9b52d51fb6793e39efcffd80b2941d994e8f7f5f688/crc32c-2.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e07cf10ef852d219d179333fd706d1c415626f1f05e60bd75acf0143a4d8b225", size = 54691, upload-time = "2024-09-24T06:18:26.578Z" }, - { url = "https://files.pythonhosted.org/packages/0c/47/4917af3c9c1df2fff28bbfa6492673c9adeae5599dcc207bbe209847489c/crc32c-2.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2a051f296e6e92e13efee3b41db388931cdb4a2800656cd1ed1d9fe4f13a086", size = 52896, upload-time = "2024-09-24T06:18:28.174Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6f/26fc3dda5835cda8f6cd9d856afe62bdeae428de4c34fea200b0888e8835/crc32c-2.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1738259802978cdf428f74156175da6a5fdfb7256f647fdc0c9de1bc6cd7173", size = 53554, upload-time = "2024-09-24T06:18:29.104Z" }, - { url = "https://files.pythonhosted.org/packages/56/3e/6f39127f7027c75d130c0ba348d86a6150dff23761fbc6a5f71659f4521e/crc32c-2.7.1-cp311-cp311-win32.whl", hash = "sha256:f7786d219a1a1bf27d0aa1869821d11a6f8e90415cfffc1e37791690d4a848a1", size = 38370, upload-time = "2024-09-24T06:18:30.013Z" }, - { url = "https://files.pythonhosted.org/packages/c9/fb/1587c2705a3a47a3d0067eecf9a6fec510761c96dec45c7b038fb5c8ff46/crc32c-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:887f6844bb3ad35f0778cd10793ad217f7123a5422e40041231b8c4c7329649d", size = 39795, upload-time = "2024-09-24T06:18:31.324Z" }, - { url = "https://files.pythonhosted.org/packages/1d/02/998dc21333413ce63fe4c1ca70eafe61ca26afc7eb353f20cecdb77d614e/crc32c-2.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7d1c4e761fe42bf856130daf8b2658df33fe0ced3c43dadafdfeaa42b57b950", size = 49568, upload-time = "2024-09-24T06:18:32.425Z" }, - { url = "https://files.pythonhosted.org/packages/9c/3e/e3656bfa76e50ef87b7136fef2dbf3c46e225629432fc9184fdd7fd187ff/crc32c-2.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:73361c79a6e4605204457f19fda18b042a94508a52e53d10a4239da5fb0f6a34", size = 37019, upload-time = "2024-09-24T06:18:34.097Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7d/5ff9904046ad15a08772515db19df43107bf5e3901a89c36a577b5f40ba0/crc32c-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd778fc8ac0ed2ffbfb122a9aa6a0e409a8019b894a1799cda12c01534493e0", size = 35373, upload-time = "2024-09-24T06:18:35.02Z" }, - { url = "https://files.pythonhosted.org/packages/4d/41/4aedc961893f26858ab89fc772d0eaba91f9870f19eaa933999dcacb94ec/crc32c-2.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ef661b34e9f25991fface7f9ad85e81bbc1b3fe3b916fd58c893eabe2fa0b8", size = 54675, upload-time = "2024-09-24T06:18:35.954Z" }, - { url = "https://files.pythonhosted.org/packages/d6/63/8cabf09b7e39b9fec8f7010646c8b33057fc8d67e6093b3cc15563d23533/crc32c-2.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571aa4429444b5d7f588e4377663592145d2d25eb1635abb530f1281794fc7c9", size = 52386, upload-time = "2024-09-24T06:18:36.896Z" }, - { url = "https://files.pythonhosted.org/packages/79/13/13576941bf7cf95026abae43d8427c812c0054408212bf8ed490eda846b0/crc32c-2.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02a3bd67dea95cdb25844aaf44ca2e1b0c1fd70b287ad08c874a95ef4bb38db", size = 53495, upload-time = "2024-09-24T06:18:38.099Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b6/55ffb26d0517d2d6c6f430ce2ad36ae7647c995c5bfd7abce7f32bb2bad1/crc32c-2.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d17637c4867672cb8adeea007294e3c3df9d43964369516cfe2c1f47ce500a", size = 54456, upload-time = "2024-09-24T06:18:39.051Z" }, - { url = "https://files.pythonhosted.org/packages/c2/1a/5562e54cb629ecc5543d3604dba86ddfc7c7b7bf31d64005b38a00d31d31/crc32c-2.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4a400ac3c69a32e180d8753fd7ec7bccb80ade7ab0812855dce8a208e72495f", size = 52647, upload-time = "2024-09-24T06:18:40.021Z" }, - { url = "https://files.pythonhosted.org/packages/48/ec/ce4138eaf356cd9aae60bbe931755e5e0151b3eca5f491fce6c01b97fd59/crc32c-2.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:588587772e55624dd9c7a906ec9e8773ae0b6ac5e270fc0bc84ee2758eba90d5", size = 53332, upload-time = "2024-09-24T06:18:40.925Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b5/144b42cd838a901175a916078781cb2c3c9f977151c9ba085aebd6d15b22/crc32c-2.7.1-cp312-cp312-win32.whl", hash = "sha256:9f14b60e5a14206e8173dd617fa0c4df35e098a305594082f930dae5488da428", size = 38371, upload-time = "2024-09-24T06:18:42.711Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c4/7929dcd5d9b57db0cce4fe6f6c191049380fc6d8c9b9f5581967f4ec018e/crc32c-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c810a246660a24dc818047dc5f89c7ce7b2814e1e08a8e99993f4103f7219e8", size = 39805, upload-time = "2024-09-24T06:18:43.6Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/1a6d60d5b3b5edc8382777b64100343cb4aa6a7e172fae4a6cfcb8ebbbd9/crc32c-2.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:24949bffb06fc411cc18188d33357923cb935273642164d0bb37a5f375654169", size = 49567, upload-time = "2024-09-24T06:18:44.485Z" }, - { url = "https://files.pythonhosted.org/packages/4f/56/0dd652d4e950e6348bbf16b964b3325e4ad8220470774128fc0b0dd069cb/crc32c-2.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2d5d326e7e118d4fa60187770d86b66af2fdfc63ce9eeb265f0d3e7d49bebe0b", size = 37018, upload-time = "2024-09-24T06:18:45.434Z" }, - { url = "https://files.pythonhosted.org/packages/47/02/2bd65fdef10139b6a802d83a7f966b7750fe5ffb1042f7cbe5dbb6403869/crc32c-2.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba110df60c64c8e2d77a9425b982a520ccdb7abe42f06604f4d98a45bb1fff62", size = 35374, upload-time = "2024-09-24T06:18:46.304Z" }, - { url = "https://files.pythonhosted.org/packages/a9/0d/3e797d1ed92d357a6a4c5b41cea15a538b27a8fdf18c7863747eb50b73ad/crc32c-2.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c277f9d16a3283e064d54854af0976b72abaa89824955579b2b3f37444f89aae", size = 54641, upload-time = "2024-09-24T06:18:47.207Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d3/4ddeef755caaa75680c559562b6c71f5910fee4c4f3a2eb5ea8b57f0e48c/crc32c-2.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881af0478a01331244e27197356929edbdeaef6a9f81b5c6bacfea18d2139289", size = 52338, upload-time = "2024-09-24T06:18:49.31Z" }, - { url = "https://files.pythonhosted.org/packages/01/cf/32f019be5de9f6e180926a50ee5f08648e686c7d9a59f2c5d0806a77b1c7/crc32c-2.7.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:724d5ff4d29ff093a983ae656be3307093706d850ea2a233bf29fcacc335d945", size = 53447, upload-time = "2024-09-24T06:18:50.296Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/92f3f62f3bafe8f7ab4af7bfb7246dc683fd11ec0d6dfb73f91e09079f69/crc32c-2.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2416c4d88696ac322632555c0f81ab35e15f154bc96055da6cf110d642dbc10", size = 54484, upload-time = "2024-09-24T06:18:51.311Z" }, - { url = "https://files.pythonhosted.org/packages/98/b2/113a50f8781f76af5ac65ffdb907e72bddbe974de8e02247f0d58bc48040/crc32c-2.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:60254251b88ec9b9795215f0f9ec015a6b5eef8b2c5fba1267c672d83c78fc02", size = 52703, upload-time = "2024-09-24T06:18:52.488Z" }, - { url = "https://files.pythonhosted.org/packages/b4/6c/309229e9acda8cf36a8ff4061d70b54d905f79b7037e16883ce6590a24ab/crc32c-2.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:edefc0e46f3c37372183f70338e5bdee42f6789b62fcd36ec53aa933e9dfbeaf", size = 53367, upload-time = "2024-09-24T06:18:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/6c6324d920396e1bd9f3efbe8753da071be0ca52bd22d6c82d446b8d6975/crc32c-2.7.1-cp313-cp313-win32.whl", hash = "sha256:813af8111218970fe2adb833c5e5239f091b9c9e76f03b4dd91aaba86e99b499", size = 38377, upload-time = "2024-09-24T06:18:54.487Z" }, - { url = "https://files.pythonhosted.org/packages/db/a0/f01ccfab538db07ef3f6b4ede46357ff147a81dd4f3c59ca6a34c791a549/crc32c-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:7d9ede7be8e4ec1c9e90aaf6884decbeef10e3473e6ddac032706d710cab5888", size = 39803, upload-time = "2024-09-24T06:18:55.419Z" }, - { url = "https://files.pythonhosted.org/packages/1b/80/61dcae7568b33acfde70c9d651c7d891c0c578c39cc049107c1cf61f1367/crc32c-2.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db9ac92294284b22521356715784b91cc9094eee42a5282ab281b872510d1831", size = 49386, upload-time = "2024-09-24T06:18:56.813Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f1/80f17c089799ab2b4c247443bdd101d6ceda30c46d7f193e16b5ca29c5a0/crc32c-2.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8fcd7f2f29a30dc92af64a9ee3d38bde0c82bd20ad939999427aac94bbd87373", size = 36937, upload-time = "2024-09-24T06:18:57.77Z" }, - { url = "https://files.pythonhosted.org/packages/63/42/5fcfc71a3de493d920fd2590843762a2749981ea56b802b380e5df82309d/crc32c-2.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5c056ef043393085523e149276a7ce0cb534b872e04f3e20d74d9a94a75c0ad7", size = 35292, upload-time = "2024-09-24T06:18:58.676Z" }, - { url = "https://files.pythonhosted.org/packages/03/de/fef962e898a953558fe1c55141644553e84ef4190693a31244c59a0856c7/crc32c-2.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03a92551a343702629af91f78d205801219692b6909f8fa126b830e332bfb0e0", size = 54223, upload-time = "2024-09-24T06:18:59.675Z" }, - { url = "https://files.pythonhosted.org/packages/21/14/fceca1a6f45c0a1814fe8602a65657b75c27425162445925ba87438cad6b/crc32c-2.7.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb9424ec1a8ca54763155a703e763bcede82e6569fe94762614bb2de1412d4e1", size = 51588, upload-time = "2024-09-24T06:19:00.938Z" }, - { url = "https://files.pythonhosted.org/packages/13/3b/13d40a7dfbf9ef05c84a0da45544ee72080dca4ce090679e5105689984bd/crc32c-2.7.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88732070f6175530db04e0bb36880ac45c33d49f8ac43fa0e50cfb1830049d23", size = 52678, upload-time = "2024-09-24T06:19:02.661Z" }, - { url = "https://files.pythonhosted.org/packages/36/09/65ffc4fb9fa60ff6714eeb50a92284a4525e5943f0b040b572c0c76368c1/crc32c-2.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57a20dfc27995f568f64775eea2bbb58ae269f1a1144561df5e4a4955f79db32", size = 53847, upload-time = "2024-09-24T06:19:03.705Z" }, - { url = "https://files.pythonhosted.org/packages/24/71/938e926085b7288da052db7c84416f3ce25e71baf7ab5b63824c7bcb6f22/crc32c-2.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f7186d098bfd2cff25eac6880b7c7ad80431b90610036131c1c7dd0eab42a332", size = 51860, upload-time = "2024-09-24T06:19:04.726Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d8/4526d5380189d6f2fa27256c204100f30214fe402f47cf6e9fb9a91ab890/crc32c-2.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:55a77e29a265418fa34bef15bd0f2c60afae5348988aaf35ed163b4bbf93cf37", size = 52508, upload-time = "2024-09-24T06:19:05.731Z" }, - { url = "https://files.pythonhosted.org/packages/19/30/15f7e35176488b77e5b88751947d321d603fccac273099ace27c7b2d50a6/crc32c-2.7.1-cp313-cp313t-win32.whl", hash = "sha256:ae38a4b6aa361595d81cab441405fbee905c72273e80a1c010fb878ae77ac769", size = 38319, upload-time = "2024-09-24T06:19:07.233Z" }, - { url = "https://files.pythonhosted.org/packages/19/c4/0b3eee04dac195f4730d102d7a9fbea894ae7a32ce075f84336df96a385d/crc32c-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:eee2a43b663feb6c79a6c1c6e5eae339c2b72cfac31ee54ec0209fa736cf7ee5", size = 39781, upload-time = "2024-09-24T06:19:08.182Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e0/14d392075db47c53a3890aa110e3640b22fb9736949c47b13d5b5e4599f7/crc32c-2.7.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2e83fedebcdeb80c19e76b7a0e5103528bb062521c40702bf34516a429e81df3", size = 36448, upload-time = "2024-09-24T06:19:50.456Z" }, - { url = "https://files.pythonhosted.org/packages/03/27/f1dab3066c90e9424d22bc942eecdc2e77267f7e805ddb5a2419bbcbace6/crc32c-2.7.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30004a7383538ef93bda9b22f7b3805bc0aa5625ab2675690e1b676b19417d4b", size = 38184, upload-time = "2024-09-24T06:19:51.466Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f3/479acfa99803c261cdd6b44f37b55bd77bdbce6163ec1f51b2014b095495/crc32c-2.7.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a01b0983aa87f517c12418f9898ecf2083bf86f4ea04122e053357c3edb0d73f", size = 38256, upload-time = "2024-09-24T06:19:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/4edb9c45457c54d51ca539f850761f31b7ab764177902b6f3247ff8c1b75/crc32c-2.7.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb2b963c42128b38872e9ed63f04a73ce1ff89a1dfad7ea38add6fe6296497b8", size = 37868, upload-time = "2024-09-24T06:19:54.159Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b0/5680db08eff8f2116a4f9bd949f8bbe9cf260e1c3451228f54c60b110d79/crc32c-2.7.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cdd5e576fee5d255c1e68a4dae4420f21e57e6f05900b38d5ae47c713fc3330d", size = 39826, upload-time = "2024-09-24T06:19:55.4Z" }, -] - -[[package]] -name = "cryptography" -version = "45.0.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" }, - { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" }, - { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" }, - { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" }, - { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" }, - { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" }, - { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" }, - { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" }, - { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" }, - { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" }, - { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" }, - { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" }, - { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" }, - { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" }, - { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" }, - { url = "https://files.pythonhosted.org/packages/56/d2/4482d97c948c029be08cb29854a91bd2ae8da7eb9c4152461f1244dcea70/cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012", size = 3576812, upload-time = "2025-08-05T23:59:04.833Z" }, - { url = "https://files.pythonhosted.org/packages/ec/24/55fc238fcaa122855442604b8badb2d442367dfbd5a7ca4bb0bd346e263a/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d", size = 4141694, upload-time = "2025-08-05T23:59:06.66Z" }, - { url = "https://files.pythonhosted.org/packages/f9/7e/3ea4fa6fbe51baf3903806a0241c666b04c73d2358a3ecce09ebee8b9622/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d", size = 4375010, upload-time = "2025-08-05T23:59:08.14Z" }, - { url = "https://files.pythonhosted.org/packages/50/42/ec5a892d82d2a2c29f80fc19ced4ba669bca29f032faf6989609cff1f8dc/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da", size = 4141377, upload-time = "2025-08-05T23:59:09.584Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d7/246c4c973a22b9c2931999da953a2c19cae7c66b9154c2d62ffed811225e/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db", size = 4374609, upload-time = "2025-08-05T23:59:11.923Z" }, - { url = "https://files.pythonhosted.org/packages/78/6d/c49ccf243f0a1b0781c2a8de8123ee552f0c8a417c6367a24d2ecb7c11b3/cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18", size = 3322156, upload-time = "2025-08-05T23:59:13.597Z" }, - { url = "https://files.pythonhosted.org/packages/61/69/c252de4ec047ba2f567ecb53149410219577d408c2aea9c989acae7eafce/cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983", size = 3584669, upload-time = "2025-08-05T23:59:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fe/deea71e9f310a31fe0a6bfee670955152128d309ea2d1c79e2a5ae0f0401/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427", size = 4153022, upload-time = "2025-08-05T23:59:16.954Z" }, - { url = "https://files.pythonhosted.org/packages/60/45/a77452f5e49cb580feedba6606d66ae7b82c128947aa754533b3d1bd44b0/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b", size = 4386802, upload-time = "2025-08-05T23:59:18.55Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b9/a2f747d2acd5e3075fdf5c145c7c3568895daaa38b3b0c960ef830db6cdc/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c", size = 4152706, upload-time = "2025-08-05T23:59:20.044Z" }, - { url = "https://files.pythonhosted.org/packages/81/ec/381b3e8d0685a3f3f304a382aa3dfce36af2d76467da0fd4bb21ddccc7b2/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385", size = 4386740, upload-time = "2025-08-05T23:59:21.525Z" }, - { url = "https://files.pythonhosted.org/packages/0a/76/cf8d69da8d0b5ecb0db406f24a63a3f69ba5e791a11b782aeeefef27ccbb/cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043", size = 3331874, upload-time = "2025-08-05T23:59:23.017Z" }, -] - -[[package]] -name = "dask" -version = "2025.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "cloudpickle" }, - { name = "fsspec" }, - { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, - { name = "packaging" }, - { name = "partd" }, - { name = "pyyaml" }, - { name = "toolz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/00/cbb7cb07742955dfe77368aa40725d7000414a8a49f415ba40c5a4379ba9/dask-2025.7.0.tar.gz", hash = "sha256:c3a0d4e78882e85ea81dbc71e6459713e45676e2d17e776c2f3f19848039e4cf", size = 10972242, upload-time = "2025-07-14T20:03:42.701Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/f9/3e04725358c17329652da8c1b2dbd88de723f3dc78bf52ca6d28d52c9279/dask-2025.7.0-py3-none-any.whl", hash = "sha256:073c29c4e99c2400a39a8a67874f35c1d15bf7af9ae3d0c30af3c7c1199f24ae", size = 1475117, upload-time = "2025-07-14T20:03:33.095Z" }, -] - -[package.optional-dependencies] -array = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] - -[[package]] -name = "debugpy" -version = "1.8.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/3a9a28ddb750a76eaec445c7f4d3147ea2c579a97dbd9e25d39001b92b21/debugpy-1.8.15.tar.gz", hash = "sha256:58d7a20b7773ab5ee6bdfb2e6cf622fdf1e40c9d5aef2857d85391526719ac00", size = 1643279, upload-time = "2025-07-15T16:43:29.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/51/0b4315169f0d945271db037ae6b98c0548a2d48cc036335cd1b2f5516c1b/debugpy-1.8.15-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:e9a8125c85172e3ec30985012e7a81ea5e70bbb836637f8a4104f454f9b06c97", size = 2084890, upload-time = "2025-07-15T16:43:31.239Z" }, - { url = "https://files.pythonhosted.org/packages/36/cc/a5391dedb079280d7b72418022e00ba8227ae0b5bc8b2e3d1ecffc5d6b01/debugpy-1.8.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd0b6b5eccaa745c214fd240ea82f46049d99ef74b185a3517dad3ea1ec55d9", size = 3561470, upload-time = "2025-07-15T16:43:32.515Z" }, - { url = "https://files.pythonhosted.org/packages/e8/92/acf64b92010c66b33c077dee3862c733798a2c90e7d14b25c01d771e2a0d/debugpy-1.8.15-cp310-cp310-win32.whl", hash = "sha256:8181cce4d344010f6bfe94a531c351a46a96b0f7987750932b2908e7a1e14a55", size = 5229194, upload-time = "2025-07-15T16:43:33.997Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f5/c58c015c9ff78de35901bea3ab4dbf7946d7a4aa867ee73875df06ba6468/debugpy-1.8.15-cp310-cp310-win_amd64.whl", hash = "sha256:af2dcae4e4cd6e8b35f982ccab29fe65f7e8766e10720a717bc80c464584ee21", size = 5260900, upload-time = "2025-07-15T16:43:35.413Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b3/1c44a2ed311199ab11c2299c9474a6c7cd80d19278defd333aeb7c287995/debugpy-1.8.15-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:babc4fb1962dd6a37e94d611280e3d0d11a1f5e6c72ac9b3d87a08212c4b6dd3", size = 2183442, upload-time = "2025-07-15T16:43:36.733Z" }, - { url = "https://files.pythonhosted.org/packages/f6/69/e2dcb721491e1c294d348681227c9b44fb95218f379aa88e12a19d85528d/debugpy-1.8.15-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f778e68f2986a58479d0ac4f643e0b8c82fdd97c2e200d4d61e7c2d13838eb53", size = 3134215, upload-time = "2025-07-15T16:43:38.116Z" }, - { url = "https://files.pythonhosted.org/packages/17/76/4ce63b95d8294dcf2fd1820860b300a420d077df4e93afcaa25a984c2ca7/debugpy-1.8.15-cp311-cp311-win32.whl", hash = "sha256:f9d1b5abd75cd965e2deabb1a06b0e93a1546f31f9f621d2705e78104377c702", size = 5154037, upload-time = "2025-07-15T16:43:39.471Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/e5a7c784465eb9c976d84408873d597dc7ce74a0fc69ed009548a1a94813/debugpy-1.8.15-cp311-cp311-win_amd64.whl", hash = "sha256:62954fb904bec463e2b5a415777f6d1926c97febb08ef1694da0e5d1463c5c3b", size = 5178133, upload-time = "2025-07-15T16:43:40.969Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4a/4508d256e52897f5cdfee6a6d7580974811e911c6d01321df3264508a5ac/debugpy-1.8.15-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:3dcc7225cb317469721ab5136cda9ff9c8b6e6fb43e87c9e15d5b108b99d01ba", size = 2511197, upload-time = "2025-07-15T16:43:42.343Z" }, - { url = "https://files.pythonhosted.org/packages/99/8d/7f6ef1097e7fecf26b4ef72338d08e41644a41b7ee958a19f494ffcffc29/debugpy-1.8.15-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:047a493ca93c85ccede1dbbaf4e66816794bdc214213dde41a9a61e42d27f8fc", size = 4229517, upload-time = "2025-07-15T16:43:44.14Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e8/e8c6a9aa33a9c9c6dacbf31747384f6ed2adde4de2e9693c766bdf323aa3/debugpy-1.8.15-cp312-cp312-win32.whl", hash = "sha256:b08e9b0bc260cf324c890626961dad4ffd973f7568fbf57feb3c3a65ab6b6327", size = 5276132, upload-time = "2025-07-15T16:43:45.529Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ad/231050c6177b3476b85fcea01e565dac83607b5233d003ff067e2ee44d8f/debugpy-1.8.15-cp312-cp312-win_amd64.whl", hash = "sha256:e2a4fe357c92334272eb2845fcfcdbec3ef9f22c16cf613c388ac0887aed15fa", size = 5317645, upload-time = "2025-07-15T16:43:46.968Z" }, - { url = "https://files.pythonhosted.org/packages/28/70/2928aad2310726d5920b18ed9f54b9f06df5aa4c10cf9b45fa18ff0ab7e8/debugpy-1.8.15-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:f5e01291ad7d6649aed5773256c5bba7a1a556196300232de1474c3c372592bf", size = 2495538, upload-time = "2025-07-15T16:43:48.927Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c6/9b8ffb4ca91fac8b2877eef63c9cc0e87dd2570b1120054c272815ec4cd0/debugpy-1.8.15-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dc0f0d00e528d915e0ce1c78e771475b2335b376c49afcc7382ee0b146bab6", size = 4221874, upload-time = "2025-07-15T16:43:50.282Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/9b8d59674b4bf489318c7c46a1aab58e606e583651438084b7e029bf3c43/debugpy-1.8.15-cp313-cp313-win32.whl", hash = "sha256:fcf0748d4f6e25f89dc5e013d1129ca6f26ad4da405e0723a4f704583896a709", size = 5275949, upload-time = "2025-07-15T16:43:52.079Z" }, - { url = "https://files.pythonhosted.org/packages/72/83/9e58e6fdfa8710a5e6ec06c2401241b9ad48b71c0a7eb99570a1f1edb1d3/debugpy-1.8.15-cp313-cp313-win_amd64.whl", hash = "sha256:73c943776cb83e36baf95e8f7f8da765896fd94b05991e7bc162456d25500683", size = 5317720, upload-time = "2025-07-15T16:43:53.703Z" }, - { url = "https://files.pythonhosted.org/packages/07/d5/98748d9860e767a1248b5e31ffa7ce8cb7006e97bf8abbf3d891d0a8ba4e/debugpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:bce2e6c5ff4f2e00b98d45e7e01a49c7b489ff6df5f12d881c67d2f1ac635f3d", size = 5282697, upload-time = "2025-07-15T16:44:07.996Z" }, -] - -[[package]] -name = "decorator" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, -] - -[[package]] -name = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, -] - -[[package]] -name = "distlib" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, -] - -[[package]] -name = "doc8" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "pygments" }, - { name = "restructuredtext-lint" }, - { name = "stevedore" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/91/88bb55225046a2ee9c2243d47346c78d2ed861c769168f451568625ad670/doc8-2.0.0.tar.gz", hash = "sha256:1267ad32758971fbcf991442417a3935c7bc9e52550e73622e0e56ba55ea1d40", size = 28436, upload-time = "2025-06-13T13:08:53.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/e9/90b7d243364d3dce38c8c2a1b8c103d7a8d1383c2b24c735fae0eee038dd/doc8-2.0.0-py3-none-any.whl", hash = "sha256:9862710027f793c25f9b1899150660e4bf1d4c9a6738742e71f32011e2e3f590", size = 25861, upload-time = "2025-06-13T13:08:51.839Z" }, -] - -[[package]] -name = "docutils" -version = "0.21.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, -] - -[[package]] -name = "donfig" -version = "0.8.1.post1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506, upload-time = "2024-05-23T14:14:31.513Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592, upload-time = "2024-05-23T14:13:55.283Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, -] - -[[package]] -name = "executing" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, -] - -[[package]] -name = "fasteners" -version = "0.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/18/7881a99ba5244bfc82f06017316ffe93217dbbbcfa52b887caa1d4f2a6d3/fasteners-0.20.tar.gz", hash = "sha256:55dce8792a41b56f727ba6e123fcaee77fd87e638a6863cec00007bfea84c8d8", size = 25087, upload-time = "2025-08-11T10:19:37.785Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, -] - -[[package]] -name = "fastjsonschema" -version = "2.21.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939, upload-time = "2024-12-02T10:55:15.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924, upload-time = "2024-12-02T10:55:07.599Z" }, -] - -[[package]] -name = "filelock" -version = "3.20.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, -] - -[[package]] -name = "fqdn" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" }, - { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" }, - { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" }, - { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" }, - { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" }, - { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" }, - { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" }, - { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" }, - { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" }, - { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" }, - { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" }, - { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, - { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, - { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, - { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, - { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, - { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, - { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, - { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, - { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, - { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, - { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, - { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, - { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, - { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, - { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, - { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, - { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, - { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, - { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, - { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, - { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, - { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, - { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, - { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, - { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, - { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, - { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, - { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, - { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, - { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, - { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, - { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, - { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, - { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, - { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, -] - -[[package]] -name = "fsspec" -version = "2025.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "html5lib" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "identify" -version = "2.6.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, -] - -[[package]] -name = "imagesize" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, -] - -[[package]] -name = "importlib-metadata" -version = "8.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - -[[package]] -name = "ipykernel" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appnope", marker = "sys_platform == 'darwin'" }, - { name = "comm" }, - { name = "debugpy" }, - { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, -] - -[[package]] -name = "ipython" -version = "8.37.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, -] - -[[package]] -name = "ipython" -version = "9.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338, upload-time = "2025-07-01T11:11:30.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021, upload-time = "2025-07-01T11:11:27.85Z" }, -] - -[[package]] -name = "ipython-pygments-lexers" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, -] - -[[package]] -name = "ipywidgets" -version = "8.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "comm" }, - { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jupyterlab-widgets" }, - { name = "traitlets" }, - { name = "widgetsnbextension" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/48/d3dbac45c2814cb73812f98dd6b38bbcc957a4e7bb31d6ea9c03bf94ed87/ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376", size = 116721, upload-time = "2025-05-05T12:42:03.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/6a/9166369a2f092bd286d24e6307de555d63616e8ddb373ebad2b5635ca4cd/ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb", size = 139806, upload-time = "2025-05-05T12:41:56.833Z" }, -] - -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - -[[package]] -name = "isoduration" -version = "20.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "arrow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, -] - -[[package]] -name = "jedi" -version = "0.19.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "parso" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "jmespath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, -] - -[[package]] -name = "json5" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907, upload-time = "2025-04-03T16:33:13.201Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079, upload-time = "2025-04-03T16:33:11.927Z" }, -] - -[[package]] -name = "jsonpointer" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.25.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" }, -] - -[package.optional-dependencies] -format-nongpl = [ - { name = "fqdn" }, - { name = "idna" }, - { name = "isoduration" }, - { name = "jsonpointer" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "rfc3987-syntax" }, - { name = "uri-template" }, - { name = "webcolors" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, -] - -[[package]] -name = "jupyter" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ipykernel" }, - { name = "ipywidgets" }, - { name = "jupyter-console" }, - { name = "jupyterlab" }, - { name = "nbconvert" }, - { name = "notebook" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959, upload-time = "2024-08-30T07:15:48.299Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657, upload-time = "2024-08-30T07:15:47.045Z" }, -] - -[[package]] -name = "jupyter-client" -version = "8.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-core" }, - { name = "python-dateutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, -] - -[[package]] -name = "jupyter-console" -version = "6.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ipykernel" }, - { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "pyzmq" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363, upload-time = "2023-03-06T14:13:31.02Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510, upload-time = "2023-03-06T14:13:28.229Z" }, -] - -[[package]] -name = "jupyter-core" -version = "5.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, - { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, -] - -[[package]] -name = "jupyter-events" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema", extra = ["format-nongpl"] }, - { name = "packaging" }, - { name = "python-json-logger" }, - { name = "pyyaml" }, - { name = "referencing" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, -] - -[[package]] -name = "jupyter-lsp" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/3d/40bdb41b665d3302390ed1356cebd5917c10769d1f190ee4ca595900840e/jupyter_lsp-2.2.6.tar.gz", hash = "sha256:0566bd9bb04fd9e6774a937ed01522b555ba78be37bebef787c8ab22de4c0361", size = 48948, upload-time = "2025-07-18T21:35:19.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/7c/12f68daf85b469b4896d5e4a629baa33c806d61de75ac5b39d8ef27ec4a2/jupyter_lsp-2.2.6-py3-none-any.whl", hash = "sha256:283783752bf0b459ee7fa88effa72104d87dd343b82d5c06cf113ef755b15b6d", size = 69371, upload-time = "2025-07-18T21:35:16.585Z" }, -] - -[[package]] -name = "jupyter-server" -version = "2.16.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "argon2-cffi" }, - { name = "jinja2" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "jupyter-events" }, - { name = "jupyter-server-terminals" }, - { name = "nbconvert" }, - { name = "nbformat" }, - { name = "overrides" }, - { name = "packaging" }, - { name = "prometheus-client" }, - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "pyzmq" }, - { name = "send2trash" }, - { name = "terminado" }, - { name = "tornado" }, - { name = "traitlets" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/c8/ba2bbcd758c47f1124c4ca14061e8ce60d9c6fd537faee9534a95f83521a/jupyter_server-2.16.0.tar.gz", hash = "sha256:65d4b44fdf2dcbbdfe0aa1ace4a842d4aaf746a2b7b168134d5aaed35621b7f6", size = 728177, upload-time = "2025-05-12T16:44:46.245Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/1f/5ebbced977171d09a7b0c08a285ff9a20aafb9c51bde07e52349ff1ddd71/jupyter_server-2.16.0-py3-none-any.whl", hash = "sha256:3d8db5be3bc64403b1c65b400a1d7f4647a5ce743f3b20dbdefe8ddb7b55af9e", size = 386904, upload-time = "2025-05-12T16:44:43.335Z" }, -] - -[[package]] -name = "jupyter-server-terminals" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "terminado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" }, -] - -[[package]] -name = "jupyterlab" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-lru" }, - { name = "httpx" }, - { name = "ipykernel" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyter-lsp" }, - { name = "jupyter-server" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "packaging" }, - { name = "setuptools" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/21/413d142686a4e8f4268d985becbdb4daf060524726248e73be4773786987/jupyterlab-4.5.1.tar.gz", hash = "sha256:09da1ddfbd9eec18b5101dbb8515612aa1e47443321fb99503725a88e93d20d9", size = 23992251, upload-time = "2025-12-15T16:58:59.361Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl", hash = "sha256:31b059de96de0754ff1f2ce6279774b6aab8c34d7082e9752db58207c99bd514", size = 12384821, upload-time = "2025-12-15T16:58:55.563Z" }, -] - -[[package]] -name = "jupyterlab-pygments" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, -] - -[[package]] -name = "jupyterlab-server" -version = "2.28.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "jinja2" }, - { name = "json5" }, - { name = "jsonschema" }, - { name = "jupyter-server" }, - { name = "packaging" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, -] - -[[package]] -name = "jupyterlab-widgets" -version = "3.0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/160595ca88ee87ac6ba95d82177d29ec60aaa63821d3077babb22ce031a5/jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b", size = 213149, upload-time = "2025-05-05T12:32:31.004Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/6a/ca128561b22b60bd5a0c4ea26649e68c8556b82bc70a0c396eebc977fe86/jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c", size = 216571, upload-time = "2025-05-05T12:32:29.534Z" }, -] - -[[package]] -name = "lark" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/60/bc7622aefb2aee1c0b4ba23c1446d3e30225c8770b38d7aedbfb65ca9d5a/lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80", size = 252132, upload-time = "2024-08-13T19:49:00.652Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/00/d90b10b962b4277f5e64a78b6609968859ff86889f5b898c1a778c06ec00/lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c", size = 111036, upload-time = "2024-08-13T19:48:58.603Z" }, -] - -[[package]] -name = "locket" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350, upload-time = "2022-04-20T22:04:44.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398, upload-time = "2022-04-20T22:04:42.23Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, -] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, -] - -[[package]] -name = "mistune" -version = "3.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/79/bda47f7dd7c3c55770478d6d02c9960c430b0cf1773b72366ff89126ea31/mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0", size = 94347, upload-time = "2025-03-19T14:27:24.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410, upload-time = "2025-03-19T14:27:23.451Z" }, -] - -[[package]] -name = "msal" -version = "1.33.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d5/da/81acbe0c1fd7e9e4ec35f55dadeba9833a847b9a6ba2e2d1e4432da901dd/msal-1.33.0.tar.gz", hash = "sha256:836ad80faa3e25a7d71015c990ce61f704a87328b1e73bcbb0623a18cbf17510", size = 153801, upload-time = "2025-07-22T19:36:33.693Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/5b/fbc73e91f7727ae1e79b21ed833308e99dc11cc1cd3d4717f579775de5e9/msal-1.33.0-py3-none-any.whl", hash = "sha256:c0cd41cecf8eaed733ee7e3be9e040291eba53b0f262d3ae9c58f38b04244273", size = 116853, upload-time = "2025-07-22T19:36:32.403Z" }, -] - -[[package]] -name = "msal-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, -] - -[[package]] -name = "multidict" -version = "6.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/67/414933982bce2efce7cbcb3169eaaf901e0f25baec69432b4874dfb1f297/multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817", size = 77017, upload-time = "2025-06-30T15:50:58.931Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fe/d8a3ee1fad37dc2ef4f75488b0d9d4f25bf204aad8306cbab63d97bff64a/multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140", size = 44897, upload-time = "2025-06-30T15:51:00.999Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e0/265d89af8c98240265d82b8cbcf35897f83b76cd59ee3ab3879050fd8c45/multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14", size = 44574, upload-time = "2025-06-30T15:51:02.449Z" }, - { url = "https://files.pythonhosted.org/packages/e6/05/6b759379f7e8e04ccc97cfb2a5dcc5cdbd44a97f072b2272dc51281e6a40/multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a", size = 225729, upload-time = "2025-06-30T15:51:03.794Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f5/8d5a15488edd9a91fa4aad97228d785df208ed6298580883aa3d9def1959/multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69", size = 242515, upload-time = "2025-06-30T15:51:05.002Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b5/a8f317d47d0ac5bb746d6d8325885c8967c2a8ce0bb57be5399e3642cccb/multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c", size = 222224, upload-time = "2025-06-30T15:51:06.148Z" }, - { url = "https://files.pythonhosted.org/packages/76/88/18b2a0d5e80515fa22716556061189c2853ecf2aa2133081ebbe85ebea38/multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751", size = 253124, upload-time = "2025-06-30T15:51:07.375Z" }, - { url = "https://files.pythonhosted.org/packages/62/bf/ebfcfd6b55a1b05ef16d0775ae34c0fe15e8dab570d69ca9941073b969e7/multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8", size = 251529, upload-time = "2025-06-30T15:51:08.691Z" }, - { url = "https://files.pythonhosted.org/packages/44/11/780615a98fd3775fc309d0234d563941af69ade2df0bb82c91dda6ddaea1/multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55", size = 241627, upload-time = "2025-06-30T15:51:10.605Z" }, - { url = "https://files.pythonhosted.org/packages/28/3d/35f33045e21034b388686213752cabc3a1b9d03e20969e6fa8f1b1d82db1/multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7", size = 239351, upload-time = "2025-06-30T15:51:12.18Z" }, - { url = "https://files.pythonhosted.org/packages/6e/cc/ff84c03b95b430015d2166d9aae775a3985d757b94f6635010d0038d9241/multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb", size = 233429, upload-time = "2025-06-30T15:51:13.533Z" }, - { url = "https://files.pythonhosted.org/packages/2e/f0/8cd49a0b37bdea673a4b793c2093f2f4ba8e7c9d6d7c9bd672fd6d38cd11/multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c", size = 243094, upload-time = "2025-06-30T15:51:14.815Z" }, - { url = "https://files.pythonhosted.org/packages/96/19/5d9a0cfdafe65d82b616a45ae950975820289069f885328e8185e64283c2/multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c", size = 248957, upload-time = "2025-06-30T15:51:16.076Z" }, - { url = "https://files.pythonhosted.org/packages/e6/dc/c90066151da87d1e489f147b9b4327927241e65f1876702fafec6729c014/multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61", size = 243590, upload-time = "2025-06-30T15:51:17.413Z" }, - { url = "https://files.pythonhosted.org/packages/ec/39/458afb0cccbb0ee9164365273be3e039efddcfcb94ef35924b7dbdb05db0/multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b", size = 237487, upload-time = "2025-06-30T15:51:19.039Z" }, - { url = "https://files.pythonhosted.org/packages/35/38/0016adac3990426610a081787011177e661875546b434f50a26319dc8372/multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318", size = 41390, upload-time = "2025-06-30T15:51:20.362Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/17897a8f3f2c5363d969b4c635aa40375fe1f09168dc09a7826780bfb2a4/multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485", size = 45954, upload-time = "2025-06-30T15:51:21.383Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5f/d4a717c1e457fe44072e33fa400d2b93eb0f2819c4d669381f925b7cba1f/multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5", size = 42981, upload-time = "2025-06-30T15:51:22.809Z" }, - { url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" }, - { url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" }, - { url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" }, - { url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" }, - { url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" }, - { url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" }, - { url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" }, - { url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" }, - { url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" }, - { url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" }, - { url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" }, - { url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, - { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, - { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, - { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, - { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, - { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, - { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, - { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, - { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, - { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" }, - { url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" }, - { url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" }, - { url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" }, - { url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" }, - { url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" }, - { url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" }, - { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" }, - { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" }, - { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" }, - { url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" }, - { url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" }, - { url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" }, - { url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" }, - { url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" }, - { url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" }, - { url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" }, - { url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" }, - { url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" }, - { url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" }, - { url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" }, - { url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" }, - { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, -] - -[[package]] -name = "mypy" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/31/e762baa3b73905c856d45ab77b4af850e8159dffffd86a52879539a08c6b/mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6", size = 10998313, upload-time = "2025-07-14T20:33:24.519Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c1/25b2f0d46fb7e0b5e2bee61ec3a47fe13eff9e3c2f2234f144858bbe6485/mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d", size = 10128922, upload-time = "2025-07-14T20:34:06.414Z" }, - { url = "https://files.pythonhosted.org/packages/02/78/6d646603a57aa8a2886df1b8881fe777ea60f28098790c1089230cd9c61d/mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b", size = 11913524, upload-time = "2025-07-14T20:33:19.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/19/dae6c55e87ee426fb76980f7e78484450cad1c01c55a1dc4e91c930bea01/mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a", size = 12650527, upload-time = "2025-07-14T20:32:44.095Z" }, - { url = "https://files.pythonhosted.org/packages/86/e1/f916845a235235a6c1e4d4d065a3930113767001d491b8b2e1b61ca56647/mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f", size = 12897284, upload-time = "2025-07-14T20:33:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/ae/dc/414760708a4ea1b096bd214d26a24e30ac5e917ef293bc33cdb6fe22d2da/mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937", size = 9506493, upload-time = "2025-07-14T20:34:01.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, - { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, - { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, - { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, - { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, - { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, - { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, - { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, - { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, - { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, - { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, - { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, - { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - -[[package]] -name = "nbclient" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "nbformat" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, -] - -[[package]] -name = "nbconvert" -version = "7.16.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "bleach", extra = ["css"] }, - { name = "defusedxml" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyterlab-pygments" }, - { name = "markupsafe" }, - { name = "mistune" }, - { name = "nbclient" }, - { name = "nbformat" }, - { name = "packaging" }, - { name = "pandocfilters" }, - { name = "pygments" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, -] - -[[package]] -name = "nbformat" -version = "5.10.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastjsonschema" }, - { name = "jsonschema" }, - { name = "jupyter-core" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, -] - -[[package]] -name = "nbsphinx" -version = "0.9.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "jinja2" }, - { name = "nbconvert" }, - { name = "nbformat" }, - { name = "sphinx" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/84/b1856b7651ac34e965aa567a158714c7f3bd42a1b1ce76bf423ffb99872c/nbsphinx-0.9.7.tar.gz", hash = "sha256:abd298a686d55fa894ef697c51d44f24e53aa312dadae38e82920f250a5456fe", size = 180479, upload-time = "2025-03-03T19:46:08.069Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/2d/8c8e635bcc6757573d311bb3c5445426382f280da32b8cd6d82d501ef4a4/nbsphinx-0.9.7-py3-none-any.whl", hash = "sha256:7292c3767fea29e405c60743eee5393682a83982ab202ff98f5eb2db02629da8", size = 31660, upload-time = "2025-03-03T19:46:06.581Z" }, -] - -[[package]] -name = "nest-asyncio" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, -] - -[[package]] -name = "notebook" -version = "7.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, - { name = "jupyterlab" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "tornado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/a9/882707b0aa639e6d7d3e7df4bfbe07479d832e9a8f02d8471002a4ea6d65/notebook-7.5.1.tar.gz", hash = "sha256:b2fb4cef4d47d08c33aecce1c6c6e84be05436fbd791f88fce8df9fbca088b75", size = 14058696, upload-time = "2025-12-16T07:38:59.223Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/86/ca516cb58ad2cb2064124d31cf0fd8b012fca64bebeb26da2d2ddf03fc79/notebook-7.5.1-py3-none-any.whl", hash = "sha256:f4e2451c19910c33b88709b84537e11f6368c1cdff1aa0c43db701aea535dd44", size = 14468080, upload-time = "2025-12-16T07:38:55.644Z" }, -] - -[[package]] -name = "notebook-shim" -version = "0.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, -] - -[[package]] -name = "numcodecs" -version = "0.13.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/56/8895a76abe4ec94ebd01eeb6d74f587bc4cddd46569670e1402852a5da13/numcodecs-0.13.1.tar.gz", hash = "sha256:a3cf37881df0898f3a9c0d4477df88133fe85185bffe57ba31bcc2fa207709bc", size = 5955215, upload-time = "2024-10-09T16:28:00.188Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/c0/6d72cde772bcec196b7188731d41282993b2958440f77fdf0db216f722da/numcodecs-0.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:96add4f783c5ce57cc7e650b6cac79dd101daf887c479a00a29bc1487ced180b", size = 1580012, upload-time = "2024-10-09T16:27:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/94/1d/f81fc1fa9210bbea97258242393a1f9feab4f6d8fb201f81f76003005e4b/numcodecs-0.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:237b7171609e868a20fd313748494444458ccd696062f67e198f7f8f52000c15", size = 1176919, upload-time = "2024-10-09T16:27:21.634Z" }, - { url = "https://files.pythonhosted.org/packages/16/e4/b9ec2f4dfc34ecf724bc1beb96a9f6fa9b91801645688ffadacd485089da/numcodecs-0.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96e42f73c31b8c24259c5fac6adba0c3ebf95536e37749dc6c62ade2989dca28", size = 8625842, upload-time = "2024-10-09T16:27:24.168Z" }, - { url = "https://files.pythonhosted.org/packages/fe/90/299952e1477954ec4f92813fa03e743945e3ff711bb4f6c9aace431cb3da/numcodecs-0.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:eda7d7823c9282e65234731fd6bd3986b1f9e035755f7fed248d7d366bb291ab", size = 828638, upload-time = "2024-10-09T16:27:27.063Z" }, - { url = "https://files.pythonhosted.org/packages/f0/78/34b8e869ef143e88d62e8231f4dbfcad85e5c41302a11fc5bd2228a13df5/numcodecs-0.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2eda97dd2f90add98df6d295f2c6ae846043396e3d51a739ca5db6c03b5eb666", size = 1580199, upload-time = "2024-10-09T16:27:29.336Z" }, - { url = "https://files.pythonhosted.org/packages/3b/cf/f70797d86bb585d258d1e6993dced30396f2044725b96ce8bcf87a02be9c/numcodecs-0.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a86f5367af9168e30f99727ff03b27d849c31ad4522060dde0bce2923b3a8bc", size = 1177203, upload-time = "2024-10-09T16:27:31.011Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b5/d14ad69b63fde041153dfd05d7181a49c0d4864de31a7a1093c8370da957/numcodecs-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233bc7f26abce24d57e44ea8ebeb5cd17084690b4e7409dd470fdb75528d615f", size = 8868743, upload-time = "2024-10-09T16:27:32.833Z" }, - { url = "https://files.pythonhosted.org/packages/13/d4/27a7b5af0b33f6d61e198faf177fbbf3cb83ff10d9d1a6857b7efc525ad5/numcodecs-0.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:796b3e6740107e4fa624cc636248a1580138b3f1c579160f260f76ff13a4261b", size = 829603, upload-time = "2024-10-09T16:27:35.415Z" }, - { url = "https://files.pythonhosted.org/packages/37/3a/bc09808425e7d3df41e5fc73fc7a802c429ba8c6b05e55f133654ade019d/numcodecs-0.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5195bea384a6428f8afcece793860b1ab0ae28143c853f0b2b20d55a8947c917", size = 1575806, upload-time = "2024-10-09T16:27:37.804Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/dc74d0bfdf9ec192332a089d199f1e543e747c556b5659118db7a437dcca/numcodecs-0.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3501a848adaddce98a71a262fee15cd3618312692aa419da77acd18af4a6a3f6", size = 1178233, upload-time = "2024-10-09T16:27:40.169Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ce/434e8e3970b8e92ae9ab6d9db16cb9bc7aa1cd02e17c11de6848224100a1/numcodecs-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2230484e6102e5fa3cc1a5dd37ca1f92dfbd183d91662074d6f7574e3e8f53", size = 8857827, upload-time = "2024-10-09T16:27:42.743Z" }, - { url = "https://files.pythonhosted.org/packages/83/e7/1d8b1b266a92f9013c755b1c146c5ad71a2bff147ecbc67f86546a2e4d6a/numcodecs-0.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5db4824ebd5389ea30e54bc8aeccb82d514d28b6b68da6c536b8fa4596f4bca", size = 826539, upload-time = "2024-10-09T16:27:44.808Z" }, - { url = "https://files.pythonhosted.org/packages/83/8b/06771dead2cc4a8ae1ea9907737cf1c8d37a323392fa28f938a586373468/numcodecs-0.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a60d75179fd6692e301ddfb3b266d51eb598606dcae7b9fc57f986e8d65cb43", size = 1571660, upload-time = "2024-10-09T16:27:47.125Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ea/d925bf85f92dfe4635356018da9fe4bfecb07b1c72f62b01c1bc47f936b1/numcodecs-0.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f593c7506b0ab248961a3b13cb148cc6e8355662ff124ac591822310bc55ecf", size = 1169925, upload-time = "2024-10-09T16:27:49.512Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d6/643a3839d571d8e439a2c77dc4b0b8cab18d96ac808e4a81dbe88e959ab6/numcodecs-0.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80d3071465f03522e776a31045ddf2cfee7f52df468b977ed3afdd7fe5869701", size = 8814257, upload-time = "2024-10-09T16:27:52.059Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c5/f3e56bc9b4e438a287fff738993d6d11abef368c0328a612ac2842ba9fca/numcodecs-0.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:90d3065ae74c9342048ae0046006f99dcb1388b7288da5a19b3bddf9c30c3176", size = 821887, upload-time = "2024-10-09T16:27:55.039Z" }, -] - -[[package]] -name = "numcodecs" -version = "0.16.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/1d/837d946aab385abe1e472ec08a6816d84b00f4ceeae5445eb8f25c5c6ca9/numcodecs-0.16.2.tar.gz", hash = "sha256:9922dae0c3b01b5bed3b4bae239f4787e891daa3262c27971298669d029d10e9", size = 6271668, upload-time = "2025-08-13T16:09:26.125Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/19/38b3fbb2335e31fdd253e1de0531b32e8245b86a7b9d46c474fa6d2cb5c5/numcodecs-0.16.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:4c1f1357896cfc5451157fd90a2858aa0f6a66409f91f3016d257b76c4c244c1", size = 1629357, upload-time = "2025-08-13T16:09:00.425Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f8/ba257b7627477f5650aa8274e7a6f9a72c4d51c1e7ac968b14b65d872c76/numcodecs-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d88aa4f82bb8fe3fa2e9c19fd44b6c268acec6fc7bcff59c92e169830d09531a", size = 1158894, upload-time = "2025-08-13T16:09:02.285Z" }, - { url = "https://files.pythonhosted.org/packages/e3/31/0c90266a9ba048223656d552c5b40ea22d788be77797e8a0db9d698a3847/numcodecs-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:276f657ec4ffef9e2cc344b732b8906fe8612c3b9484504830ccdc7a2dd0715a", size = 8285212, upload-time = "2025-08-13T16:09:03.716Z" }, - { url = "https://files.pythonhosted.org/packages/01/24/7889b678c0b7e3f605e1ec2fe1ddee0f48920098fecb0cadd5bd66483044/numcodecs-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b7d8174e14c66191e0e1a4a44dd264bd776fb96b1ce3adc0216154f34b755c", size = 8811508, upload-time = "2025-08-13T16:09:05.68Z" }, - { url = "https://files.pythonhosted.org/packages/87/26/23b474b188a8e005bbd5e516e3415ec9f2b59fbf1f36636e5cfc14f59c54/numcodecs-0.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:559163a7d97ec8aef1654e47c6e97f88dcc4c38a975338bf1bfb214d3c8d70f9", size = 804099, upload-time = "2025-08-13T16:09:07.589Z" }, - { url = "https://files.pythonhosted.org/packages/03/c3/5470273d6d5c986521140ccec6476664ea4e03c0cfc51b370fb03368bb41/numcodecs-0.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30f04c2b7bb802133866e7fb554d47943864f977dfe8a95c814eb801c797df3c", size = 1668488, upload-time = "2025-08-13T16:09:08.942Z" }, - { url = "https://files.pythonhosted.org/packages/db/bf/cc1aaea87371097d6b5236ec44f8eb96387b52204b4e671fac716e5de325/numcodecs-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0b2776cf47b7702ba0ccc0b6afaad28afbf8d5bb7b9a5274b5f08ecc651b2", size = 1155083, upload-time = "2025-08-13T16:09:10.641Z" }, - { url = "https://files.pythonhosted.org/packages/6b/c1/ba5ab0cf4c4d737635d20d8b72a61c26f8f99c0529606dfbfa3e5d3a4221/numcodecs-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e94f0e90aaf4d01b2e26476d3b081c2cf8c17036af45e04e735de9c0cf64f", size = 8260568, upload-time = "2025-08-13T16:09:12.063Z" }, - { url = "https://files.pythonhosted.org/packages/3b/64/7177bf632520705893683fa4ca202ed540450bf971c0453ad1351baa2007/numcodecs-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b565b16366749011e290343617571db861b2b2e58b038697afde6d02f537c91", size = 8792262, upload-time = "2025-08-13T16:09:14.058Z" }, - { url = "https://files.pythonhosted.org/packages/10/90/df01799f4c1bb8618b842582d10d362829e980c4d5eb9701c1aeadf5c4e3/numcodecs-0.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:e4cfdde4e99bf47580f4eb3a876630c73ba14e4a1380fec5959ac727e22ce0d2", size = 803444, upload-time = "2025-08-13T16:09:16.09Z" }, - { url = "https://files.pythonhosted.org/packages/b6/e3/f61c422259a4b6c8c2496d284f85ed17f8686b3a53feb797d3bd66ef499c/numcodecs-0.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f965ccb2f6d215ffd2e3239ec02e33139d7ce311ff49704d340704b81dda653", size = 1664476, upload-time = "2025-08-13T16:09:17.327Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a7/fa4d66b86e277643d135af263efc0dd1f98cf1228d3b4554b843c0c1a09b/numcodecs-0.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b4bc110b73d59de7f46310c680f075f9007ead915174c895368274c646c9ea74", size = 1148718, upload-time = "2025-08-13T16:09:19.075Z" }, - { url = "https://files.pythonhosted.org/packages/17/43/9656a6b0ed7250ca3a5c126a6077a29398c3dca9176224dba4634847a4a4/numcodecs-0.16.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51615cf2811343a8a3fa42953cb4120ac7187875a161260444f53ada5710063e", size = 8205330, upload-time = "2025-08-13T16:09:20.904Z" }, - { url = "https://files.pythonhosted.org/packages/55/7f/0ab8db32ef9b51c60f7b759c2b155e1edcb08febb508c22a9d04b19ec735/numcodecs-0.16.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a18ced1ecbdd0e9ee716820dbb3a094c896eed8005273bbcab9980bdac270ae", size = 8750769, upload-time = "2025-08-13T16:09:22.516Z" }, - { url = "https://files.pythonhosted.org/packages/f0/0c/25f96c7969bdbfcc1427dc82eba92f2ef4df84c63369c95ab99af6404c23/numcodecs-0.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:f640ed8406e1eb5806787a3e5be223d455b75c99eb2088a290947ed6dbd77e8e", size = 800281, upload-time = "2025-08-13T16:09:24.691Z" }, -] - -[package.optional-dependencies] -crc32c = [ - { name = "crc32c", marker = "python_full_version >= '3.11'" }, -] - -[[package]] -name = "numpy" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, - { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, - { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, - { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, - { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, - { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, -] - -[[package]] -name = "numpy" -version = "2.3.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" }, - { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" }, - { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" }, - { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" }, - { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" }, - { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" }, - { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" }, - { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, - { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, - { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, - { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, - { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, - { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, - { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, - { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, - { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, - { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, - { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, - { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, - { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, - { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, - { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, - { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, - { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, - { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, - { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, - { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, - { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, - { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, - { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, - { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, - { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, - { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, - { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, - { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, - { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, - { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" }, - { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" }, - { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" }, - { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, -] - -[[package]] -name = "odc-geo" -version = "0.4.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "affine" }, - { name = "cachetools" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pyproj", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "shapely" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/f6/f22d9728ed1652f092bdb8e0f04cf68f7212d273ad118225a252fc8cbfb4/odc_geo-0.4.10.tar.gz", hash = "sha256:5670a797c4243e9379b20905ea37ff23ad287e7fa37c672dc53546923daf6d52", size = 192809, upload-time = "2025-03-03T04:12:24.077Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/e2/311fb383d9534eef7bfbe858fad931b6e3dbe85843c50592f50063c3bc83/odc_geo-0.4.10-py3-none-any.whl", hash = "sha256:ab5f26f56883a11286a9583c04d30190e35a2702e8aa72fa3b4b438bea851b19", size = 155067, upload-time = "2025-03-03T04:12:22.114Z" }, -] - -[[package]] -name = "odc-loader" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dask", extra = ["array"] }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "odc-geo" }, - { name = "rasterio" }, - { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "xarray", version = "2025.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/c6/fd9dbf40aaf94bf3c9712a9ffdd0a791599764c3e4b80b5917aaf6280ec8/odc_loader-0.5.1.tar.gz", hash = "sha256:bf3adf53b10248bbab3bb1fcb043995c12e204dbde4f9a751cd3331b1c6a1212", size = 41616, upload-time = "2025-04-03T00:14:03.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/48/d45d414b8228325051b0a09f68322ef26717eb9b6517579ae395adf2fbfc/odc_loader-0.5.1-py3-none-any.whl", hash = "sha256:fbe505e2f0e8ac4db2542935bdf5f706e6147817d818073a8a98d3226bd6470f", size = 50717, upload-time = "2025-04-03T00:14:01.687Z" }, -] - -[[package]] -name = "odc-stac" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "affine" }, - { name = "dask", extra = ["array"] }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "odc-geo" }, - { name = "odc-loader" }, - { name = "pandas" }, - { name = "pystac" }, - { name = "rasterio" }, - { name = "toolz" }, - { name = "typing-extensions" }, - { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "xarray", version = "2025.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7f/2e/b5f29cd12f8a5b23f499d7da716cb6d0c55f9542d805111a1e6d92347a31/odc_stac-0.4.0.tar.gz", hash = "sha256:3535767940840e8c3ec2e3dda15ac36e30de0e51624dcc82e1052e19a7dab091", size = 114343, upload-time = "2025-05-01T05:20:51.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/21/05c243c9b563519ae5b7442a73424ad0537c7d312cf8969bdf9d0fa31c00/odc_stac-0.4.0-py3-none-any.whl", hash = "sha256:a76b9d96d7aea53cabcd7ba99b0cf467a6eab6d2ad6a52802af36b5c407c5952", size = 44259, upload-time = "2025-05-01T05:20:49.632Z" }, -] - -[[package]] -name = "orjson" -version = "3.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/3b/fd9ff8ff64ae3900f11554d5cfc835fb73e501e043c420ad32ec574fe27f/orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96", size = 5393373, upload-time = "2025-07-25T14:33:52.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/8b/7dd88f416e2e5834fd9809d871f471aae7d12dfd83d4786166fa5a926601/orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532", size = 241312, upload-time = "2025-07-25T14:31:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/f3/5d/5bfc371bd010ffbec90e64338aa59abcb13ed94191112199048653ee2f34/orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65", size = 132791, upload-time = "2025-07-25T14:31:55.547Z" }, - { url = "https://files.pythonhosted.org/packages/48/e2/c07854a6bad71e4249345efadb686c0aff250073bdab8ba9be7626af6516/orjson-3.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5caf7f13f2e1b4e137060aed892d4541d07dabc3f29e6d891e2383c7ed483440", size = 128690, upload-time = "2025-07-25T14:31:56.708Z" }, - { url = "https://files.pythonhosted.org/packages/48/e4/2e075348e7772aa1404d51d8df25ff4d6ee3daf682732cb21308e3b59c32/orjson-3.11.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f716bcc166524eddfcf9f13f8209ac19a7f27b05cf591e883419079d98c8c99d", size = 130646, upload-time = "2025-07-25T14:31:58.165Z" }, - { url = "https://files.pythonhosted.org/packages/97/09/50daacd3ac7ae564186924c8d1121940f2c78c64d6804dbe81dd735ab087/orjson-3.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507d6012fab05465d8bf21f5d7f4635ba4b6d60132874e349beff12fb51af7fe", size = 132620, upload-time = "2025-07-25T14:31:59.226Z" }, - { url = "https://files.pythonhosted.org/packages/da/21/5f22093fa90e6d6fcf8111942b530a4ad19ee1cc0b06ddad4a63b16ab852/orjson-3.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1545083b0931f754c80fd2422a73d83bea7a6d1b6de104a5f2c8dd3d64c291e", size = 135121, upload-time = "2025-07-25T14:32:00.653Z" }, - { url = "https://files.pythonhosted.org/packages/48/90/77ad4bfa6bd400a3d241695e3e39975e32fe027aea5cb0b171bd2080c427/orjson-3.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e217ce3bad76351e1eb29ebe5ca630326f45cd2141f62620107a229909501a3", size = 131131, upload-time = "2025-07-25T14:32:01.821Z" }, - { url = "https://files.pythonhosted.org/packages/5a/64/d383675229f7ffd971b6ec6cdd3016b00877bb6b2d5fc1fd099c2ec2ad57/orjson-3.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ef26e009304bda4df42e4afe518994cde6f89b4b04c0ff24021064f83f4fbb", size = 131025, upload-time = "2025-07-25T14:32:02.879Z" }, - { url = "https://files.pythonhosted.org/packages/d4/82/e4017d8d98597f6056afaf75021ff390154d1e2722c66ba45a4d50f82606/orjson-3.11.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ba49683b87bea3ae1489a88e766e767d4f423a669a61270b6d6a7ead1c33bd65", size = 404464, upload-time = "2025-07-25T14:32:04.384Z" }, - { url = "https://files.pythonhosted.org/packages/77/7e/45c7f813c30d386c0168a32ce703494262458af6b222a3eeac1c0bb88822/orjson-3.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5072488fcc5cbcda2ece966d248e43ea1d222e19dd4c56d3f82747777f24d864", size = 146416, upload-time = "2025-07-25T14:32:05.57Z" }, - { url = "https://files.pythonhosted.org/packages/41/71/6ccb4d7875ec3349409960769a28349f477856f05de9fd961454c2b99230/orjson-3.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f58ae2bcd119226fe4aa934b5880fe57b8e97b69e51d5d91c88a89477a307016", size = 135497, upload-time = "2025-07-25T14:32:06.704Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ce/df8dac7da075962fdbfca55d53e3601aa910c9f23606033bf0f084835720/orjson-3.11.1-cp310-cp310-win32.whl", hash = "sha256:6723be919c07906781b9c63cc52dc7d2fb101336c99dd7e85d3531d73fb493f7", size = 136807, upload-time = "2025-07-25T14:32:08.303Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a0/f6c2be24709d1742d878b4530fa0c3f4a5e190d51397b680abbf44d11dbf/orjson-3.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:5fd44d69ddfdfb4e8d0d83f09d27a4db34930fba153fbf79f8d4ae8b47914e04", size = 131561, upload-time = "2025-07-25T14:32:09.444Z" }, - { url = "https://files.pythonhosted.org/packages/a5/92/7ab270b5b3df8d5b0d3e572ddf2f03c9f6a79726338badf1ec8594e1469d/orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef", size = 240918, upload-time = "2025-07-25T14:32:11.021Z" }, - { url = "https://files.pythonhosted.org/packages/80/41/df44684cfbd2e2e03bf9b09fdb14b7abcfff267998790b6acfb69ad435f0/orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd", size = 129386, upload-time = "2025-07-25T14:32:12.361Z" }, - { url = "https://files.pythonhosted.org/packages/c1/08/958f56edd18ba1827ad0c74b2b41a7ae0864718adee8ccb5d1a5528f8761/orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c", size = 132508, upload-time = "2025-07-25T14:32:13.917Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/5e56e189dacbf51e53ba8150c20e61ee746f6d57b697f5c52315ffc88a83/orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a", size = 128501, upload-time = "2025-07-25T14:32:15.13Z" }, - { url = "https://files.pythonhosted.org/packages/fe/de/f6c301a514f5934405fd4b8f3d3efc758c911d06c3de3f4be1e30d675fa4/orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09", size = 130465, upload-time = "2025-07-25T14:32:17.355Z" }, - { url = "https://files.pythonhosted.org/packages/47/08/f7dbaab87d6f05eebff2d7b8e6a8ed5f13b2fe3e3ae49472b527d03dbd7a/orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27", size = 132416, upload-time = "2025-07-25T14:32:18.933Z" }, - { url = "https://files.pythonhosted.org/packages/43/3f/dd5a185273b7ba6aa238cfc67bf9edaa1885ae51ce942bc1a71d0f99f574/orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495", size = 134924, upload-time = "2025-07-25T14:32:20.134Z" }, - { url = "https://files.pythonhosted.org/packages/db/ef/729d23510eaa81f0ce9d938d99d72dcf5e4ed3609d9d0bcf9c8a282cc41a/orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c", size = 130938, upload-time = "2025-07-25T14:32:21.769Z" }, - { url = "https://files.pythonhosted.org/packages/82/96/120feb6807f9e1f4c68fc842a0f227db8575eafb1a41b2537567b91c19d8/orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d", size = 130811, upload-time = "2025-07-25T14:32:22.931Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/4695e946a453fa22ff945da4b1ed0691b3f4ec86b828d398288db4a0ff79/orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84", size = 404272, upload-time = "2025-07-25T14:32:25.238Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7b/1c953e2c9e55af126c6cb678a30796deb46d7713abdeb706b8765929464c/orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39", size = 146196, upload-time = "2025-07-25T14:32:26.909Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c2/bef5d3bc83f2e178592ff317e2cf7bd38ebc16b641f076ea49f27aadd1d3/orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55", size = 135336, upload-time = "2025-07-25T14:32:28.22Z" }, - { url = "https://files.pythonhosted.org/packages/92/95/bc6006881ebdb4608ed900a763c3e3c6be0d24c3aadd62beb774f9464ec6/orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17", size = 136665, upload-time = "2025-07-25T14:32:29.976Z" }, - { url = "https://files.pythonhosted.org/packages/59/c3/1f2b9cc0c60ea2473d386fed2df2b25ece50aeb73c798d4669aadff3061e/orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313", size = 131388, upload-time = "2025-07-25T14:32:31.595Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e5/40c97e5a6b85944022fe54b463470045b8651b7bb2f1e16a95c42812bf97/orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af", size = 126786, upload-time = "2025-07-25T14:32:32.787Z" }, - { url = "https://files.pythonhosted.org/packages/98/77/e55513826b712807caadb2b733eee192c1df105c6bbf0d965c253b72f124/orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910", size = 240955, upload-time = "2025-07-25T14:32:34.056Z" }, - { url = "https://files.pythonhosted.org/packages/c9/88/a78132dddcc9c3b80a9fa050b3516bb2c996a9d78ca6fb47c8da2a80a696/orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d", size = 129294, upload-time = "2025-07-25T14:32:35.323Z" }, - { url = "https://files.pythonhosted.org/packages/09/02/6591e0dcb2af6bceea96cb1b5f4b48c1445492a3ef2891ac4aa306bb6f73/orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5", size = 132310, upload-time = "2025-07-25T14:32:36.53Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/c1cfbc617bcfa4835db275d5e0fe9bbdbe561a4b53d3b2de16540ec29c50/orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b", size = 128529, upload-time = "2025-07-25T14:32:37.817Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bd/91a156c5df3aaf1d68b2ab5be06f1969955a8d3e328d7794f4338ac1d017/orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf", size = 130925, upload-time = "2025-07-25T14:32:39.03Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4c/a65cc24e9a5f87c9833a50161ab97b5edbec98bec99dfbba13827549debc/orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56", size = 132432, upload-time = "2025-07-25T14:32:40.619Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4d/3fc3e5d7115f4f7d01b481e29e5a79bcbcc45711a2723242787455424f40/orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289", size = 135069, upload-time = "2025-07-25T14:32:41.84Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c6/7585aa8522af896060dc0cd7c336ba6c574ae854416811ee6642c505cc95/orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81", size = 131045, upload-time = "2025-07-25T14:32:43.085Z" }, - { url = "https://files.pythonhosted.org/packages/6a/4e/b8a0a943793d2708ebc39e743c943251e08ee0f3279c880aefd8e9cb0c70/orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968", size = 130597, upload-time = "2025-07-25T14:32:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/72/2b/7d30e2aed2f585d5d385fb45c71d9b16ba09be58c04e8767ae6edc6c9282/orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a", size = 404207, upload-time = "2025-07-25T14:32:45.612Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7e/772369ec66fcbce79477f0891918309594cd00e39b67a68d4c445d2ab754/orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d", size = 146628, upload-time = "2025-07-25T14:32:46.981Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c8/62bdb59229d7e393ae309cef41e32cc1f0b567b21dfd0742da70efb8b40c/orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4", size = 135449, upload-time = "2025-07-25T14:32:48.727Z" }, - { url = "https://files.pythonhosted.org/packages/02/47/1c99aa60e19f781424eabeaacd9e999eafe5b59c81ead4273b773f0f3af1/orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf", size = 136653, upload-time = "2025-07-25T14:32:50.622Z" }, - { url = "https://files.pythonhosted.org/packages/31/9a/132999929a2892ab07e916669accecc83e5bff17e11a1186b4c6f23231f0/orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae", size = 131426, upload-time = "2025-07-25T14:32:51.883Z" }, - { url = "https://files.pythonhosted.org/packages/9c/77/d984ee5a1ca341090902e080b187721ba5d1573a8d9759e0c540975acfb2/orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d", size = 126635, upload-time = "2025-07-25T14:32:53.2Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e9/880ef869e6f66279ce3a381a32afa0f34e29a94250146911eee029e56efc/orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e", size = 240835, upload-time = "2025-07-25T14:32:54.507Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1f/52039ef3d03eeea21763b46bc99ebe11d9de8510c72b7b5569433084a17e/orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064", size = 129226, upload-time = "2025-07-25T14:32:55.908Z" }, - { url = "https://files.pythonhosted.org/packages/ee/da/59fdffc9465a760be2cd3764ef9cd5535eec8f095419f972fddb123b6d0e/orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6", size = 132261, upload-time = "2025-07-25T14:32:57.538Z" }, - { url = "https://files.pythonhosted.org/packages/bb/5c/8610911c7e969db7cf928c8baac4b2f1e68d314bc3057acf5ca64f758435/orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894", size = 128614, upload-time = "2025-07-25T14:32:58.808Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a1/a1db9d4310d014c90f3b7e9b72c6fb162cba82c5f46d0b345669eaebdd3a/orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912", size = 130968, upload-time = "2025-07-25T14:33:00.038Z" }, - { url = "https://files.pythonhosted.org/packages/56/ff/11acd1fd7c38ea7a1b5d6bf582ae3da05931bee64620995eb08fd63c77fe/orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2", size = 132439, upload-time = "2025-07-25T14:33:01.354Z" }, - { url = "https://files.pythonhosted.org/packages/70/f9/bb564dd9450bf8725e034a8ad7f4ae9d4710a34caf63b85ce1c0c6d40af0/orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8", size = 135299, upload-time = "2025-07-25T14:33:03.079Z" }, - { url = "https://files.pythonhosted.org/packages/94/bb/c8eafe6051405e241dda3691db4d9132d3c3462d1d10a17f50837dd130b4/orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4", size = 131004, upload-time = "2025-07-25T14:33:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/a2/40/bed8d7dcf1bd2df8813bf010a25f645863a2f75e8e0ebdb2b55784cf1a62/orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24", size = 130583, upload-time = "2025-07-25T14:33:05.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/e7/cfa2eb803ad52d74fbb5424a429b5be164e51d23f1d853e5e037173a5c48/orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014", size = 404218, upload-time = "2025-07-25T14:33:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/d5/21/bc703af5bc6e9c7e18dcf4404dcc4ec305ab9bb6c82d3aee5952c0c56abf/orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058", size = 146605, upload-time = "2025-07-25T14:33:08.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d26a0150534c4965a06f556aa68bf3c3b82999d5d7b0facd3af7b390c4af/orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f", size = 135434, upload-time = "2025-07-25T14:33:09.967Z" }, - { url = "https://files.pythonhosted.org/packages/89/b6/1cb28365f08cbcffc464f8512320c6eb6db6a653f03d66de47ea3c19385f/orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092", size = 136596, upload-time = "2025-07-25T14:33:11.333Z" }, - { url = "https://files.pythonhosted.org/packages/f9/35/7870d0d3ed843652676d84d8a6038791113eacc85237b673b925802826b8/orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77", size = 131319, upload-time = "2025-07-25T14:33:12.614Z" }, - { url = "https://files.pythonhosted.org/packages/b7/3e/5bcd50fd865eb664d4edfdaaaff51e333593ceb5695a22c0d0a0d2b187ba/orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4", size = 126613, upload-time = "2025-07-25T14:33:13.927Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/0a5cd31ed100b4e569e143cb0cddefc21f0bcb8ce284f44bca0bb0e10f3d/orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b", size = 240819, upload-time = "2025-07-25T14:33:15.223Z" }, - { url = "https://files.pythonhosted.org/packages/b9/95/7eb2c76c92192ceca16bc81845ff100bbb93f568b4b94d914b6a4da47d61/orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e", size = 129218, upload-time = "2025-07-25T14:33:16.637Z" }, - { url = "https://files.pythonhosted.org/packages/da/84/e6b67f301b18adbbc346882f456bea44daebbd032ba725dbd7b741e3a7f1/orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727", size = 132238, upload-time = "2025-07-25T14:33:17.934Z" }, - { url = "https://files.pythonhosted.org/packages/84/78/a45a86e29d9b2f391f9d00b22da51bc4b46b86b788fd42df2c5fcf3e8005/orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c", size = 130998, upload-time = "2025-07-25T14:33:19.282Z" }, - { url = "https://files.pythonhosted.org/packages/ea/8f/6eb3ee6760d93b2ce996a8529164ee1f5bafbdf64b74c7314b68db622b32/orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037", size = 130559, upload-time = "2025-07-25T14:33:20.589Z" }, - { url = "https://files.pythonhosted.org/packages/1b/78/9572ae94bdba6813917c9387e7834224c011ea6b4530ade07d718fd31598/orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa", size = 404231, upload-time = "2025-07-25T14:33:22.019Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a3/68381ad0757e084927c5ee6cfdeab1c6c89405949ee493db557e60871c4c/orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de", size = 146658, upload-time = "2025-07-25T14:33:23.675Z" }, - { url = "https://files.pythonhosted.org/packages/00/db/fac56acf77aab778296c3f541a3eec643266f28ecd71d6c0cba251e47655/orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45", size = 135443, upload-time = "2025-07-25T14:33:25.04Z" }, - { url = "https://files.pythonhosted.org/packages/76/b1/326fa4b87426197ead61c1eec2eeb3babc9eb33b480ac1f93894e40c8c08/orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1", size = 136643, upload-time = "2025-07-25T14:33:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8e/2987ae2109f3bfd39680f8a187d1bc09ad7f8fb019dcdc719b08c7242ade/orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8", size = 131324, upload-time = "2025-07-25T14:33:27.896Z" }, - { url = "https://files.pythonhosted.org/packages/21/5f/253e08e6974752b124fbf3a4de3ad53baa766b0cb4a333d47706d307e396/orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1", size = 126605, upload-time = "2025-07-25T14:33:29.244Z" }, -] - -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - -[[package]] -name = "pandas" -version = "2.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/8e/0e90233ac205ad182bd6b422532695d2b9414944a280488105d598c70023/pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb", size = 4488684, upload-time = "2025-08-21T10:28:29.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/16/a8eeb70aad84ccbf14076793f90e0031eded63c1899aeae9fdfbf37881f4/pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35", size = 11539648, upload-time = "2025-08-21T10:26:36.236Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/c5bdaea13bf3708554d93e948b7ea74121ce6e0d59537ca4c4f77731072b/pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b", size = 10786923, upload-time = "2025-08-21T10:26:40.518Z" }, - { url = "https://files.pythonhosted.org/packages/bb/10/811fa01476d29ffed692e735825516ad0e56d925961819e6126b4ba32147/pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424", size = 11726241, upload-time = "2025-08-21T10:26:43.175Z" }, - { url = "https://files.pythonhosted.org/packages/c4/6a/40b043b06e08df1ea1b6d20f0e0c2f2c4ec8c4f07d1c92948273d943a50b/pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf", size = 12349533, upload-time = "2025-08-21T10:26:46.611Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ea/2e081a2302e41a9bca7056659fdd2b85ef94923723e41665b42d65afd347/pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba", size = 13202407, upload-time = "2025-08-21T10:26:49.068Z" }, - { url = "https://files.pythonhosted.org/packages/f4/12/7ff9f6a79e2ee8869dcf70741ef998b97ea20050fe25f83dc759764c1e32/pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6", size = 13837212, upload-time = "2025-08-21T10:26:51.832Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/5ab92fcd76455a632b3db34a746e1074d432c0cdbbd28d7cd1daba46a75d/pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a", size = 11338099, upload-time = "2025-08-21T10:26:54.382Z" }, - { url = "https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743", size = 11563308, upload-time = "2025-08-21T10:26:56.656Z" }, - { url = "https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4", size = 10820319, upload-time = "2025-08-21T10:26:59.162Z" }, - { url = "https://files.pythonhosted.org/packages/95/3b/1e9b69632898b048e223834cd9702052bcf06b15e1ae716eda3196fb972e/pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2", size = 11790097, upload-time = "2025-08-21T10:27:02.204Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e", size = 12397958, upload-time = "2025-08-21T10:27:05.409Z" }, - { url = "https://files.pythonhosted.org/packages/23/82/e6b85f0d92e9afb0e7f705a51d1399b79c7380c19687bfbf3d2837743249/pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea", size = 13225600, upload-time = "2025-08-21T10:27:07.791Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f1/f682015893d9ed51611948bd83683670842286a8edd4f68c2c1c3b231eef/pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372", size = 13879433, upload-time = "2025-08-21T10:27:10.347Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f", size = 11336557, upload-time = "2025-08-21T10:27:12.983Z" }, - { url = "https://files.pythonhosted.org/packages/ec/db/614c20fb7a85a14828edd23f1c02db58a30abf3ce76f38806155d160313c/pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9", size = 11587652, upload-time = "2025-08-21T10:27:15.888Z" }, - { url = "https://files.pythonhosted.org/packages/99/b0/756e52f6582cade5e746f19bad0517ff27ba9c73404607c0306585c201b3/pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b", size = 10717686, upload-time = "2025-08-21T10:27:18.486Z" }, - { url = "https://files.pythonhosted.org/packages/37/4c/dd5ccc1e357abfeee8353123282de17997f90ff67855f86154e5a13b81e5/pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175", size = 11278722, upload-time = "2025-08-21T10:27:21.149Z" }, - { url = "https://files.pythonhosted.org/packages/d3/a4/f7edcfa47e0a88cda0be8b068a5bae710bf264f867edfdf7b71584ace362/pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9", size = 11987803, upload-time = "2025-08-21T10:27:23.767Z" }, - { url = "https://files.pythonhosted.org/packages/f6/61/1bce4129f93ab66f1c68b7ed1c12bac6a70b1b56c5dab359c6bbcd480b52/pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4", size = 12766345, upload-time = "2025-08-21T10:27:26.6Z" }, - { url = "https://files.pythonhosted.org/packages/8e/46/80d53de70fee835531da3a1dae827a1e76e77a43ad22a8cd0f8142b61587/pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811", size = 13439314, upload-time = "2025-08-21T10:27:29.213Z" }, - { url = "https://files.pythonhosted.org/packages/28/30/8114832daff7489f179971dbc1d854109b7f4365a546e3ea75b6516cea95/pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae", size = 10983326, upload-time = "2025-08-21T10:27:31.901Z" }, - { url = "https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e", size = 11531061, upload-time = "2025-08-21T10:27:34.647Z" }, - { url = "https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9", size = 10668666, upload-time = "2025-08-21T10:27:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/50/e2/f775ba76ecfb3424d7f5862620841cf0edb592e9abd2d2a5387d305fe7a8/pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a", size = 11332835, upload-time = "2025-08-21T10:27:40.188Z" }, - { url = "https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b", size = 12057211, upload-time = "2025-08-21T10:27:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/0b/9d/2df913f14b2deb9c748975fdb2491da1a78773debb25abbc7cbc67c6b549/pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6", size = 12749277, upload-time = "2025-08-21T10:27:45.474Z" }, - { url = "https://files.pythonhosted.org/packages/87/af/da1a2417026bd14d98c236dba88e39837182459d29dcfcea510b2ac9e8a1/pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a", size = 13415256, upload-time = "2025-08-21T10:27:49.885Z" }, - { url = "https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b", size = 10982579, upload-time = "2025-08-21T10:28:08.435Z" }, - { url = "https://files.pythonhosted.org/packages/f3/98/8df69c4097a6719e357dc249bf437b8efbde808038268e584421696cbddf/pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57", size = 12028163, upload-time = "2025-08-21T10:27:52.232Z" }, - { url = "https://files.pythonhosted.org/packages/0e/23/f95cbcbea319f349e10ff90db488b905c6883f03cbabd34f6b03cbc3c044/pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2", size = 11391860, upload-time = "2025-08-21T10:27:54.673Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1b/6a984e98c4abee22058aa75bfb8eb90dce58cf8d7296f8bc56c14bc330b0/pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9", size = 11309830, upload-time = "2025-08-21T10:27:56.957Z" }, - { url = "https://files.pythonhosted.org/packages/15/d5/f0486090eb18dd8710bf60afeaf638ba6817047c0c8ae5c6a25598665609/pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2", size = 11883216, upload-time = "2025-08-21T10:27:59.302Z" }, - { url = "https://files.pythonhosted.org/packages/10/86/692050c119696da19e20245bbd650d8dfca6ceb577da027c3a73c62a047e/pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012", size = 12699743, upload-time = "2025-08-21T10:28:02.447Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" }, -] - -[[package]] -name = "pandocfilters" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, -] - -[[package]] -name = "parso" -version = "0.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, -] - -[[package]] -name = "partd" -version = "1.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "locket" }, - { name = "toolz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - -[[package]] -name = "pbr" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/d2/510cc0d218e753ba62a1bc1434651db3cd797a9716a0a66cc714cb4f0935/pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b", size = 125702, upload-time = "2025-02-04T14:28:06.514Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/ac/684d71315abc7b1214d59304e23a982472967f6bf4bde5a98f1503f648dc/pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76", size = 108997, upload-time = "2025-02-04T14:28:03.168Z" }, -] - -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, -] - -[[package]] -name = "planetary-computer" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "pystac" }, - { name = "pystac-client" }, - { name = "python-dotenv" }, - { name = "pytz" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/d3/ccfb0e823e9beb72b7e6b749f0985c4db1530d4e4a145b1b5d12f5de417e/planetary-computer-1.0.0.tar.gz", hash = "sha256:5958a8e1d8ba1aafc7ac45878df2d7d03405806ae31ed2e675333faebca960cc", size = 17076, upload-time = "2023-07-05T13:06:58.679Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/7a/3965a2b58d172e106c1064af19707d29f86a0142c28ff40185490a0a8cfe/planetary_computer-1.0.0-py3-none-any.whl", hash = "sha256:7af5839f9346c1d23d53fff4e80e955db18a2d81992877816e22dcbc2f90c40d", size = 14163, upload-time = "2023-07-05T13:06:57.06Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pre-commit" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, -] - -[[package]] -name = "prometheus-client" -version = "0.22.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/cf/40dde0a2be27cc1eb41e333d1a674a74ce8b8b0457269cc640fd42b07cf7/prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28", size = 69746, upload-time = "2025-06-02T14:29:01.152Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/ae/ec06af4fe3ee72d16973474f122541746196aaa16cea6f66d18b963c6177/prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094", size = 58694, upload-time = "2025-06-02T14:29:00.068Z" }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.51" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, -] - -[[package]] -name = "propcache" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, - { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, - { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, - { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, - { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, - { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, - { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, - { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, - { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, - { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, - { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, - { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, - { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, - { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, - { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, - { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, - { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, - { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, - { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, - { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, -] - -[[package]] -name = "psutil" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, -] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, -] - -[[package]] -name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, -] - -[[package]] -name = "pydantic" -version = "2.11.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.33.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, -] - -[[package]] -name = "pydata-sphinx-theme" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accessible-pygments" }, - { name = "babel" }, - { name = "beautifulsoup4" }, - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pympler" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dd/37/c384631908029676d8e7213dd956bb686af303a80db7afbc9be36bc49495/pympler-1.1.tar.gz", hash = "sha256:1eaa867cb8992c218430f1708fdaccda53df064144d1c5656b1e6f1ee6000424", size = 179954, upload-time = "2024-06-28T19:56:06.563Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/4f/a6a2e2b202d7fd97eadfe90979845b8706676b41cbd3b42ba75adf329d1f/Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506", size = 165766, upload-time = "2024-06-28T19:56:05.087Z" }, -] - -[[package]] -name = "pyparsing" -version = "3.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, -] - -[[package]] -name = "pyproj" -version = "3.7.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "certifi", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/10/a8480ea27ea4bbe896c168808854d00f2a9b49f95c0319ddcbba693c8a90/pyproj-3.7.1.tar.gz", hash = "sha256:60d72facd7b6b79853f19744779abcd3f804c4e0d4fa8815469db20c9f640a47", size = 226339, upload-time = "2025-02-16T04:28:46.621Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/a3/c4cd4bba5b336075f145fe784fcaf4ef56ffbc979833303303e7a659dda2/pyproj-3.7.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:bf09dbeb333c34e9c546364e7df1ff40474f9fddf9e70657ecb0e4f670ff0b0e", size = 6262524, upload-time = "2025-02-16T04:27:19.725Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/4fdf18f4cc1995f1992771d2a51cf186a9d7a8ec973c9693f8453850c707/pyproj-3.7.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6575b2e53cc9e3e461ad6f0692a5564b96e7782c28631c7771c668770915e169", size = 4665102, upload-time = "2025-02-16T04:27:24.428Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d2/360eb127380106cee83569954ae696b88a891c804d7a93abe3fbc15f5976/pyproj-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb516ee35ed57789b46b96080edf4e503fdb62dbb2e3c6581e0d6c83fca014b", size = 9432667, upload-time = "2025-02-16T04:27:27.04Z" }, - { url = "https://files.pythonhosted.org/packages/76/a5/c6e11b9a99ce146741fb4d184d5c468446c6d6015b183cae82ac822a6cfa/pyproj-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e47c4e93b88d99dd118875ee3ca0171932444cdc0b52d493371b5d98d0f30ee", size = 9259185, upload-time = "2025-02-16T04:27:30.35Z" }, - { url = "https://files.pythonhosted.org/packages/41/56/a3c15c42145797a99363fa0fdb4e9805dccb8b4a76a6d7b2cdf36ebcc2a1/pyproj-3.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3e8d276caeae34fcbe4813855d0d97b9b825bab8d7a8b86d859c24a6213a5a0d", size = 10469103, upload-time = "2025-02-16T04:27:33.542Z" }, - { url = "https://files.pythonhosted.org/packages/ef/73/c9194c2802fefe2a4fd4230bdd5ab083e7604e93c64d0356fa49c363bad6/pyproj-3.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f173f851ee75e54acdaa053382b6825b400cb2085663a9bb073728a59c60aebb", size = 10401391, upload-time = "2025-02-16T04:27:36.051Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1d/ce8bb5b9251b04d7c22d63619bb3db3d2397f79000a9ae05b3fd86a5837e/pyproj-3.7.1-cp310-cp310-win32.whl", hash = "sha256:f550281ed6e5ea88fcf04a7c6154e246d5714be495c50c9e8e6b12d3fb63e158", size = 5869997, upload-time = "2025-02-16T04:27:38.302Z" }, - { url = "https://files.pythonhosted.org/packages/09/6a/ca145467fd2e5b21e3d5b8c2b9645dcfb3b68f08b62417699a1f5689008e/pyproj-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3537668992a709a2e7f068069192138618c00d0ba113572fdd5ee5ffde8222f3", size = 6278581, upload-time = "2025-02-16T04:27:41.051Z" }, - { url = "https://files.pythonhosted.org/packages/ab/0d/63670fc527e664068b70b7cab599aa38b7420dd009bdc29ea257e7f3dfb3/pyproj-3.7.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:a94e26c1a4950cea40116775588a2ca7cf56f1f434ff54ee35a84718f3841a3d", size = 6264315, upload-time = "2025-02-16T04:27:44.539Z" }, - { url = "https://files.pythonhosted.org/packages/25/9d/cbaf82cfb290d1f1fa42feb9ba9464013bb3891e40c4199f8072112e4589/pyproj-3.7.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:263b54ba5004b6b957d55757d846fc5081bc02980caa0279c4fc95fa0fff6067", size = 4666267, upload-time = "2025-02-16T04:27:47.019Z" }, - { url = "https://files.pythonhosted.org/packages/79/53/24f9f9b8918c0550f3ff49ad5de4cf3f0688c9f91ff191476db8979146fe/pyproj-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d6a2ccd5607cd15ef990c51e6f2dd27ec0a741e72069c387088bba3aab60fa", size = 9680510, upload-time = "2025-02-16T04:27:49.239Z" }, - { url = "https://files.pythonhosted.org/packages/3c/ac/12fab74a908d40b63174dc704587febd0729414804bbfd873cabe504ff2d/pyproj-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5dcf24ede53d8abab7d8a77f69ff1936c6a8843ef4fcc574646e4be66e5739", size = 9493619, upload-time = "2025-02-16T04:27:52.65Z" }, - { url = "https://files.pythonhosted.org/packages/c4/45/26311d6437135da2153a178125db5dfb6abce831ce04d10ec207eabac70a/pyproj-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c2e7449840a44ce860d8bea2c6c1c4bc63fa07cba801dcce581d14dcb031a02", size = 10709755, upload-time = "2025-02-16T04:27:55.239Z" }, - { url = "https://files.pythonhosted.org/packages/99/52/4ecd0986f27d0e6c8ee3a7bc5c63da15acd30ac23034f871325b297e61fd/pyproj-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0829865c1d3a3543f918b3919dc601eea572d6091c0dd175e1a054db9c109274", size = 10642970, upload-time = "2025-02-16T04:27:58.343Z" }, - { url = "https://files.pythonhosted.org/packages/3f/a5/d3bfc018fc92195a000d1d28acc1f3f1df15ff9f09ece68f45a2636c0134/pyproj-3.7.1-cp311-cp311-win32.whl", hash = "sha256:6181960b4b812e82e588407fe5c9c68ada267c3b084db078f248db5d7f45d18a", size = 5868295, upload-time = "2025-02-16T04:28:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/92/39/ef6f06a5b223dbea308cfcbb7a0f72e7b506aef1850e061b2c73b0818715/pyproj-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ad0ff443a785d84e2b380869fdd82e6bfc11eba6057d25b4409a9bbfa867970", size = 6279871, upload-time = "2025-02-16T04:28:04.988Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c9/876d4345b8d17f37ac59ebd39f8fa52fc6a6a9891a420f72d050edb6b899/pyproj-3.7.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:2781029d90df7f8d431e29562a3f2d8eafdf233c4010d6fc0381858dc7373217", size = 6264087, upload-time = "2025-02-16T04:28:09.036Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/5f8691f8c90e7f402cc80a6276eb19d2ec1faa150d5ae2dd9c7b0a254da8/pyproj-3.7.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d61bf8ab04c73c1da08eedaf21a103b72fa5b0a9b854762905f65ff8b375d394", size = 4669628, upload-time = "2025-02-16T04:28:10.944Z" }, - { url = "https://files.pythonhosted.org/packages/42/ec/16475bbb79c1c68845c0a0d9c60c4fb31e61b8a2a20bc18b1a81e81c7f68/pyproj-3.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04abc517a8555d1b05fcee768db3280143fe42ec39fdd926a2feef31631a1f2f", size = 9721415, upload-time = "2025-02-16T04:28:13.342Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a3/448f05b15e318bd6bea9a32cfaf11e886c4ae61fa3eee6e09ed5c3b74bb2/pyproj-3.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084c0a475688f934d386c2ab3b6ce03398a473cd48adfda70d9ab8f87f2394a0", size = 9556447, upload-time = "2025-02-16T04:28:15.818Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ae/bd15fe8d8bd914ead6d60bca7f895a4e6f8ef7e3928295134ff9a7dad14c/pyproj-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a20727a23b1e49c7dc7fe3c3df8e56a8a7acdade80ac2f5cca29d7ca5564c145", size = 10758317, upload-time = "2025-02-16T04:28:18.338Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d9/5ccefb8bca925f44256b188a91c31238cae29ab6ee7f53661ecc04616146/pyproj-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bf84d766646f1ebd706d883755df4370aaf02b48187cedaa7e4239f16bc8213d", size = 10771259, upload-time = "2025-02-16T04:28:20.822Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7d/31dedff9c35fa703162f922eeb0baa6c44a3288469a5fd88d209e2892f9e/pyproj-3.7.1-cp312-cp312-win32.whl", hash = "sha256:5f0da2711364d7cb9f115b52289d4a9b61e8bca0da57f44a3a9d6fc9bdeb7274", size = 5859914, upload-time = "2025-02-16T04:28:23.303Z" }, - { url = "https://files.pythonhosted.org/packages/3e/47/c6ab03d6564a7c937590cff81a2742b5990f096cce7c1a622d325be340ee/pyproj-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:aee664a9d806612af30a19dba49e55a7a78ebfec3e9d198f6a6176e1d140ec98", size = 6273196, upload-time = "2025-02-16T04:28:25.227Z" }, - { url = "https://files.pythonhosted.org/packages/ef/01/984828464c9960036c602753fc0f21f24f0aa9043c18fa3f2f2b66a86340/pyproj-3.7.1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:5f8d02ef4431dee414d1753d13fa82a21a2f61494737b5f642ea668d76164d6d", size = 6253062, upload-time = "2025-02-16T04:28:27.861Z" }, - { url = "https://files.pythonhosted.org/packages/68/65/6ecdcdc829811a2c160cdfe2f068a009fc572fd4349664f758ccb0853a7c/pyproj-3.7.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b853ae99bda66cbe24b4ccfe26d70601d84375940a47f553413d9df570065e0", size = 4660548, upload-time = "2025-02-16T04:28:29.526Z" }, - { url = "https://files.pythonhosted.org/packages/67/da/dda94c4490803679230ba4c17a12f151b307a0d58e8110820405ca2d98db/pyproj-3.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83db380c52087f9e9bdd8a527943b2e7324f275881125e39475c4f9277bdeec4", size = 9662464, upload-time = "2025-02-16T04:28:31.437Z" }, - { url = "https://files.pythonhosted.org/packages/6f/57/f61b7d22c91ae1d12ee00ac4c0038714e774ebcd851b9133e5f4f930dd40/pyproj-3.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b35ed213892e211a3ce2bea002aa1183e1a2a9b79e51bb3c6b15549a831ae528", size = 9497461, upload-time = "2025-02-16T04:28:33.848Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f6/932128236f79d2ac7d39fe1a19667fdf7155d9a81d31fb9472a7a497790f/pyproj-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8b15b0463d1303bab113d1a6af2860a0d79013c3a66fcc5475ce26ef717fd4f", size = 10708869, upload-time = "2025-02-16T04:28:37.34Z" }, - { url = "https://files.pythonhosted.org/packages/1d/0d/07ac7712994454a254c383c0d08aff9916a2851e6512d59da8dc369b1b02/pyproj-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:87229e42b75e89f4dad6459200f92988c5998dfb093c7c631fb48524c86cd5dc", size = 10729260, upload-time = "2025-02-16T04:28:40.639Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d0/9c604bc72c37ba69b867b6df724d6a5af6789e8c375022c952f65b2af558/pyproj-3.7.1-cp313-cp313-win32.whl", hash = "sha256:d666c3a3faaf3b1d7fc4a544059c4eab9d06f84a604b070b7aa2f318e227798e", size = 5855462, upload-time = "2025-02-16T04:28:42.827Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/68a2b7f5fb6400c64aad82d72bcc4bc531775e62eedff993a77c780defd0/pyproj-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3caac7473be22b6d6e102dde6c46de73b96bc98334e577dfaee9886f102ea2e", size = 6266573, upload-time = "2025-02-16T04:28:44.727Z" }, -] - -[[package]] -name = "pyproj" -version = "3.7.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz", hash = "sha256:39a0cf1ecc7e282d1d30f36594ebd55c9fae1fda8a2622cee5d100430628f88c", size = 226279, upload-time = "2025-08-14T12:05:42.18Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/bd/f205552cd1713b08f93b09e39a3ec99edef0b3ebbbca67b486fdf1abe2de/pyproj-3.7.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2514d61f24c4e0bb9913e2c51487ecdaeca5f8748d8313c933693416ca41d4d5", size = 6227022, upload-time = "2025-08-14T12:03:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/75/4c/9a937e659b8b418ab573c6d340d27e68716928953273e0837e7922fcac34/pyproj-3.7.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8693ca3892d82e70de077701ee76dd13d7bca4ae1c9d1e739d72004df015923a", size = 4625810, upload-time = "2025-08-14T12:03:53.808Z" }, - { url = "https://files.pythonhosted.org/packages/c0/7d/a9f41e814dc4d1dc54e95b2ccaf0b3ebe3eb18b1740df05fe334724c3d89/pyproj-3.7.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5e26484d80fea56273ed1555abaea161e9661d81a6c07815d54b8e883d4ceb25", size = 9638694, upload-time = "2025-08-14T12:03:55.669Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ab/9bdb4a6216b712a1f9aab1c0fcbee5d3726f34a366f29c3e8c08a78d6b70/pyproj-3.7.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:281cb92847814e8018010c48b4069ff858a30236638631c1a91dd7bfa68f8a8a", size = 9493977, upload-time = "2025-08-14T12:03:57.937Z" }, - { url = "https://files.pythonhosted.org/packages/c9/db/2db75b1b6190f1137b1c4e8ef6a22e1c338e46320f6329bfac819143e063/pyproj-3.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9c8577f0b7bb09118ec2e57e3babdc977127dd66326d6c5d755c76b063e6d9dc", size = 10841151, upload-time = "2025-08-14T12:04:00.271Z" }, - { url = "https://files.pythonhosted.org/packages/89/f7/989643394ba23a286e9b7b3f09981496172f9e0d4512457ffea7dc47ffc7/pyproj-3.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a23f59904fac3a5e7364b3aa44d288234af267ca041adb2c2b14a903cd5d3ac5", size = 10751585, upload-time = "2025-08-14T12:04:02.228Z" }, - { url = "https://files.pythonhosted.org/packages/53/6d/ad928fe975a6c14a093c92e6a319ca18f479f3336bb353a740bdba335681/pyproj-3.7.2-cp311-cp311-win32.whl", hash = "sha256:f2af4ed34b2cf3e031a2d85b067a3ecbd38df073c567e04b52fa7a0202afde8a", size = 5908533, upload-time = "2025-08-14T12:04:04.821Z" }, - { url = "https://files.pythonhosted.org/packages/79/e0/b95584605cec9ed50b7ebaf7975d1c4ddeec5a86b7a20554ed8b60042bd7/pyproj-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:0b7cb633565129677b2a183c4d807c727d1c736fcb0568a12299383056e67433", size = 6320742, upload-time = "2025-08-14T12:04:06.357Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4d/536e8f93bca808175c2d0a5ac9fdf69b960d8ab6b14f25030dccb07464d7/pyproj-3.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:38b08d85e3a38e455625b80e9eb9f78027c8e2649a21dec4df1f9c3525460c71", size = 6245772, upload-time = "2025-08-14T12:04:08.365Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ab/9893ea9fb066be70ed9074ae543914a618c131ed8dff2da1e08b3a4df4db/pyproj-3.7.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0a9bb26a6356fb5b033433a6d1b4542158fb71e3c51de49b4c318a1dff3aeaab", size = 6219832, upload-time = "2025-08-14T12:04:10.264Z" }, - { url = "https://files.pythonhosted.org/packages/53/78/4c64199146eed7184eb0e85bedec60a4aa8853b6ffe1ab1f3a8b962e70a0/pyproj-3.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:567caa03021178861fad27fabde87500ec6d2ee173dd32f3e2d9871e40eebd68", size = 4620650, upload-time = "2025-08-14T12:04:11.978Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ac/14a78d17943898a93ef4f8c6a9d4169911c994e3161e54a7cedeba9d8dde/pyproj-3.7.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c203101d1dc3c038a56cff0447acc515dd29d6e14811406ac539c21eed422b2a", size = 9667087, upload-time = "2025-08-14T12:04:13.964Z" }, - { url = "https://files.pythonhosted.org/packages/b8/be/212882c450bba74fc8d7d35cbd57e4af84792f0a56194819d98106b075af/pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1edc34266c0c23ced85f95a1ee8b47c9035eae6aca5b6b340327250e8e281630", size = 9552797, upload-time = "2025-08-14T12:04:16.624Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c0/c0f25c87b5d2a8686341c53c1792a222a480d6c9caf60311fec12c99ec26/pyproj-3.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa9f26c21bc0e2dc3d224cb1eb4020cf23e76af179a7c66fea49b828611e4260", size = 10837036, upload-time = "2025-08-14T12:04:18.733Z" }, - { url = "https://files.pythonhosted.org/packages/5d/37/5cbd6772addde2090c91113332623a86e8c7d583eccb2ad02ea634c4a89f/pyproj-3.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9428b318530625cb389b9ddc9c51251e172808a4af79b82809376daaeabe5e9", size = 10775952, upload-time = "2025-08-14T12:04:20.709Z" }, - { url = "https://files.pythonhosted.org/packages/69/a1/dc250e3cf83eb4b3b9a2cf86fdb5e25288bd40037ae449695550f9e96b2f/pyproj-3.7.2-cp312-cp312-win32.whl", hash = "sha256:b3d99ed57d319da042f175f4554fc7038aa4bcecc4ac89e217e350346b742c9d", size = 5898872, upload-time = "2025-08-14T12:04:22.485Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a6/6fe724b72b70f2b00152d77282e14964d60ab092ec225e67c196c9b463e5/pyproj-3.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:11614a054cd86a2ed968a657d00987a86eeb91fdcbd9ad3310478685dc14a128", size = 6312176, upload-time = "2025-08-14T12:04:24.736Z" }, - { url = "https://files.pythonhosted.org/packages/5d/68/915cc32c02a91e76d02c8f55d5a138d6ef9e47a0d96d259df98f4842e558/pyproj-3.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:509a146d1398bafe4f53273398c3bb0b4732535065fa995270e52a9d3676bca3", size = 6233452, upload-time = "2025-08-14T12:04:27.287Z" }, - { url = "https://files.pythonhosted.org/packages/be/14/faf1b90d267cea68d7e70662e7f88cefdb1bc890bd596c74b959e0517a72/pyproj-3.7.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:19466e529b1b15eeefdf8ff26b06fa745856c044f2f77bf0edbae94078c1dfa1", size = 6214580, upload-time = "2025-08-14T12:04:28.804Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/da9a45b184d375f62667f62eba0ca68569b0bd980a0bb7ffcc1d50440520/pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c79b9b84c4a626c5dc324c0d666be0bfcebd99f7538d66e8898c2444221b3da7", size = 4615388, upload-time = "2025-08-14T12:04:30.553Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e7/d2b459a4a64bca328b712c1b544e109df88e5c800f7c143cfbc404d39bfb/pyproj-3.7.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ceecf374cacca317bc09e165db38ac548ee3cad07c3609442bd70311c59c21aa", size = 9628455, upload-time = "2025-08-14T12:04:32.435Z" }, - { url = "https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5141a538ffdbe4bfd157421828bb2e07123a90a7a2d6f30fa1462abcfb5ce681", size = 9514269, upload-time = "2025-08-14T12:04:34.599Z" }, - { url = "https://files.pythonhosted.org/packages/34/38/07a9b89ae7467872f9a476883a5bad9e4f4d1219d31060f0f2b282276cbe/pyproj-3.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f000841e98ea99acbb7b8ca168d67773b0191de95187228a16110245c5d954d5", size = 10808437, upload-time = "2025-08-14T12:04:36.485Z" }, - { url = "https://files.pythonhosted.org/packages/12/56/fda1daeabbd39dec5b07f67233d09f31facb762587b498e6fc4572be9837/pyproj-3.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8115faf2597f281a42ab608ceac346b4eb1383d3b45ab474fd37341c4bf82a67", size = 10745540, upload-time = "2025-08-14T12:04:38.568Z" }, - { url = "https://files.pythonhosted.org/packages/0d/90/c793182cbba65a39a11db2ac6b479fe76c59e6509ae75e5744c344a0da9d/pyproj-3.7.2-cp313-cp313-win32.whl", hash = "sha256:f18c0579dd6be00b970cb1a6719197fceecc407515bab37da0066f0184aafdf3", size = 5896506, upload-time = "2025-08-14T12:04:41.059Z" }, - { url = "https://files.pythonhosted.org/packages/be/0f/747974129cf0d800906f81cd25efd098c96509026e454d4b66868779ab04/pyproj-3.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:bb41c29d5f60854b1075853fe80c58950b398d4ebb404eb532536ac8d2834ed7", size = 6310195, upload-time = "2025-08-14T12:04:42.974Z" }, - { url = "https://files.pythonhosted.org/packages/82/64/fc7598a53172c4931ec6edf5228280663063150625d3f6423b4c20f9daff/pyproj-3.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:2b617d573be4118c11cd96b8891a0b7f65778fa7733ed8ecdb297a447d439100", size = 6230748, upload-time = "2025-08-14T12:04:44.491Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f0/611dd5cddb0d277f94b7af12981f56e1441bf8d22695065d4f0df5218498/pyproj-3.7.2-cp313-cp313t-macosx_13_0_x86_64.whl", hash = "sha256:d27b48f0e81beeaa2b4d60c516c3a1cfbb0c7ff6ef71256d8e9c07792f735279", size = 6241729, upload-time = "2025-08-14T12:04:46.274Z" }, - { url = "https://files.pythonhosted.org/packages/15/93/40bd4a6c523ff9965e480870611aed7eda5aa2c6128c6537345a2b77b542/pyproj-3.7.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:55a3610d75023c7b1c6e583e48ef8f62918e85a2ae81300569d9f104d6684bb6", size = 4652497, upload-time = "2025-08-14T12:04:48.203Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ae/7150ead53c117880b35e0d37960d3138fe640a235feb9605cb9386f50bb0/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8d7349182fa622696787cc9e195508d2a41a64765da9b8a6bee846702b9e6220", size = 9942610, upload-time = "2025-08-14T12:04:49.652Z" }, - { url = "https://files.pythonhosted.org/packages/d8/17/7a4a7eafecf2b46ab64e5c08176c20ceb5844b503eaa551bf12ccac77322/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:d230b186eb876ed4f29a7c5ee310144c3a0e44e89e55f65fb3607e13f6db337c", size = 9692390, upload-time = "2025-08-14T12:04:51.731Z" }, - { url = "https://files.pythonhosted.org/packages/c3/55/ae18f040f6410f0ea547a21ada7ef3e26e6c82befa125b303b02759c0e9d/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:237499c7862c578d0369e2b8ac56eec550e391a025ff70e2af8417139dabb41c", size = 11047596, upload-time = "2025-08-14T12:04:53.748Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2e/d3fff4d2909473f26ae799f9dda04caa322c417a51ff3b25763f7d03b233/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8c225f5978abd506fd9a78eaaf794435e823c9156091cabaab5374efb29d7f69", size = 10896975, upload-time = "2025-08-14T12:04:55.875Z" }, - { url = "https://files.pythonhosted.org/packages/f2/bc/8fc7d3963d87057b7b51ebe68c1e7c51c23129eee5072ba6b86558544a46/pyproj-3.7.2-cp313-cp313t-win32.whl", hash = "sha256:2da731876d27639ff9d2d81c151f6ab90a1546455fabd93368e753047be344a2", size = 5953057, upload-time = "2025-08-14T12:04:58.466Z" }, - { url = "https://files.pythonhosted.org/packages/cc/27/ea9809966cc47d2d51e6d5ae631ea895f7c7c7b9b3c29718f900a8f7d197/pyproj-3.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f54d91ae18dd23b6c0ab48126d446820e725419da10617d86a1b69ada6d881d3", size = 6375414, upload-time = "2025-08-14T12:04:59.861Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f8/1ef0129fba9a555c658e22af68989f35e7ba7b9136f25758809efec0cd6e/pyproj-3.7.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fc52ba896cfc3214dc9f9ca3c0677a623e8fdd096b257c14a31e719d21ff3fdd", size = 6262501, upload-time = "2025-08-14T12:05:01.39Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:2aaa328605ace41db050d06bac1adc11f01b71fe95c18661497763116c3a0f02", size = 6214541, upload-time = "2025-08-14T12:05:03.166Z" }, - { url = "https://files.pythonhosted.org/packages/03/68/68ada9c8aea96ded09a66cfd9bf87aa6db8c2edebe93f5bf9b66b0143fbc/pyproj-3.7.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:35dccbce8201313c596a970fde90e33605248b66272595c061b511c8100ccc08", size = 4617456, upload-time = "2025-08-14T12:05:04.563Z" }, - { url = "https://files.pythonhosted.org/packages/81/e4/4c50ceca7d0e937977866b02cb64e6ccf4df979a5871e521f9e255df6073/pyproj-3.7.2-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:25b0b7cb0042444c29a164b993c45c1b8013d6c48baa61dc1160d834a277e83b", size = 9615590, upload-time = "2025-08-14T12:05:06.094Z" }, - { url = "https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:85def3a6388e9ba51f964619aa002a9d2098e77c6454ff47773bb68871024281", size = 9474960, upload-time = "2025-08-14T12:05:07.973Z" }, - { url = "https://files.pythonhosted.org/packages/51/07/9d48ad0a8db36e16f842f2c8a694c1d9d7dcf9137264846bef77585a71f3/pyproj-3.7.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b1bccefec3875ab81eabf49059e2b2ea77362c178b66fd3528c3e4df242f1516", size = 10799478, upload-time = "2025-08-14T12:05:14.102Z" }, - { url = "https://files.pythonhosted.org/packages/85/cf/2f812b529079f72f51ff2d6456b7fef06c01735e5cfd62d54ffb2b548028/pyproj-3.7.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d5371ca114d6990b675247355a801925814eca53e6c4b2f1b5c0a956336ee36e", size = 10710030, upload-time = "2025-08-14T12:05:16.317Z" }, - { url = "https://files.pythonhosted.org/packages/99/9b/4626a19e1f03eba4c0e77b91a6cf0f73aa9cb5d51a22ee385c22812bcc2c/pyproj-3.7.2-cp314-cp314-win32.whl", hash = "sha256:77f066626030f41be543274f5ac79f2a511fe89860ecd0914f22131b40a0ec25", size = 5991181, upload-time = "2025-08-14T12:05:19.492Z" }, - { url = "https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a964da1696b8522806f4276ab04ccfff8f9eb95133a92a25900697609d40112", size = 6434721, upload-time = "2025-08-14T12:05:21.022Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ce/6c910ea2e1c74ef673c5d48c482564b8a7824a44c4e35cca2e765b68cfcc/pyproj-3.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:e258ab4dbd3cf627809067c0ba8f9884ea76c8e5999d039fb37a1619c6c3e1f6", size = 6363821, upload-time = "2025-08-14T12:05:22.627Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e4/5532f6f7491812ba782a2177fe9de73fd8e2912b59f46a1d056b84b9b8f2/pyproj-3.7.2-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:bbbac2f930c6d266f70ec75df35ef851d96fdb3701c674f42fd23a9314573b37", size = 6241773, upload-time = "2025-08-14T12:05:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/20/1f/0938c3f2bbbef1789132d1726d9b0e662f10cfc22522743937f421ad664e/pyproj-3.7.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b7544e0a3d6339dc9151e9c8f3ea62a936ab7cc446a806ec448bbe86aebb979b", size = 4652537, upload-time = "2025-08-14T12:05:26.391Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a8/488b1ed47d25972f33874f91f09ca8f2227902f05f63a2b80dc73e7b1c97/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f7f5133dca4c703e8acadf6f30bc567d39a42c6af321e7f81975c2518f3ed357", size = 9940864, upload-time = "2025-08-14T12:05:27.985Z" }, - { url = "https://files.pythonhosted.org/packages/c7/cc/7f4c895d0cb98e47b6a85a6d79eaca03eb266129eed2f845125c09cf31ff/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5aff3343038d7426aa5076f07feb88065f50e0502d1b0d7c22ddfdd2c75a3f81", size = 9688868, upload-time = "2025-08-14T12:05:30.425Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/c7e306b8bb0f071d9825b753ee4920f066c40fbfcce9372c4f3cfb2fc4ed/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b0552178c61f2ac1c820d087e8ba6e62b29442debddbb09d51c4bf8acc84d888", size = 11045910, upload-time = "2025-08-14T12:05:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/42/fb/538a4d2df695980e2dde5c04d965fbdd1fe8c20a3194dc4aaa3952a4d1be/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:47d87db2d2c436c5fd0409b34d70bb6cdb875cca2ebe7a9d1c442367b0ab8d59", size = 10895724, upload-time = "2025-08-14T12:05:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8b/a3f0618b03957de9db5489a04558a8826f43906628bb0b766033aa3b5548/pyproj-3.7.2-cp314-cp314t-win32.whl", hash = "sha256:c9b6f1d8ad3e80a0ee0903a778b6ece7dca1d1d40f6d114ae01bc8ddbad971aa", size = 6056848, upload-time = "2025-08-14T12:05:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/bc/56/413240dd5149dd3291eda55aa55a659da4431244a2fd1319d0ae89407cfb/pyproj-3.7.2-cp314-cp314t-win_amd64.whl", hash = "sha256:1914e29e27933ba6f9822663ee0600f169014a2859f851c054c88cf5ea8a333c", size = 6517676, upload-time = "2025-08-14T12:05:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/15/73/a7141a1a0559bf1a7aa42a11c879ceb19f02f5c6c371c6d57fd86cefd4d1/pyproj-3.7.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4", size = 6391844, upload-time = "2025-08-14T12:05:40.745Z" }, -] - -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - -[[package]] -name = "pystac" -source = { editable = "." } -dependencies = [ - { name = "python-dateutil" }, -] - -[package.optional-dependencies] -jinja2 = [ - { name = "jinja2" }, -] -orjson = [ - { name = "orjson" }, -] -urllib3 = [ - { name = "urllib3" }, -] -validation = [ - { name = "jsonschema" }, -] - -[package.dev-dependencies] -dev = [ - { name = "asv" }, - { name = "codespell" }, - { name = "coverage" }, - { name = "doc8" }, - { name = "filelock" }, - { name = "html5lib" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "mypy" }, - { name = "orjson" }, - { name = "packaging" }, - { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-mock" }, - { name = "pytest-recording" }, - { name = "requests-mock" }, - { name = "ruff" }, - { name = "types-html5lib" }, - { name = "types-jsonschema" }, - { name = "types-orjson" }, - { name = "types-python-dateutil" }, - { name = "types-urllib3" }, - { name = "urllib3" }, - { name = "virtualenv" }, -] -docs = [ - { name = "adlfs" }, - { name = "boto3" }, - { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jinja2" }, - { name = "jupyter" }, - { name = "jupyterlab" }, - { name = "nbsphinx" }, - { name = "odc-stac" }, - { name = "planetary-computer" }, - { name = "pydata-sphinx-theme" }, - { name = "rasterio" }, - { name = "rioxarray" }, - { name = "shapely" }, - { name = "sphinx" }, - { name = "sphinx-autobuild" }, - { name = "sphinx-design" }, - { name = "sphinxcontrib-fulltoc" }, - { name = "starlette" }, - { name = "xpystac", version = "0.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "xpystac", version = "0.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "zarr", version = "2.18.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "zarr", version = "3.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] - -[package.metadata] -requires-dist = [ - { name = "jinja2", marker = "extra == 'jinja2'", specifier = "<4.0" }, - { name = "jsonschema", marker = "extra == 'validation'", specifier = "~=4.18" }, - { name = "orjson", marker = "extra == 'orjson'", specifier = ">=3.5" }, - { name = "python-dateutil", specifier = ">=2.7.0" }, - { name = "urllib3", marker = "extra == 'urllib3'", specifier = ">=2.6.0" }, -] -provides-extras = ["jinja2", "orjson", "urllib3", "validation"] - -[package.metadata.requires-dev] -dev = [ - { name = "asv", specifier = ">=0.6.4" }, - { name = "codespell", specifier = "<2.3" }, - { name = "coverage", specifier = ">=7.6.2" }, - { name = "doc8", specifier = ">=1.1.2" }, - { name = "filelock", specifier = ">=3.20.1" }, - { name = "html5lib", specifier = ">=1.1" }, - { name = "jinja2", specifier = ">=3.1.4" }, - { name = "jsonschema", specifier = ">=4.23.0" }, - { name = "mypy", specifier = ">=1.11.2" }, - { name = "orjson", specifier = ">=3.10.7" }, - { name = "packaging", specifier = ">=24.1" }, - { name = "pre-commit", specifier = ">=4.0.1" }, - { name = "pytest", specifier = ">=8.3.3" }, - { name = "pytest-cov", specifier = ">=5.0.0" }, - { name = "pytest-mock", specifier = ">=3.14.0" }, - { name = "pytest-recording", specifier = ">=0.13.2" }, - { name = "requests-mock", specifier = ">=1.12.1" }, - { name = "ruff", specifier = ">=0.6.9" }, - { name = "types-html5lib", specifier = ">=1.1.11.20240806" }, - { name = "types-jsonschema", specifier = ">=4.23.0.20240813" }, - { name = "types-orjson", specifier = ">=3.6.2" }, - { name = "types-python-dateutil", specifier = ">=2.9.0.20241003" }, - { name = "types-urllib3", specifier = ">=1.26.25.14" }, - { name = "urllib3", specifier = ">=1.26.19" }, - { name = "virtualenv", specifier = ">=20.26.6" }, -] -docs = [ - { name = "adlfs", specifier = ">=2024.12.0" }, - { name = "boto3", specifier = ">=1.35.39" }, - { name = "ipython", specifier = ">=8.28.0" }, - { name = "jinja2", specifier = ">=3.1.4" }, - { name = "jupyter", specifier = ">=1.1.1" }, - { name = "jupyterlab", specifier = ">=4.4.8" }, - { name = "nbsphinx", specifier = ">=0.9.5" }, - { name = "odc-stac", specifier = ">=0.4.0" }, - { name = "planetary-computer", specifier = ">=1.0.0" }, - { name = "pydata-sphinx-theme", specifier = ">=0.15.4" }, - { name = "rasterio", specifier = ">=1.4.1" }, - { name = "rioxarray", specifier = ">=0.19.0" }, - { name = "shapely", specifier = ">=2.0.6" }, - { name = "sphinx", specifier = ">=8.1.1" }, - { name = "sphinx-autobuild", specifier = ">=2024.10.3" }, - { name = "sphinx-design", specifier = ">=0.6.1" }, - { name = "sphinxcontrib-fulltoc", specifier = ">=1.2.0" }, - { name = "starlette", specifier = ">=0.49.1" }, - { name = "xpystac", specifier = ">=0.2.0" }, - { name = "zarr", specifier = ">=2.18.3" }, -] - -[[package]] -name = "pystac-client" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pystac", extra = ["validation"] }, - { name = "python-dateutil" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/8d/b98aeffd325fc208e1624cf586d0c4dfb927bc7a2bce20d3b58ee80d2483/pystac_client-0.9.0.tar.gz", hash = "sha256:3908951583bcc6a3aaaf2828024a8e03764e6ca9d9f9f1d8149df587e14dd744", size = 52339, upload-time = "2025-07-18T15:44:41.1Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/d2/5f6367b14c9f250d1a6725d18bd1e9584f5ab1587e292f3a847e59189598/pystac_client-0.9.0-py3-none-any.whl", hash = "sha256:eed146b5980f93646aaa3a59080f11f1dcab6000b0bfbc28b1d0c6fd0a61eda1", size = 41826, upload-time = "2025-07-18T15:44:40.197Z" }, -] - -[[package]] -name = "pytest" -version = "8.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, -] - -[[package]] -name = "pytest-cov" -version = "6.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, -] - -[[package]] -name = "pytest-recording" -version = "0.13.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, - { name = "vcrpy", version = "5.1.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation == 'PyPy'" }, - { name = "vcrpy", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/32/9c/f4027c5f1693847b06d11caf4b4f6bb09f22c1581ada4663877ec166b8c6/pytest_recording-0.13.4.tar.gz", hash = "sha256:568d64b2a85992eec4ae0a419c855d5fd96782c5fb016784d86f18053792768c", size = 26576, upload-time = "2025-05-08T10:41:11.231Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/c2/ce34735972cc42d912173e79f200fe66530225190c06655c5632a9d88f1e/pytest_recording-0.13.4-py3-none-any.whl", hash = "sha256:ad49a434b51b1c4f78e85b1e6b74fdcc2a0a581ca16e52c798c6ace971f7f439", size = 13723, upload-time = "2025-05-08T10:41:09.684Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, -] - -[[package]] -name = "python-json-logger" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/de/d3144a0bceede957f961e975f3752760fbe390d57fbe194baf709d8f1f7b/python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84", size = 16642, upload-time = "2025-03-07T07:08:27.301Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/20/0f2523b9e50a8052bc6a8b732dfc8568abbdc42010aef03a2d750bdab3b2/python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7", size = 15163, upload-time = "2025-03-07T07:08:25.627Z" }, -] - -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, - { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, -] - -[[package]] -name = "pywinpty" -version = "2.0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/7c/917f9c4681bb8d34bfbe0b79d36bbcd902651aeab48790df3d30ba0202fb/pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2", size = 29017, upload-time = "2025-02-03T21:53:23.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/b7/855db919ae526d2628f3f2e6c281c4cdff7a9a8af51bb84659a9f07b1861/pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e", size = 1405161, upload-time = "2025-02-03T21:56:25.008Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ac/6884dcb7108af66ad53f73ef4dad096e768c9203a6e6ce5e6b0c4a46e238/pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca", size = 1405249, upload-time = "2025-02-03T21:55:47.114Z" }, - { url = "https://files.pythonhosted.org/packages/88/e5/9714def18c3a411809771a3fbcec70bffa764b9675afb00048a620fca604/pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc", size = 1405243, upload-time = "2025-02-03T21:56:52.476Z" }, - { url = "https://files.pythonhosted.org/packages/fb/16/2ab7b3b7f55f3c6929e5f629e1a68362981e4e5fed592a2ed1cb4b4914a5/pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408", size = 1405020, upload-time = "2025-02-03T21:56:04.753Z" }, - { url = "https://files.pythonhosted.org/packages/7c/16/edef3515dd2030db2795dbfbe392232c7a0f3dc41b98e92b38b42ba497c7/pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901", size = 1404151, upload-time = "2025-02-03T21:55:53.628Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, -] - -[[package]] -name = "pyzmq" -version = "27.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/06/50a4e9648b3e8b992bef8eb632e457307553a89d294103213cfd47b3da69/pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf", size = 280478, upload-time = "2025-06-13T14:09:07.087Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/09/1681d4b047626d352c083770618ac29655ab1f5c20eee31dc94c000b9b7b/pyzmq-27.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:b973ee650e8f442ce482c1d99ca7ab537c69098d53a3d046676a484fd710c87a", size = 1329291, upload-time = "2025-06-13T14:06:57.945Z" }, - { url = "https://files.pythonhosted.org/packages/9d/b2/9c9385225fdd54db9506ed8accbb9ea63ca813ba59d43d7f282a6a16a30b/pyzmq-27.0.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:661942bc7cd0223d569d808f2e5696d9cc120acc73bf3e88a1f1be7ab648a7e4", size = 905952, upload-time = "2025-06-13T14:07:03.232Z" }, - { url = "https://files.pythonhosted.org/packages/41/73/333c72c7ec182cdffe25649e3da1c3b9f3cf1cede63cfdc23d1384d4a601/pyzmq-27.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50360fb2a056ffd16e5f4177eee67f1dd1017332ea53fb095fe7b5bf29c70246", size = 666165, upload-time = "2025-06-13T14:07:04.667Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fe/fc7b9c1a50981928e25635a926653cb755364316db59ccd6e79cfb9a0b4f/pyzmq-27.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf209a6dc4b420ed32a7093642843cbf8703ed0a7d86c16c0b98af46762ebefb", size = 853755, upload-time = "2025-06-13T14:07:06.93Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4c/740ed4b6e8fa160cd19dc5abec8db68f440564b2d5b79c1d697d9862a2f7/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2dace4a7041cca2fba5357a2d7c97c5effdf52f63a1ef252cfa496875a3762d", size = 1654868, upload-time = "2025-06-13T14:07:08.224Z" }, - { url = "https://files.pythonhosted.org/packages/97/00/875b2ecfcfc78ab962a59bd384995186818524ea957dc8ad3144611fae12/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:63af72b2955fc77caf0a77444baa2431fcabb4370219da38e1a9f8d12aaebe28", size = 2033443, upload-time = "2025-06-13T14:07:09.653Z" }, - { url = "https://files.pythonhosted.org/packages/60/55/6dd9c470c42d713297c5f2a56f7903dc1ebdb4ab2edda996445c21651900/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e8c4adce8e37e75c4215297d7745551b8dcfa5f728f23ce09bf4e678a9399413", size = 1891288, upload-time = "2025-06-13T14:07:11.099Z" }, - { url = "https://files.pythonhosted.org/packages/28/5d/54b0ef50d40d7c65a627f4a4b4127024ba9820f2af8acd933a4d30ae192e/pyzmq-27.0.0-cp310-cp310-win32.whl", hash = "sha256:5d5ef4718ecab24f785794e0e7536436698b459bfbc19a1650ef55280119d93b", size = 567936, upload-time = "2025-06-13T14:07:12.468Z" }, - { url = "https://files.pythonhosted.org/packages/18/ea/dedca4321de748ca48d3bcdb72274d4d54e8d84ea49088d3de174bd45d88/pyzmq-27.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e40609380480b3d12c30f841323f42451c755b8fece84235236f5fe5ffca8c1c", size = 628686, upload-time = "2025-06-13T14:07:14.051Z" }, - { url = "https://files.pythonhosted.org/packages/d4/a7/fcdeedc306e71e94ac262cba2d02337d885f5cdb7e8efced8e5ffe327808/pyzmq-27.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6b0397b0be277b46762956f576e04dc06ced265759e8c2ff41a0ee1aa0064198", size = 559039, upload-time = "2025-06-13T14:07:15.289Z" }, - { url = "https://files.pythonhosted.org/packages/44/df/84c630654106d9bd9339cdb564aa941ed41b023a0264251d6743766bb50e/pyzmq-27.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:21457825249b2a53834fa969c69713f8b5a79583689387a5e7aed880963ac564", size = 1332718, upload-time = "2025-06-13T14:07:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8e/f6a5461a07654d9840d256476434ae0ff08340bba562a455f231969772cb/pyzmq-27.0.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1958947983fef513e6e98eff9cb487b60bf14f588dc0e6bf35fa13751d2c8251", size = 908248, upload-time = "2025-06-13T14:07:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/7c/93/82863e8d695a9a3ae424b63662733ae204a295a2627d52af2f62c2cd8af9/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0dc628b5493f9a8cd9844b8bee9732ef587ab00002157c9329e4fc0ef4d3afa", size = 668647, upload-time = "2025-06-13T14:07:19.378Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/15278769b348121eacdbfcbd8c4d40f1102f32fa6af5be1ffc032ed684be/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7bbe9e1ed2c8d3da736a15694d87c12493e54cc9dc9790796f0321794bbc91f", size = 856600, upload-time = "2025-06-13T14:07:20.906Z" }, - { url = "https://files.pythonhosted.org/packages/d4/af/1c469b3d479bd095edb28e27f12eee10b8f00b356acbefa6aeb14dd295d1/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc1091f59143b471d19eb64f54bae4f54bcf2a466ffb66fe45d94d8d734eb495", size = 1657748, upload-time = "2025-06-13T14:07:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f4/17f965d0ee6380b1d6326da842a50e4b8b9699745161207945f3745e8cb5/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7011ade88c8e535cf140f8d1a59428676fbbce7c6e54fefce58bf117aefb6667", size = 2034311, upload-time = "2025-06-13T14:07:23.966Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6e/7c391d81fa3149fd759de45d298003de6cfab343fb03e92c099821c448db/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c386339d7e3f064213aede5d03d054b237937fbca6dd2197ac8cf3b25a6b14e", size = 1893630, upload-time = "2025-06-13T14:07:25.899Z" }, - { url = "https://files.pythonhosted.org/packages/0e/e0/eaffe7a86f60e556399e224229e7769b717f72fec0706b70ab2c03aa04cb/pyzmq-27.0.0-cp311-cp311-win32.whl", hash = "sha256:0546a720c1f407b2172cb04b6b094a78773491497e3644863cf5c96c42df8cff", size = 567706, upload-time = "2025-06-13T14:07:27.595Z" }, - { url = "https://files.pythonhosted.org/packages/c9/05/89354a8cffdcce6e547d48adaaf7be17007fc75572123ff4ca90a4ca04fc/pyzmq-27.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f39d50bd6c9091c67315ceb878a4f531957b121d2a05ebd077eb35ddc5efed", size = 630322, upload-time = "2025-06-13T14:07:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/4ab976d5e1e63976719389cc4f3bfd248a7f5f2bb2ebe727542363c61b5f/pyzmq-27.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c5817641eebb391a2268c27fecd4162448e03538387093cdbd8bf3510c316b38", size = 558435, upload-time = "2025-06-13T14:07:30.256Z" }, - { url = "https://files.pythonhosted.org/packages/93/a7/9ad68f55b8834ede477842214feba6a4c786d936c022a67625497aacf61d/pyzmq-27.0.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:cbabc59dcfaac66655c040dfcb8118f133fb5dde185e5fc152628354c1598e52", size = 1305438, upload-time = "2025-06-13T14:07:31.676Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ee/26aa0f98665a22bc90ebe12dced1de5f3eaca05363b717f6fb229b3421b3/pyzmq-27.0.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cb0ac5179cba4b2f94f1aa208fbb77b62c4c9bf24dd446278b8b602cf85fcda3", size = 895095, upload-time = "2025-06-13T14:07:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/cf/85/c57e7ab216ecd8aa4cc7e3b83b06cc4e9cf45c87b0afc095f10cd5ce87c1/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53a48f0228eab6cbf69fde3aa3c03cbe04e50e623ef92ae395fce47ef8a76152", size = 651826, upload-time = "2025-06-13T14:07:34.831Z" }, - { url = "https://files.pythonhosted.org/packages/69/9a/9ea7e230feda9400fb0ae0d61d7d6ddda635e718d941c44eeab22a179d34/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111db5f395e09f7e775f759d598f43cb815fc58e0147623c4816486e1a39dc22", size = 839750, upload-time = "2025-06-13T14:07:36.553Z" }, - { url = "https://files.pythonhosted.org/packages/08/66/4cebfbe71f3dfbd417011daca267539f62ed0fbc68105357b68bbb1a25b7/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c8878011653dcdc27cc2c57e04ff96f0471e797f5c19ac3d7813a245bcb24371", size = 1641357, upload-time = "2025-06-13T14:07:38.21Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f6/b0f62578c08d2471c791287149cb8c2aaea414ae98c6e995c7dbe008adfb/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0ed2c1f335ba55b5fdc964622254917d6b782311c50e138863eda409fbb3b6d", size = 2020281, upload-time = "2025-06-13T14:07:39.599Z" }, - { url = "https://files.pythonhosted.org/packages/37/b9/4f670b15c7498495da9159edc374ec09c88a86d9cd5a47d892f69df23450/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e918d70862d4cfd4b1c187310015646a14e1f5917922ab45b29f28f345eeb6be", size = 1877110, upload-time = "2025-06-13T14:07:41.027Z" }, - { url = "https://files.pythonhosted.org/packages/66/31/9dee25c226295b740609f0d46db2fe972b23b6f5cf786360980524a3ba92/pyzmq-27.0.0-cp312-abi3-win32.whl", hash = "sha256:88b4e43cab04c3c0f0d55df3b1eef62df2b629a1a369b5289a58f6fa8b07c4f4", size = 559297, upload-time = "2025-06-13T14:07:42.533Z" }, - { url = "https://files.pythonhosted.org/packages/9b/12/52da5509800f7ff2d287b2f2b4e636e7ea0f001181cba6964ff6c1537778/pyzmq-27.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:dce4199bf5f648a902ce37e7b3afa286f305cd2ef7a8b6ec907470ccb6c8b371", size = 619203, upload-time = "2025-06-13T14:07:43.843Z" }, - { url = "https://files.pythonhosted.org/packages/93/6d/7f2e53b19d1edb1eb4f09ec7c3a1f945ca0aac272099eab757d15699202b/pyzmq-27.0.0-cp312-abi3-win_arm64.whl", hash = "sha256:56e46bbb85d52c1072b3f809cc1ce77251d560bc036d3a312b96db1afe76db2e", size = 551927, upload-time = "2025-06-13T14:07:45.51Z" }, - { url = "https://files.pythonhosted.org/packages/19/62/876b27c4ff777db4ceba1c69ea90d3c825bb4f8d5e7cd987ce5802e33c55/pyzmq-27.0.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c36ad534c0c29b4afa088dc53543c525b23c0797e01b69fef59b1a9c0e38b688", size = 1340826, upload-time = "2025-06-13T14:07:46.881Z" }, - { url = "https://files.pythonhosted.org/packages/43/69/58ef8f4f59d3bcd505260c73bee87b008850f45edca40ddaba54273c35f4/pyzmq-27.0.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:67855c14173aec36395d7777aaba3cc527b393821f30143fd20b98e1ff31fd38", size = 897283, upload-time = "2025-06-13T14:07:49.562Z" }, - { url = "https://files.pythonhosted.org/packages/43/15/93a0d0396700a60475ad3c5d42c5f1c308d3570bc94626b86c71ef9953e0/pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8617c7d43cd8ccdb62aebe984bfed77ca8f036e6c3e46dd3dddda64b10f0ab7a", size = 660567, upload-time = "2025-06-13T14:07:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b3/fe055513e498ca32f64509abae19b9c9eb4d7c829e02bd8997dd51b029eb/pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67bfbcbd0a04c575e8103a6061d03e393d9f80ffdb9beb3189261e9e9bc5d5e9", size = 847681, upload-time = "2025-06-13T14:07:52.77Z" }, - { url = "https://files.pythonhosted.org/packages/b6/4f/ff15300b00b5b602191f3df06bbc8dd4164e805fdd65bb77ffbb9c5facdc/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5cd11d46d7b7e5958121b3eaf4cd8638eff3a720ec527692132f05a57f14341d", size = 1650148, upload-time = "2025-06-13T14:07:54.178Z" }, - { url = "https://files.pythonhosted.org/packages/c4/6f/84bdfff2a224a6f26a24249a342e5906993c50b0761e311e81b39aef52a7/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:b801c2e40c5aa6072c2f4876de8dccd100af6d9918d4d0d7aa54a1d982fd4f44", size = 2023768, upload-time = "2025-06-13T14:07:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/64/39/dc2db178c26a42228c5ac94a9cc595030458aa64c8d796a7727947afbf55/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20d5cb29e8c5f76a127c75b6e7a77e846bc4b655c373baa098c26a61b7ecd0ef", size = 1885199, upload-time = "2025-06-13T14:07:57.166Z" }, - { url = "https://files.pythonhosted.org/packages/c7/21/dae7b06a1f8cdee5d8e7a63d99c5d129c401acc40410bef2cbf42025e26f/pyzmq-27.0.0-cp313-cp313t-win32.whl", hash = "sha256:a20528da85c7ac7a19b7384e8c3f8fa707841fd85afc4ed56eda59d93e3d98ad", size = 575439, upload-time = "2025-06-13T14:07:58.959Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bc/1709dc55f0970cf4cb8259e435e6773f9946f41a045c2cb90e870b7072da/pyzmq-27.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d8229f2efece6a660ee211d74d91dbc2a76b95544d46c74c615e491900dc107f", size = 639933, upload-time = "2025-06-13T14:08:00.777Z" }, - { url = "https://files.pythonhosted.org/packages/09/6f/be6523a7f3821c0b5370912ef02822c028611360e0d206dd945bdbf9eaef/pyzmq-27.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:656c1866505a5735d0660b7da6d7147174bbf59d4975fc2b7f09f43c9bc25745", size = 835950, upload-time = "2025-06-13T14:08:35Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1e/a50fdd5c15018de07ab82a61bc460841be967ee7bbe7abee3b714d66f7ac/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74175b9e12779382432dd1d1f5960ebe7465d36649b98a06c6b26be24d173fab", size = 799876, upload-time = "2025-06-13T14:08:36.849Z" }, - { url = "https://files.pythonhosted.org/packages/88/a1/89eb5b71f5a504f8f887aceb8e1eb3626e00c00aa8085381cdff475440dc/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c6de908465697a8708e4d6843a1e884f567962fc61eb1706856545141d0cbb", size = 567400, upload-time = "2025-06-13T14:08:38.95Z" }, - { url = "https://files.pythonhosted.org/packages/56/aa/4571dbcff56cfb034bac73fde8294e123c975ce3eea89aff31bf6dc6382b/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c644aaacc01d0df5c7072826df45e67301f191c55f68d7b2916d83a9ddc1b551", size = 747031, upload-time = "2025-06-13T14:08:40.413Z" }, - { url = "https://files.pythonhosted.org/packages/46/e0/d25f30fe0991293c5b2f5ef3b070d35fa6d57c0c7428898c3ab4913d0297/pyzmq-27.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:10f70c1d9a446a85013a36871a296007f6fe4232b530aa254baf9da3f8328bc0", size = 544726, upload-time = "2025-06-13T14:08:41.997Z" }, - { url = "https://files.pythonhosted.org/packages/98/a6/92394373b8dbc1edc9d53c951e8d3989d518185174ee54492ec27711779d/pyzmq-27.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd1dc59763effd1576f8368047c9c31468fce0af89d76b5067641137506792ae", size = 835948, upload-time = "2025-06-13T14:08:43.516Z" }, - { url = "https://files.pythonhosted.org/packages/56/f3/4dc38d75d9995bfc18773df3e41f2a2ca9b740b06f1a15dbf404077e7588/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:60e8cc82d968174650c1860d7b716366caab9973787a1c060cf8043130f7d0f7", size = 799874, upload-time = "2025-06-13T14:08:45.017Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ba/64af397e0f421453dc68e31d5e0784d554bf39013a2de0872056e96e58af/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14fe7aaac86e4e93ea779a821967360c781d7ac5115b3f1a171ced77065a0174", size = 567400, upload-time = "2025-06-13T14:08:46.855Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/ec956cbe98809270b59a22891d5758edae147a258e658bf3024a8254c855/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ad0562d4e6abb785be3e4dd68599c41be821b521da38c402bc9ab2a8e7ebc7e", size = 747031, upload-time = "2025-06-13T14:08:48.419Z" }, - { url = "https://files.pythonhosted.org/packages/be/8a/4a3764a68abc02e2fbb0668d225b6fda5cd39586dd099cee8b2ed6ab0452/pyzmq-27.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9df43a2459cd3a3563404c1456b2c4c69564daa7dbaf15724c09821a3329ce46", size = 544726, upload-time = "2025-06-13T14:08:49.903Z" }, -] - -[[package]] -name = "rasterio" -version = "1.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "affine" }, - { name = "attrs" }, - { name = "certifi" }, - { name = "click" }, - { name = "click-plugins" }, - { name = "cligj" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pyparsing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/19/ab4326e419b543da623ce4191f68e3f36a4d9adc64f3df5c78f044d8d9ca/rasterio-1.4.3.tar.gz", hash = "sha256:201f05dbc7c4739dacb2c78a1cf4e09c0b7265b0a4d16ccbd1753ce4f2af350a", size = 442990, upload-time = "2024-12-02T14:49:25.571Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/1b/fbc6e3f11fe42898c787d27b6844f660bdd7081967d5f68b950c4bd9f043/rasterio-1.4.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:80f994b92e5dda78f13291710bd5c43efcfd164f69a8a2c20489115df9d178c8", size = 21539592, upload-time = "2024-12-02T14:48:24.761Z" }, - { url = "https://files.pythonhosted.org/packages/28/e6/97914b0c1824106ae9499446515915db3b4a8924d0568b6e888f4d305472/rasterio-1.4.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1a6e6ca9ec361599b48c9918ce25adb1a9203b8c8ca9b34ad78dccb3aef7945a", size = 18770366, upload-time = "2024-12-02T14:48:28.58Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f5/5cc3a8ee9deee2292432d69237ab4c5364f886844234d8e6dad29358aef0/rasterio-1.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b8a4311582274de2346450e5361d092b80b8b5c7b02fda6203402ba101ffabf", size = 22216305, upload-time = "2024-12-02T14:48:32.15Z" }, - { url = "https://files.pythonhosted.org/packages/51/2f/f72f77633aecba9afda903f9201c566520cc2dfeb0e1e0d36c102aa18189/rasterio-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:e79847a5a0e01399457a1e02d8c92040cb56729d054fe7796f0c17b246b18bf0", size = 25442319, upload-time = "2024-12-02T14:48:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fd/ba3850e4cbccc47d03037f2c96889f7f221a674a7be6665c7da7cd483a07/rasterio-1.4.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:9c30114d95ebba4ff49f078b3c193d29ff56d832588649400a3fa78f1dda1c96", size = 21540767, upload-time = "2024-12-02T14:48:38.794Z" }, - { url = "https://files.pythonhosted.org/packages/b3/59/ca86697161206233eea6353237b0c0f02f6f70434144db162f964a7e1b19/rasterio-1.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:812c854e7177064aeb58def2d59752887ad6b3d39ff3f858ed9df3f2ddc95613", size = 18762975, upload-time = "2024-12-02T14:48:41.895Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fa/723fa6a48a419b044146cd92fa6a66ead8532d96c352fbc2f2a1546cb5b6/rasterio-1.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54eef32d20a0dfbba59a8bb9828e562c3e9e97e2355b8dfe4a5274117976059f", size = 22204391, upload-time = "2024-12-02T14:48:44.447Z" }, - { url = "https://files.pythonhosted.org/packages/7e/1f/56462740694de764fde264051224fcbf800dad43cac92a66753153128866/rasterio-1.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:4009f7ce4e0883d8e5b400970daa3f1ca309caac8916d955722ee4486654d452", size = 25452478, upload-time = "2024-12-02T14:48:46.893Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f2/b7417292ceace70d815760f7e41fe5b0244ebff78ede11b1ffa9ca01c370/rasterio-1.4.3-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:e703e4b2c74c678786d5d110a3f30e26f3acfd65f09ccf35f69683a532f7a772", size = 21514543, upload-time = "2024-12-02T14:48:49.757Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ea/e21010457847b26bb4aea3983e9b44afbcefef07defc5e9a3285a8fe2f0c/rasterio-1.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:38a126f8dbf405cd3450b5bd10c6cc493a2e1be4cf83442d26f5e4f412372d36", size = 18735924, upload-time = "2024-12-02T14:48:53.263Z" }, - { url = "https://files.pythonhosted.org/packages/67/72/331727423b28fffdfd8bf18bdc55c18d374c0fefd2dde390cd833f8f4477/rasterio-1.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e90c2c300294265c16becc9822337ded0f01fb8664500b4d77890d633d8cd0e", size = 22251721, upload-time = "2024-12-02T14:48:56.533Z" }, - { url = "https://files.pythonhosted.org/packages/be/cc/453816b489af94b9a243eda889865973d518989ba6923b2381f6d6722b43/rasterio-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:a962ad4c29feaf38b1d7a94389313127de3646a5b9b734fbf9a04e16051a27ff", size = 25430154, upload-time = "2024-12-02T14:48:59.261Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e0/718c06b825d1f62077913e5bff1e70b71ac673718b135d55a0256d88d4ba/rasterio-1.4.3-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:5d4fcb635379b3d7b2f5e944c153849e3d27e93f35ad73ad4d3f0b8a580f0c8e", size = 21532284, upload-time = "2024-12-02T14:49:03.325Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a8/3b6b11923300d6835453d1157fabb518338067a67366c5c52e9df9a2314f/rasterio-1.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:98a9c89eade8c779e8ac1e525269faaa18c6b9818fc3c72cfc4627df71c66d0d", size = 18729960, upload-time = "2024-12-02T14:49:06.423Z" }, - { url = "https://files.pythonhosted.org/packages/05/19/94d6c66184c7d0f9374330c714f62c147dbb53eda9efdcc8fc6e2ac454c5/rasterio-1.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9bab1a0bb22b8bed1db34b5258db93d790ed4e61ef21ac055a7c6933c8d5e84", size = 22237518, upload-time = "2024-12-02T14:49:09.155Z" }, - { url = "https://files.pythonhosted.org/packages/df/88/9db5f49ebfdd9c12365e4cac76c34ccb1a642b1c8cbab4124b3c681495de/rasterio-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1839960e2f3057a6daa323ccf67b330f8f2f0dbd4a50cc7031e88e649301c5c0", size = 25424949, upload-time = "2024-12-02T14:49:11.742Z" }, -] - -[[package]] -name = "referencing" -version = "0.36.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, -] - -[[package]] -name = "requests" -version = "2.32.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, -] - -[[package]] -name = "requests-mock" -version = "1.12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/32/587625f91f9a0a3d84688bf9cfc4b2480a7e8ec327cefd0ff2ac891fd2cf/requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401", size = 60901, upload-time = "2024-03-29T03:54:29.446Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/ec/889fbc557727da0c34a33850950310240f2040f3b1955175fdb2b36a8910/requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", size = 27695, upload-time = "2024-03-29T03:54:27.64Z" }, -] - -[[package]] -name = "restructuredtext-lint" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45", size = 16723, upload-time = "2022-02-24T05:51:10.907Z" } - -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, -] - -[[package]] -name = "rfc3986-validator" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, -] - -[[package]] -name = "rfc3987-syntax" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lark" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, -] - -[[package]] -name = "rioxarray" -version = "0.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "pyproj", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "rasterio" }, - { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "xarray", version = "2025.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/8e/fe4e87460f8c62d8d5c683e09f19fbde5d9cfcfd0342d02df1f452999b5d/rioxarray-0.19.0.tar.gz", hash = "sha256:7819a0036fd874c8c8e280447cbbe43d8dc72fc4a14ac7852a665b1bdb7d4b04", size = 54600, upload-time = "2025-04-21T17:46:54.183Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2f/63d2cacc0e525f8e3398bcf32bd3620385f22cd1600834ec49d7f3597a7b/rioxarray-0.19.0-py3-none-any.whl", hash = "sha256:494ee4fff1781072d55ee5276f5d07b63d93b05093cb33b926a12186ba5bb8ef", size = 62151, upload-time = "2025-04-21T17:46:52.801Z" }, -] - -[[package]] -name = "rpds-py" -version = "0.26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/31/1459645f036c3dfeacef89e8e5825e430c77dde8489f3b99eaafcd4a60f5/rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37", size = 372466, upload-time = "2025-07-01T15:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ff/3d0727f35836cc8773d3eeb9a46c40cc405854e36a8d2e951f3a8391c976/rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0", size = 357825, upload-time = "2025-07-01T15:53:42.247Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ce/badc5e06120a54099ae287fa96d82cbb650a5f85cf247ffe19c7b157fd1f/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd", size = 381530, upload-time = "2025-07-01T15:53:43.585Z" }, - { url = "https://files.pythonhosted.org/packages/1e/a5/fa5d96a66c95d06c62d7a30707b6a4cfec696ab8ae280ee7be14e961e118/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79", size = 396933, upload-time = "2025-07-01T15:53:45.78Z" }, - { url = "https://files.pythonhosted.org/packages/00/a7/7049d66750f18605c591a9db47d4a059e112a0c9ff8de8daf8fa0f446bba/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3", size = 513973, upload-time = "2025-07-01T15:53:47.085Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f1/528d02c7d6b29d29fac8fd784b354d3571cc2153f33f842599ef0cf20dd2/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf", size = 402293, upload-time = "2025-07-01T15:53:48.117Z" }, - { url = "https://files.pythonhosted.org/packages/15/93/fde36cd6e4685df2cd08508f6c45a841e82f5bb98c8d5ecf05649522acb5/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc", size = 383787, upload-time = "2025-07-01T15:53:50.874Z" }, - { url = "https://files.pythonhosted.org/packages/69/f2/5007553aaba1dcae5d663143683c3dfd03d9395289f495f0aebc93e90f24/rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19", size = 416312, upload-time = "2025-07-01T15:53:52.046Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a7/ce52c75c1e624a79e48a69e611f1c08844564e44c85db2b6f711d76d10ce/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11", size = 558403, upload-time = "2025-07-01T15:53:53.192Z" }, - { url = "https://files.pythonhosted.org/packages/79/d5/e119db99341cc75b538bf4cb80504129fa22ce216672fb2c28e4a101f4d9/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f", size = 588323, upload-time = "2025-07-01T15:53:54.336Z" }, - { url = "https://files.pythonhosted.org/packages/93/94/d28272a0b02f5fe24c78c20e13bbcb95f03dc1451b68e7830ca040c60bd6/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323", size = 554541, upload-time = "2025-07-01T15:53:55.469Z" }, - { url = "https://files.pythonhosted.org/packages/93/e0/8c41166602f1b791da892d976057eba30685486d2e2c061ce234679c922b/rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45", size = 220442, upload-time = "2025-07-01T15:53:56.524Z" }, - { url = "https://files.pythonhosted.org/packages/87/f0/509736bb752a7ab50fb0270c2a4134d671a7b3038030837e5536c3de0e0b/rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84", size = 231314, upload-time = "2025-07-01T15:53:57.842Z" }, - { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610, upload-time = "2025-07-01T15:53:58.844Z" }, - { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032, upload-time = "2025-07-01T15:53:59.985Z" }, - { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525, upload-time = "2025-07-01T15:54:01.162Z" }, - { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089, upload-time = "2025-07-01T15:54:02.319Z" }, - { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255, upload-time = "2025-07-01T15:54:03.38Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283, upload-time = "2025-07-01T15:54:04.923Z" }, - { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881, upload-time = "2025-07-01T15:54:06.482Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822, upload-time = "2025-07-01T15:54:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347, upload-time = "2025-07-01T15:54:08.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956, upload-time = "2025-07-01T15:54:09.963Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363, upload-time = "2025-07-01T15:54:11.073Z" }, - { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123, upload-time = "2025-07-01T15:54:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732, upload-time = "2025-07-01T15:54:13.434Z" }, - { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917, upload-time = "2025-07-01T15:54:14.559Z" }, - { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" }, - { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" }, - { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" }, - { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" }, - { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" }, - { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" }, - { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" }, - { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" }, - { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" }, - { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214, upload-time = "2025-07-01T15:54:37.469Z" }, - { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113, upload-time = "2025-07-01T15:54:38.954Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189, upload-time = "2025-07-01T15:54:40.57Z" }, - { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998, upload-time = "2025-07-01T15:54:43.025Z" }, - { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903, upload-time = "2025-07-01T15:54:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785, upload-time = "2025-07-01T15:54:46.043Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329, upload-time = "2025-07-01T15:54:47.64Z" }, - { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875, upload-time = "2025-07-01T15:54:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636, upload-time = "2025-07-01T15:54:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663, upload-time = "2025-07-01T15:54:52.023Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428, upload-time = "2025-07-01T15:54:53.692Z" }, - { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571, upload-time = "2025-07-01T15:54:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" }, - { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415, upload-time = "2025-07-01T15:54:59.751Z" }, - { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783, upload-time = "2025-07-01T15:55:00.898Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844, upload-time = "2025-07-01T15:55:02.201Z" }, - { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105, upload-time = "2025-07-01T15:55:03.698Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440, upload-time = "2025-07-01T15:55:05.398Z" }, - { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759, upload-time = "2025-07-01T15:55:08.316Z" }, - { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032, upload-time = "2025-07-01T15:55:09.52Z" }, - { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416, upload-time = "2025-07-01T15:55:11.216Z" }, - { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, - { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" }, - { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" }, - { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" }, - { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" }, - { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" }, - { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" }, - { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" }, - { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" }, - { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" }, - { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, - { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" }, - { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" }, - { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" }, - { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" }, - { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" }, - { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" }, - { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" }, - { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" }, - { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, - { url = "https://files.pythonhosted.org/packages/ef/9a/1f033b0b31253d03d785b0cd905bc127e555ab496ea6b4c7c2e1f951f2fd/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958", size = 373226, upload-time = "2025-07-01T15:56:16.578Z" }, - { url = "https://files.pythonhosted.org/packages/58/29/5f88023fd6aaaa8ca3c4a6357ebb23f6f07da6079093ccf27c99efce87db/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e", size = 359230, upload-time = "2025-07-01T15:56:17.978Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6c/13eaebd28b439da6964dde22712b52e53fe2824af0223b8e403249d10405/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08", size = 382363, upload-time = "2025-07-01T15:56:19.977Z" }, - { url = "https://files.pythonhosted.org/packages/55/fc/3bb9c486b06da19448646f96147796de23c5811ef77cbfc26f17307b6a9d/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6", size = 397146, upload-time = "2025-07-01T15:56:21.39Z" }, - { url = "https://files.pythonhosted.org/packages/15/18/9d1b79eb4d18e64ba8bba9e7dec6f9d6920b639f22f07ee9368ca35d4673/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871", size = 514804, upload-time = "2025-07-01T15:56:22.78Z" }, - { url = "https://files.pythonhosted.org/packages/4f/5a/175ad7191bdbcd28785204621b225ad70e85cdfd1e09cc414cb554633b21/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4", size = 402820, upload-time = "2025-07-01T15:56:24.584Z" }, - { url = "https://files.pythonhosted.org/packages/11/45/6a67ecf6d61c4d4aff4bc056e864eec4b2447787e11d1c2c9a0242c6e92a/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f", size = 384567, upload-time = "2025-07-01T15:56:26.064Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ba/16589da828732b46454c61858950a78fe4c931ea4bf95f17432ffe64b241/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73", size = 416520, upload-time = "2025-07-01T15:56:27.608Z" }, - { url = "https://files.pythonhosted.org/packages/81/4b/00092999fc7c0c266045e984d56b7314734cc400a6c6dc4d61a35f135a9d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f", size = 559362, upload-time = "2025-07-01T15:56:29.078Z" }, - { url = "https://files.pythonhosted.org/packages/96/0c/43737053cde1f93ac4945157f7be1428724ab943e2132a0d235a7e161d4e/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84", size = 588113, upload-time = "2025-07-01T15:56:30.485Z" }, - { url = "https://files.pythonhosted.org/packages/46/46/8e38f6161466e60a997ed7e9951ae5de131dedc3cf778ad35994b4af823d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b", size = 555429, upload-time = "2025-07-01T15:56:31.956Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ac/65da605e9f1dd643ebe615d5bbd11b6efa1d69644fc4bf623ea5ae385a82/rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8", size = 231950, upload-time = "2025-07-01T15:56:33.337Z" }, - { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505, upload-time = "2025-07-01T15:56:34.716Z" }, - { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468, upload-time = "2025-07-01T15:56:36.219Z" }, - { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680, upload-time = "2025-07-01T15:56:37.644Z" }, - { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035, upload-time = "2025-07-01T15:56:39.241Z" }, - { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922, upload-time = "2025-07-01T15:56:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822, upload-time = "2025-07-01T15:56:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336, upload-time = "2025-07-01T15:56:44.239Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871, upload-time = "2025-07-01T15:56:46.284Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" }, - { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, + { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, + { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, + { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, + { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" }, + { url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" }, + { url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" }, + { url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" }, + { url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" }, + { url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" }, + { url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" }, + { url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" }, + { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" }, + { url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" }, + { url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" }, + { url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" }, + { url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" }, + { url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" }, + { url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" }, + { url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" }, + { url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" }, + { url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" }, + { url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" }, + { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, ] [[package]] -name = "ruff" -version = "0.12.5" +name = "nodejs-wheel-binaries" +version = "24.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/35/d806c2ca66072e36dc340ccdbeb2af7e4f1b5bcc33f1481f00ceed476708/nodejs_wheel_binaries-24.12.0.tar.gz", hash = "sha256:f1b50aa25375e264697dec04b232474906b997c2630c8f499f4caf3692938435", size = 8058, upload-time = "2025-12-11T21:12:26.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" }, - { url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" }, - { url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" }, - { url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" }, - { url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" }, - { url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" }, - { url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" }, - { url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" }, - { url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3b/9d6f044319cd5b1e98f07c41e2465b58cadc1c9c04a74c891578f3be6cb5/nodejs_wheel_binaries-24.12.0-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:7564ddea0a87eff34e9b3ef71764cc2a476a8f09a5cccfddc4691148b0a47338", size = 55125859, upload-time = "2025-12-11T21:11:58.132Z" }, + { url = "https://files.pythonhosted.org/packages/48/a5/f5722bf15c014e2f476d7c76bce3d55c341d19122d8a5d86454db32a61a4/nodejs_wheel_binaries-24.12.0-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:8ff929c4669e64613ceb07f5bbd758d528c3563820c75d5de3249eb452c0c0ab", size = 55309035, upload-time = "2025-12-11T21:12:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/a9/61/68d39a6f1b5df67805969fd2829ba7e80696c9af19537856ec912050a2be/nodejs_wheel_binaries-24.12.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6ebacefa8891bc456ad3655e6bce0af7e20ba08662f79d9109986faeb703fd6f", size = 59661017, upload-time = "2025-12-11T21:12:05.268Z" }, + { url = "https://files.pythonhosted.org/packages/16/a1/31aad16f55a5e44ca7ea62d1367fc69f4b6e1dba67f58a0a41d0ed854540/nodejs_wheel_binaries-24.12.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:3292649a03682ccbfa47f7b04d3e4240e8c46ef04dc941b708f20e4e6a764f75", size = 60159770, upload-time = "2025-12-11T21:12:08.696Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5e/b7c569aa1862690ca4d4daf3a64cafa1ea6ce667a9e3ae3918c56e127d9b/nodejs_wheel_binaries-24.12.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7fb83df312955ea355ba7f8cbd7055c477249a131d3cb43b60e4aeb8f8c730b1", size = 61653561, upload-time = "2025-12-11T21:12:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/71/87/567f58d7ba69ff0208be849b37be0f2c2e99c69e49334edd45ff44f00043/nodejs_wheel_binaries-24.12.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2473c819448fedd7b036dde236b09f3c8bbf39fbbd0c1068790a0498800f498b", size = 62238331, upload-time = "2025-12-11T21:12:16.143Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9d/c6492188ce8de90093c6755a4a63bb6b2b4efb17094cb4f9a9a49c73ed3b/nodejs_wheel_binaries-24.12.0-py2.py3-none-win_amd64.whl", hash = "sha256:2090d59f75a68079fabc9b86b14df8238b9aecb9577966dc142ce2a23a32e9bb", size = 41342076, upload-time = "2025-12-11T21:12:20.618Z" }, + { url = "https://files.pythonhosted.org/packages/df/af/cd3290a647df567645353feed451ef4feaf5844496ced69c4dcb84295ff4/nodejs_wheel_binaries-24.12.0-py2.py3-none-win_arm64.whl", hash = "sha256:d0c2273b667dd7e3f55e369c0085957b702144b1b04bfceb7ce2411e58333757", size = 39048104, upload-time = "2025-12-11T21:12:23.495Z" }, ] [[package]] -name = "s3transfer" -version = "0.13.1" +name = "obspec" +version = "0.1.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589, upload-time = "2025-07-18T19:22:42.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/94/7a9ad6927cac6ec7680e11772fb692145a05a93bafd80b84f6f0ef12f4e7/obspec-0.1.0.tar.gz", hash = "sha256:b189781a53f82ef8d6abf0c9e77fd4c46ac9f244d5a91eb35ee61c2e2b204a4a", size = 117254, upload-time = "2025-06-25T05:24:00.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, + { url = "https://files.pythonhosted.org/packages/bb/69/96feeac84ce0b871567225c78515f3b557c023e72ed9b4f1833f3662bd6b/obspec-0.1.0-py3-none-any.whl", hash = "sha256:307f0fa2c2998b324ecf0eed6a2a89049a4c40c9b1fa2b5e1af28f0ee72136b3", size = 15231, upload-time = "2025-06-25T05:23:58.735Z" }, ] [[package]] -name = "send2trash" -version = "1.8.3" +name = "obstore" +version = "0.8.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/9ec984edd0f3b72226adfaa19b1c61b15823b35b52f311ca4af36d009d15/obstore-0.8.2.tar.gz", hash = "sha256:a467bc4e97169e2ba749981b4fd0936015428d9b8f3fb83a5528536b1b6f377f", size = 168852, upload-time = "2025-09-16T15:34:55.786Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/dc/60fefbb5736e69eab56657bca04ca64dc07fdeccb3814164a31b62ad066b/obstore-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bb70ce297a47392b1d9a3e310f18d59cd5ebbb9453428210fef02ed60e4d75d1", size = 3612955, upload-time = "2025-09-16T15:33:29.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/844e8f382e5a12b8a3796a05d76a03e12c7aedc13d6900419e39207d7868/obstore-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1619bf618428abf1f607e0b219b2e230a966dcf697b717deccfa0983dd91f646", size = 3346564, upload-time = "2025-09-16T15:33:30.698Z" }, + { url = "https://files.pythonhosted.org/packages/89/73/8537f99e09a38a54a6a15ede907aa25d4da089f767a808f0b2edd9c03cec/obstore-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4605c3ed7c9515aeb4c619b5f7f2c9986ed4a79fe6045e536b5e59b804b1476", size = 3460809, upload-time = "2025-09-16T15:33:31.837Z" }, + { url = "https://files.pythonhosted.org/packages/b4/99/7714dec721e43f521d6325a82303a002cddad089437640f92542b84e9cc8/obstore-0.8.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce42670417876dd8668cbb8659e860e9725e5f26bbc86449fd259970e2dd9d18", size = 3692081, upload-time = "2025-09-16T15:33:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/4ac4175fe95a24c220a96021c25c432bcc0c0212f618be0737184eebbaad/obstore-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a3e893b2a06585f651c541c1972fe1e3bf999ae2a5fda052ee55eb7e6516f5", size = 3957466, upload-time = "2025-09-16T15:33:34.528Z" }, + { url = "https://files.pythonhosted.org/packages/4e/04/caa288fb735484fc5cb019bdf3d896eaccfae0ac4622e520d05692c46790/obstore-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08462b32f95a9948ed56ed63e88406e2e5a4cae1fde198f9682e0fb8487100ed", size = 3951293, upload-time = "2025-09-16T15:33:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/44/2f/d380239da2d6a1fda82e17df5dae600a404e8a93a065784518ff8325d5f6/obstore-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0bf7763292a8fc47d01cd66e6f19002c5c6ad4b3ed4e6b2729f5e190fa8a0d", size = 3766199, upload-time = "2025-09-16T15:33:36.904Z" }, + { url = "https://files.pythonhosted.org/packages/28/41/d391be069d3da82969b54266948b2582aeca5dd735abeda4d63dba36e07b/obstore-0.8.2-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:bcd47f8126cb192cbe86942b8f73b1c45a651ce7e14c9a82c5641dfbf8be7603", size = 3529678, upload-time = "2025-09-16T15:33:38.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/4c/4862fdd1a3abde459ee8eea699b1797df638a460af235b18ca82c8fffb72/obstore-0.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57eda9fd8c757c3b4fe36cf3918d7e589cc1286591295cc10b34122fa36dd3fd", size = 3698079, upload-time = "2025-09-16T15:33:39.696Z" }, + { url = "https://files.pythonhosted.org/packages/68/ca/014e747bc53b570059c27e3565b2316fbe5c107d4134551f4cd3e24aa667/obstore-0.8.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ea44442aad8992166baa69f5069750979e4c5d9ffce772e61565945eea5774b9", size = 3687154, upload-time = "2025-09-16T15:33:40.92Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/6db5f8edd93028e5b8bfbeee15e6bd3e56f72106107d31cb208b57659de4/obstore-0.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41496a3ab8527402db4142aaaf0d42df9d7d354b13ba10d9c33e0e48dd49dd96", size = 3773444, upload-time = "2025-09-16T15:33:42.123Z" }, + { url = "https://files.pythonhosted.org/packages/26/e5/c9e2cc540689c873beb61246e1615d6e38301e6a34dec424f5a5c63c1afd/obstore-0.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43da209803f052df96c7c3cbec512d310982efd2407e4a435632841a51143170", size = 3939315, upload-time = "2025-09-16T15:33:43.252Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c9/bb53280ca50103c1ffda373cdc9b0f835431060039c2897cbc87ddd92e42/obstore-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:1836f5dcd49f9f2950c75889ab5c51fb290d3ea93cdc39a514541e0be3af016e", size = 3978234, upload-time = "2025-09-16T15:33:44.393Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5d/8c3316cc958d386d5e6ab03e9db9ddc27f8e2141cee4a6777ae5b92f3aac/obstore-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:212f033e53fe6e53d64957923c5c88949a400e9027f7038c705ec2e9038be563", size = 3612027, upload-time = "2025-09-16T15:33:45.6Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4d/699359774ce6330130536d008bfc32827fab0c25a00238d015a5974a3d1d/obstore-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bee21fa4ba148d08fa90e47a96df11161661ed31e09c056a373cb2154b0f2852", size = 3344686, upload-time = "2025-09-16T15:33:47.185Z" }, + { url = "https://files.pythonhosted.org/packages/82/37/55437341f10512906e02fd9fa69a8a95ad3f2f6a916d3233fda01763d110/obstore-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c66594b59832ff1ced4c72575d9beb8b5f9b4e404ac1150a42bfb226617fd50", size = 3459860, upload-time = "2025-09-16T15:33:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/7a/51/4245a616c94ee4851965e33f7a563ab4090cc81f52cc73227ff9ceca2e46/obstore-0.8.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:089f33af5c2fe132d00214a0c1f40601b28f23a38e24ef9f79fb0576f2730b74", size = 3691648, upload-time = "2025-09-16T15:33:49.524Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/4e2fb24171e3ca3641a4653f006be826e7e17634b11688a5190553b00b83/obstore-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87f658dfd340d5d9ea2d86a7c90d44da77a0db9e00c034367dca335735110cf", size = 3956867, upload-time = "2025-09-16T15:33:51.082Z" }, + { url = "https://files.pythonhosted.org/packages/42/f5/b703115361c798c9c1744e1e700d5908d904a8c2e2bd38bec759c9ffb469/obstore-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2e4fa92828c4fbc2d487f3da2d3588701a1b67d9f6ca3c97cc2afc912e9c63", size = 3950599, upload-time = "2025-09-16T15:33:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/53/20/08c6dc0f20c1394e2324b9344838e4e7af770cdcb52c30757a475f50daeb/obstore-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab440e89c5c37a8ec230857dd65147d4b923e0cada33297135d05e0f937d696a", size = 3765865, upload-time = "2025-09-16T15:33:53.291Z" }, + { url = "https://files.pythonhosted.org/packages/77/20/77907765e29b2eba6bd8821872284d91170d7084f670855b2dfcb249ea14/obstore-0.8.2-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:b9beed107c5c9cd995d4a73263861fcfbc414d58773ed65c14f80eb18258a932", size = 3529807, upload-time = "2025-09-16T15:33:54.535Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/f629d39cc30d050f52b1bf927e4d65c1cc7d7ffbb8a635cd546b5c5219a0/obstore-0.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b75b4e7746292c785e31edcd5aadc8b758238372a19d4c5e394db5c305d7d175", size = 3693629, upload-time = "2025-09-16T15:33:56.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/ff/106763fd10f2a1cb47f2ef1162293c78ad52f4e73223d8d43fc6b755445d/obstore-0.8.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f33e6c366869d05ab0b7f12efe63269e631c5450d95d6b4ba4c5faf63f69de70", size = 3686176, upload-time = "2025-09-16T15:33:57.247Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/d2ccb6f32feeca906d5a7c4255340df5262af8838441ca06c9e4e37b67d5/obstore-0.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:12c885a9ce5ceb09d13cc186586c0c10b62597eff21b985f6ce8ff9dab963ad3", size = 3773081, upload-time = "2025-09-16T15:33:58.475Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/40d1cc504cefc89c9b3dd8874287f3fddc7d963a8748d6dffc5880222013/obstore-0.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4accc883b93349a81c9931e15dd318cc703b02bbef2805d964724c73d006d00e", size = 3938589, upload-time = "2025-09-16T15:33:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/916c6777222db3271e9fb3cf9a97ed92b3a9b3e465bdeec96de9ab809d53/obstore-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ec850adf9980e5788a826ccfd5819989724e2a2f712bfa3258e85966c8d9981e", size = 3977768, upload-time = "2025-09-16T15:34:01.25Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/66f8dc98bbf5613bbfe5bf21747b4c8091442977f4bd897945895ab7325c/obstore-0.8.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1431e40e9bb4773a261e51b192ea6489d0799b9d4d7dbdf175cdf813eb8c0503", size = 3623364, upload-time = "2025-09-16T15:34:02.957Z" }, + { url = "https://files.pythonhosted.org/packages/1a/66/6d527b3027e42f625c8fc816ac7d19b0d6228f95bfe7666e4d6b081d2348/obstore-0.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ddb39d4da303f50b959da000aa42734f6da7ac0cc0be2d5a7838b62c97055bb9", size = 3347764, upload-time = "2025-09-16T15:34:04.236Z" }, + { url = "https://files.pythonhosted.org/packages/0d/79/c00103302b620192ea447a948921ad3fed031ce3d19e989f038e1183f607/obstore-0.8.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e01f4e13783db453e17e005a4a3ceff09c41c262e44649ba169d253098c775e8", size = 3460981, upload-time = "2025-09-16T15:34:05.595Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d9/bfe4ed4b1aebc45b56644dd5b943cf8e1673505cccb352e66878a457e807/obstore-0.8.2-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0fc2d0bc17caff9b538564ddc26d7616f7e8b7c65b1a3c90b5048a8ad2e797", size = 3692711, upload-time = "2025-09-16T15:34:06.796Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/cd6c2cbb18e1f40c77e7957a4a03d2d83f1859a2e876a408f1ece81cad4c/obstore-0.8.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e439d06c99a140348f046c9f598ee349cc2dcd9105c15540a4b231f9cc48bbae", size = 3958362, upload-time = "2025-09-16T15:34:08.277Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ea/5ee82bf23abd71c7d6a3f2d008197ae8f8f569d41314c26a8f75318245be/obstore-0.8.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e37d9046669fcc59522d0faf1d105fcbfd09c84cccaaa1e809227d8e030f32c", size = 3957082, upload-time = "2025-09-16T15:34:09.477Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ee/46650405e50fdaa8d95f30375491f9c91fac9517980e8a28a4a6af66927f/obstore-0.8.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2646fdcc4bbe92dc2bb5bcdff15574da1211f5806c002b66d514cee2a23c7cb8", size = 3775539, upload-time = "2025-09-16T15:34:10.726Z" }, + { url = "https://files.pythonhosted.org/packages/35/d6/348a7ebebe2ca3d94dfc75344ea19675ae45472823e372c1852844078307/obstore-0.8.2-cp314-cp314-manylinux_2_24_aarch64.whl", hash = "sha256:e31a7d37675056d93dfc244605089dee67f5bba30f37c88436623c8c5ad9ba9d", size = 3535048, upload-time = "2025-09-16T15:34:12.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/b7a16cc0da91a4b902d47880ad24016abfe7880c63f7cdafda45d89a2f91/obstore-0.8.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:656313dd8170dde0f0cd471433283337a63912e8e790a121f7cc7639c83e3816", size = 3699035, upload-time = "2025-09-16T15:34:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/3269a3a58347e0b019742d888612c4b765293c9c75efa44e144b1e884c0d/obstore-0.8.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329038c9645d6d1741e77fe1a53e28a14b1a5c1461cfe4086082ad39ebabf981", size = 3687307, upload-time = "2025-09-16T15:34:14.501Z" }, + { url = "https://files.pythonhosted.org/packages/01/f9/4fd4819ad6a49d2f462a45be453561f4caebded0dc40112deeffc34b89b1/obstore-0.8.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1e4df99b369790c97c752d126b286dc86484ea49bff5782843a265221406566f", size = 3776076, upload-time = "2025-09-16T15:34:16.207Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/7c4f958fa0b9fc4778fb3d232e38b37db8c6b260f641022fbba48b049d7e/obstore-0.8.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9e1c65c65e20cc990414a8a9af88209b1bbc0dd9521b5f6b0293c60e19439bb7", size = 3947445, upload-time = "2025-09-16T15:34:17.423Z" }, ] [[package]] -name = "setuptools" -version = "80.9.0" +name = "packaging" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] -name = "shapely" -version = "2.1.1" +name = "paginate" +version = "0.5.7" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/fa/f18025c95b86116dd8f1ec58cab078bd59ab51456b448136ca27463be533/shapely-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8ccc872a632acb7bdcb69e5e78df27213f7efd195882668ffba5405497337c6", size = 1825117, upload-time = "2025-05-19T11:03:43.547Z" }, - { url = "https://files.pythonhosted.org/packages/c7/65/46b519555ee9fb851234288be7c78be11e6260995281071d13abf2c313d0/shapely-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f24f2ecda1e6c091da64bcbef8dd121380948074875bd1b247b3d17e99407099", size = 1628541, upload-time = "2025-05-19T11:03:45.162Z" }, - { url = "https://files.pythonhosted.org/packages/29/51/0b158a261df94e33505eadfe737db9531f346dfa60850945ad25fd4162f1/shapely-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45112a5be0b745b49e50f8829ce490eb67fefb0cea8d4f8ac5764bfedaa83d2d", size = 2948453, upload-time = "2025-05-19T11:03:46.681Z" }, - { url = "https://files.pythonhosted.org/packages/a9/4f/6c9bb4bd7b1a14d7051641b9b479ad2a643d5cbc382bcf5bd52fd0896974/shapely-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c10ce6f11904d65e9bbb3e41e774903c944e20b3f0b282559885302f52f224a", size = 3057029, upload-time = "2025-05-19T11:03:48.346Z" }, - { url = "https://files.pythonhosted.org/packages/89/0b/ad1b0af491d753a83ea93138eee12a4597f763ae12727968d05934fe7c78/shapely-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:61168010dfe4e45f956ffbbaf080c88afce199ea81eb1f0ac43230065df320bd", size = 3894342, upload-time = "2025-05-19T11:03:49.602Z" }, - { url = "https://files.pythonhosted.org/packages/7d/96/73232c5de0b9fdf0ec7ddfc95c43aaf928740e87d9f168bff0e928d78c6d/shapely-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cacf067cdff741cd5c56a21c52f54ece4e4dad9d311130493a791997da4a886b", size = 4056766, upload-time = "2025-05-19T11:03:51.252Z" }, - { url = "https://files.pythonhosted.org/packages/43/cc/eec3c01f754f5b3e0c47574b198f9deb70465579ad0dad0e1cef2ce9e103/shapely-2.1.1-cp310-cp310-win32.whl", hash = "sha256:23b8772c3b815e7790fb2eab75a0b3951f435bc0fce7bb146cb064f17d35ab4f", size = 1523744, upload-time = "2025-05-19T11:03:52.624Z" }, - { url = "https://files.pythonhosted.org/packages/50/fc/a7187e6dadb10b91e66a9e715d28105cde6489e1017cce476876185a43da/shapely-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c7b2b6143abf4fa77851cef8ef690e03feade9a0d48acd6dc41d9e0e78d7ca6", size = 1703061, upload-time = "2025-05-19T11:03:54.695Z" }, - { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368, upload-time = "2025-05-19T11:03:55.937Z" }, - { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362, upload-time = "2025-05-19T11:03:57.06Z" }, - { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005, upload-time = "2025-05-19T11:03:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489, upload-time = "2025-05-19T11:04:00.059Z" }, - { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727, upload-time = "2025-05-19T11:04:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311, upload-time = "2025-05-19T11:04:03.134Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982, upload-time = "2025-05-19T11:04:05.217Z" }, - { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872, upload-time = "2025-05-19T11:04:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021, upload-time = "2025-05-19T11:04:08.022Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018, upload-time = "2025-05-19T11:04:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417, upload-time = "2025-05-19T11:04:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224, upload-time = "2025-05-19T11:04:11.903Z" }, - { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982, upload-time = "2025-05-19T11:04:13.224Z" }, - { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122, upload-time = "2025-05-19T11:04:14.477Z" }, - { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437, upload-time = "2025-05-19T11:04:16.203Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479, upload-time = "2025-05-19T11:04:18.497Z" }, - { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107, upload-time = "2025-05-19T11:04:19.736Z" }, - { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355, upload-time = "2025-05-19T11:04:21.035Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871, upload-time = "2025-05-19T11:04:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830, upload-time = "2025-05-19T11:04:23.997Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961, upload-time = "2025-05-19T11:04:25.702Z" }, - { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623, upload-time = "2025-05-19T11:04:27.171Z" }, - { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916, upload-time = "2025-05-19T11:04:28.405Z" }, - { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746, upload-time = "2025-05-19T11:04:29.643Z" }, - { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482, upload-time = "2025-05-19T11:04:30.852Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256, upload-time = "2025-05-19T11:04:32.068Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614, upload-time = "2025-05-19T11:04:33.7Z" }, - { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542, upload-time = "2025-05-19T11:04:34.952Z" }, - { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961, upload-time = "2025-05-19T11:04:36.32Z" }, - { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514, upload-time = "2025-05-19T11:04:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607, upload-time = "2025-05-19T11:04:38.925Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] [[package]] -name = "six" -version = "1.17.0" +name = "pathspec" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] -name = "sniffio" -version = "1.3.1" +name = "platformdirs" +version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] -name = "snowballstemmer" -version = "3.0.1" +name = "pluggy" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] -name = "soupsieve" -version = "2.7" +name = "propcache" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, ] [[package]] -name = "sphinx" -version = "8.1.3" +name = "pygments" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] -name = "sphinx-autobuild" -version = "2024.10.3" +name = "pymdown-extensions" +version = "10.20" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama" }, - { name = "sphinx" }, - { name = "starlette" }, - { name = "uvicorn" }, - { name = "watchfiles" }, - { name = "websockets" }, + { name = "markdown" }, + { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" }, + { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" }, ] [[package]] -name = "sphinx-design" -version = "0.6.1" +name = "pympler" +version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/37/c384631908029676d8e7213dd956bb686af303a80db7afbc9be36bc49495/pympler-1.1.tar.gz", hash = "sha256:1eaa867cb8992c218430f1708fdaccda53df064144d1c5656b1e6f1ee6000424", size = 179954, upload-time = "2024-06-28T19:56:06.563Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/a6a2e2b202d7fd97eadfe90979845b8706676b41cbd3b42ba75adf329d1f/Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506", size = 165766, upload-time = "2024-06-28T19:56:05.087Z" }, ] [[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" +name = "pyproject-hooks" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] [[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +name = "pystac" +source = { editable = "." } +dependencies = [ + { name = "python-dateutil" }, ] -[[package]] -name = "sphinxcontrib-fulltoc" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/d1297db9b75650681e5429e92e13df139ee6b64303ff1b2eea4ebd32c0a9/sphinxcontrib-fulltoc-1.2.0.tar.gz", hash = "sha256:c845d62fc467f3135d4543e9f10e13ef91852683bd1c90fd19d07f9d36757cd9", size = 13752, upload-time = "2017-04-10T14:12:57.75Z" } - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +[package.optional-dependencies] +jinja2 = [ + { name = "jinja2" }, ] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +obstore = [ + { name = "obspec" }, + { name = "obstore" }, ] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +validation = [ + { name = "jsonschema" }, + { name = "referencing" }, ] -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +[package.dev-dependencies] +bench = [ + { name = "asv" }, ] - -[[package]] -name = "stack-data" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, +dev = [ + { name = "basedpyright" }, + { name = "html5lib" }, + { name = "jsonschema" }, + { name = "pytest" }, + { name = "pytest-mock" }, + { name = "pytest-recording" }, + { name = "requests-mock" }, + { name = "ruff" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +docs = [ + { name = "mkdocs-material" }, + { name = "mkdocstrings-python" }, ] -[[package]] -name = "starlette" -version = "0.50.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, +[package.metadata] +requires-dist = [ + { name = "jinja2", marker = "extra == 'jinja2'", specifier = ">=3.1.6" }, + { name = "jsonschema", marker = "extra == 'validation'", specifier = ">=4.25.1" }, + { name = "obspec", marker = "extra == 'obstore'", specifier = ">=0.1.0" }, + { name = "obstore", marker = "extra == 'obstore'", specifier = ">=0.8.2" }, + { name = "python-dateutil", specifier = ">=2.9.0.post0" }, + { name = "referencing", marker = "extra == 'validation'", specifier = ">=0.37.0" }, ] +provides-extras = ["jinja2", "obstore", "validation"] -[[package]] -name = "stevedore" -version = "5.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pbr" }, +[package.metadata.requires-dev] +bench = [{ name = "asv", specifier = ">=0.6.5" }] +dev = [ + { name = "basedpyright", specifier = ">=1.36.1" }, + { name = "html5lib", specifier = ">=1.1" }, + { name = "jsonschema", specifier = ">=4.25.1" }, + { name = "pytest", specifier = ">=9.0.2" }, + { name = "pytest-mock", specifier = ">=3.15.1" }, + { name = "pytest-recording", specifier = ">=0.13.4" }, + { name = "requests-mock", specifier = ">=1.12.1" }, + { name = "ruff", specifier = ">=0.14.9" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/3f/13cacea96900bbd31bb05c6b74135f85d15564fc583802be56976c940470/stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b", size = 513858, upload-time = "2025-02-20T14:03:57.285Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/45/8c4ebc0c460e6ec38e62ab245ad3c7fc10b210116cea7c16d61602aa9558/stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe", size = 49533, upload-time = "2025-02-20T14:03:55.849Z" }, +docs = [ + { name = "mkdocs-material", specifier = ">=9.7.0" }, + { name = "mkdocstrings-python", specifier = ">=2.0.1" }, ] [[package]] -name = "tabulate" -version = "0.9.0" +name = "pytest" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] -name = "terminado" -version = "0.18.1" +name = "pytest-mock" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "os_name != 'nt'" }, - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "tornado" }, + { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] [[package]] -name = "tinycss2" -version = "1.4.0" +name = "pytest-recording" +version = "0.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings" }, + { name = "pytest" }, + { name = "vcrpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/9c/f4027c5f1693847b06d11caf4b4f6bb09f22c1581ada4663877ec166b8c6/pytest_recording-0.13.4.tar.gz", hash = "sha256:568d64b2a85992eec4ae0a419c855d5fd96782c5fb016784d86f18053792768c", size = 26576, upload-time = "2025-05-08T10:41:11.231Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/42/c2/ce34735972cc42d912173e79f200fe66530225190c06655c5632a9d88f1e/pytest_recording-0.13.4-py3-none-any.whl", hash = "sha256:ad49a434b51b1c4f78e85b1e6b74fdcc2a0a581ca16e52c798c6ace971f7f439", size = 13723, upload-time = "2025-05-08T10:41:09.684Z" }, ] [[package]] -name = "tomli" -version = "2.2.1" +name = "python-dateutil" +version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +dependencies = [ + { name = "six" }, ] - -[[package]] -name = "toolz" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790, upload-time = "2024-10-04T16:17:04.001Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383, upload-time = "2024-10-04T16:17:01.533Z" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] -name = "tornado" -version = "6.5.1" +name = "pywin32" +version = "311" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload-time = "2025-05-22T18:15:38.788Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload-time = "2025-05-22T18:15:20.862Z" }, - { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload-time = "2025-05-22T18:15:22.591Z" }, - { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload-time = "2025-05-22T18:15:24.027Z" }, - { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload-time = "2025-05-22T18:15:25.735Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload-time = "2025-05-22T18:15:27.499Z" }, - { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload-time = "2025-05-22T18:15:29.299Z" }, - { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload-time = "2025-05-22T18:15:31.038Z" }, - { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload-time = "2025-05-22T18:15:32.426Z" }, - { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload-time = "2025-05-22T18:15:34.205Z" }, - { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload-time = "2025-05-22T18:15:36.1Z" }, - { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload-time = "2025-05-22T18:15:37.433Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] [[package]] -name = "traitlets" -version = "5.14.3" +name = "pyyaml" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] -name = "types-html5lib" -version = "1.1.11.20250708" +name = "pyyaml-env-tag" +version = "1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/3b/1f5ba4358cfc1421cced5cdb9d2b08b4b99e4f9a41da88ce079f6d1a7bf1/types_html5lib-1.1.11.20250708.tar.gz", hash = "sha256:24321720fdbac71cee50d5a4bec9b7448495b7217974cffe3fcf1ede4eef7afe", size = 16799, upload-time = "2025-07-08T03:13:53.14Z" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/50/5fc23cf647eee23acdd337c8150861d39980cf11f33dd87f78e87d2a4bad/types_html5lib-1.1.11.20250708-py3-none-any.whl", hash = "sha256:bb898066b155de7081cb182179e2ded31b9e0e234605e2cb46536894e68a6954", size = 22913, upload-time = "2025-07-08T03:13:52.098Z" }, + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] [[package]] -name = "types-jsonschema" -version = "4.25.0.20250720" +name = "referencing" +version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "referencing" }, + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/f3/7dba3ea10a52f57f6bbc7905b34ae0b04fd1487448c089c96aab710d745d/types_jsonschema-4.25.0.20250720.tar.gz", hash = "sha256:765a3b6144798fe3161fd8cbe570a756ed3e8c0e5adb7c09693eb49faad39dbd", size = 15470, upload-time = "2025-07-20T03:29:35.208Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/b6/e9ca8ba6803992fd5a8a83a21ae61e7d050cc24b51d33215860510b561d9/types_jsonschema-4.25.0.20250720-py3-none-any.whl", hash = "sha256:7d7897c715310d8bf9ae27a2cedba78bbb09e4cad83ce06d2aa79b73a88941df", size = 15769, upload-time = "2025-07-20T03:29:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] -name = "types-orjson" -version = "3.6.2" +name = "requests" +version = "2.32.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/97/3f78cfdf663e5668e8b490d8c84d6de089d2d8dbad935f0dc43555d52a90/types-orjson-3.6.2.tar.gz", hash = "sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638", size = 1999, upload-time = "2022-01-07T11:31:10.518Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/84/b34abd2d08381c5113e475908a1d79d27dc9a15f669213cee4ca03d1a891/types_orjson-3.6.2-py3-none-any.whl", hash = "sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4", size = 2224, upload-time = "2022-01-07T11:31:09.271Z" }, +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation == 'PyPy'" }, + { name = "urllib3", version = "2.6.2", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation != 'PyPy'" }, ] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20250708" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/95/6bdde7607da2e1e99ec1c1672a759d42f26644bbacf939916e086db34870/types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab", size = 15834, upload-time = "2025-07-08T03:14:03.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/52/43e70a8e57fefb172c22a21000b03ebcc15e47e97f5cb8495b9c2832efb4/types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f", size = 17724, upload-time = "2025-07-08T03:14:02.593Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] -name = "types-urllib3" -version = "1.26.25.14" +name = "requests-mock" +version = "1.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/de/b9d7a68ad39092368fb21dd6194b362b98a1daeea5dcfef5e1adb5031c7e/types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", size = 11239, upload-time = "2023-07-20T15:19:31.307Z" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/32/587625f91f9a0a3d84688bf9cfc4b2480a7e8ec327cefd0ff2ac891fd2cf/requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401", size = 60901, upload-time = "2024-03-29T03:54:29.446Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/7b/3fc711b2efea5e85a7a0bbfe269ea944aa767bbba5ec52f9ee45d362ccf3/types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e", size = 15377, upload-time = "2023-07-20T15:19:30.379Z" }, + { url = "https://files.pythonhosted.org/packages/97/ec/889fbc557727da0c34a33850950310240f2040f3b1955175fdb2b36a8910/requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", size = 27695, upload-time = "2024-03-29T03:54:27.64Z" }, ] [[package]] -name = "typing-extensions" -version = "4.14.1" +name = "rpds-py" +version = "0.26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, + { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" }, + { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" }, + { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" }, + { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" }, + { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" }, + { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" }, + { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214, upload-time = "2025-07-01T15:54:37.469Z" }, + { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113, upload-time = "2025-07-01T15:54:38.954Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189, upload-time = "2025-07-01T15:54:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998, upload-time = "2025-07-01T15:54:43.025Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903, upload-time = "2025-07-01T15:54:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785, upload-time = "2025-07-01T15:54:46.043Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329, upload-time = "2025-07-01T15:54:47.64Z" }, + { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875, upload-time = "2025-07-01T15:54:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636, upload-time = "2025-07-01T15:54:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663, upload-time = "2025-07-01T15:54:52.023Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428, upload-time = "2025-07-01T15:54:53.692Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571, upload-time = "2025-07-01T15:54:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415, upload-time = "2025-07-01T15:54:59.751Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783, upload-time = "2025-07-01T15:55:00.898Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844, upload-time = "2025-07-01T15:55:02.201Z" }, + { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105, upload-time = "2025-07-01T15:55:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440, upload-time = "2025-07-01T15:55:05.398Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759, upload-time = "2025-07-01T15:55:08.316Z" }, + { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032, upload-time = "2025-07-01T15:55:09.52Z" }, + { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416, upload-time = "2025-07-01T15:55:11.216Z" }, + { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" }, + { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" }, + { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" }, + { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" }, + { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" }, + { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" }, + { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" }, + { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" }, + { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, ] [[package]] -name = "typing-inspection" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +name = "ruff" +version = "0.14.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, ] [[package]] -name = "tzdata" -version = "2025.2" +name = "six" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] -name = "uri-template" -version = "1.3.0" +name = "tabulate" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] [[package]] -name = "urllib3" -version = "2.6.2" +name = "typing-extensions" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] -name = "uvicorn" -version = "0.35.0" +name = "urllib3" +version = "1.26.20" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +resolution-markers = [ + "platform_python_implementation == 'PyPy'", ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, ] [[package]] -name = "vcrpy" -version = "5.1.0" +name = "urllib3" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", -] -dependencies = [ - { name = "pyyaml", marker = "platform_python_implementation == 'PyPy'" }, - { name = "wrapt", marker = "platform_python_implementation == 'PyPy'" }, - { name = "yarl", marker = "platform_python_implementation == 'PyPy'" }, + "platform_python_implementation != 'PyPy'", ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/ea/a166a3cce4ac5958ba9bbd9768acdb1ba38ae17ff7986da09fa5b9dbc633/vcrpy-5.1.0.tar.gz", hash = "sha256:bbf1532f2618a04f11bce2a99af3a9647a32c880957293ff91e0a5f187b6b3d2", size = 84576, upload-time = "2023-07-31T03:19:32.231Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/5b/3f70bcb279ad30026cc4f1df0a0491a0205a24dddd88301f396c485de9e7/vcrpy-5.1.0-py2.py3-none-any.whl", hash = "sha256:605e7b7a63dcd940db1df3ab2697ca7faf0e835c0852882142bafb19649d599e", size = 41969, upload-time = "2023-07-31T03:19:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] name = "vcrpy" version = "7.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] dependencies = [ - { name = "pyyaml", marker = "platform_python_implementation != 'PyPy'" }, - { name = "urllib3", marker = "platform_python_implementation != 'PyPy'" }, - { name = "wrapt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "yarl", marker = "platform_python_implementation != 'PyPy'" }, + { name = "pyyaml" }, + { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation == 'PyPy'" }, + { name = "urllib3", version = "2.6.2", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation != 'PyPy'" }, + { name = "wrapt" }, + { name = "yarl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/d3/856e06184d4572aada1dd559ddec3bedc46df1f2edc5ab2c91121a2cccdb/vcrpy-7.0.0.tar.gz", hash = "sha256:176391ad0425edde1680c5b20738ea3dc7fb942520a48d2993448050986b3a50", size = 85502, upload-time = "2024-12-31T00:07:57.894Z" } wheels = [ @@ -4293,134 +1147,40 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.32.0" +version = "20.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970, upload-time = "2025-07-21T04:09:50.985Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" }, -] - -[[package]] -name = "watchfiles" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757, upload-time = "2025-06-15T19:04:51.058Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511, upload-time = "2025-06-15T19:04:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739, upload-time = "2025-06-15T19:04:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106, upload-time = "2025-06-15T19:04:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264, upload-time = "2025-06-15T19:04:57.009Z" }, - { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612, upload-time = "2025-06-15T19:04:58.409Z" }, - { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242, upload-time = "2025-06-15T19:04:59.786Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148, upload-time = "2025-06-15T19:05:01.103Z" }, - { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574, upload-time = "2025-06-15T19:05:02.582Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378, upload-time = "2025-06-15T19:05:03.719Z" }, - { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829, upload-time = "2025-06-15T19:05:04.822Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192, upload-time = "2025-06-15T19:05:06.348Z" }, - { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, - { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, - { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, - { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, - { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, - { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, - { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, - { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, - { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748, upload-time = "2025-06-15T19:06:44.2Z" }, - { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801, upload-time = "2025-06-15T19:06:45.774Z" }, - { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528, upload-time = "2025-06-15T19:06:46.791Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095, upload-time = "2025-06-15T19:06:48.211Z" }, - { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, -] - -[[package]] -name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/49/87e23d8f742f10f965bce5d6b285fc88a4f436b11daf6b6225d4d66f8492/virtualenv-20.36.0.tar.gz", hash = "sha256:a3601f540b515a7983508113f14e78993841adc3d83710fa70f0ac50f43b23ed", size = 6032237, upload-time = "2026-01-07T17:20:04.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6a/0af36875e0023a1f2d0b66b4051721fc26740e947696922df1665b75e5d3/virtualenv-20.36.0-py3-none-any.whl", hash = "sha256:e7ded577f3af534fd0886d4ca03277f5542053bedb98a70a989d3c22cfa5c9ac", size = 6008261, upload-time = "2026-01-07T17:20:02.87Z" }, ] [[package]] -name = "webcolors" -version = "24.11.1" +name = "watchdog" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] [[package]] @@ -4432,111 +1192,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, ] -[[package]] -name = "websocket-client" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, -] - -[[package]] -name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, - { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, - { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, - { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, - { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, - { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, - { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, - { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, -] - -[[package]] -name = "widgetsnbextension" -version = "4.0.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/53/2e0253c5efd69c9656b1843892052a31c36d37ad42812b5da45c62191f7e/widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af", size = 1097428, upload-time = "2025-04-10T13:01:25.628Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/51/5447876806d1088a0f8f71e16542bf350918128d0a69437df26047c8e46f/widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575", size = 2196503, upload-time = "2025-04-10T13:01:23.086Z" }, -] - [[package]] name = "wrapt" version = "1.17.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307, upload-time = "2025-01-14T10:33:13.616Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486, upload-time = "2025-01-14T10:33:15.947Z" }, - { url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777, upload-time = "2025-01-14T10:33:17.462Z" }, - { url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314, upload-time = "2025-01-14T10:33:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947, upload-time = "2025-01-14T10:33:24.414Z" }, - { url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778, upload-time = "2025-01-14T10:33:26.152Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716, upload-time = "2025-01-14T10:33:27.372Z" }, - { url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548, upload-time = "2025-01-14T10:33:28.52Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334, upload-time = "2025-01-14T10:33:29.643Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427, upload-time = "2025-01-14T10:33:30.832Z" }, - { url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774, upload-time = "2025-01-14T10:33:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload-time = "2025-01-14T10:33:33.992Z" }, - { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload-time = "2025-01-14T10:33:35.264Z" }, - { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload-time = "2025-01-14T10:33:38.28Z" }, - { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload-time = "2025-01-14T10:33:40.678Z" }, - { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload-time = "2025-01-14T10:33:41.868Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload-time = "2025-01-14T10:33:43.598Z" }, - { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload-time = "2025-01-14T10:33:48.499Z" }, - { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload-time = "2025-01-14T10:33:51.191Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload-time = "2025-01-14T10:33:52.328Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload-time = "2025-01-14T10:33:53.551Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload-time = "2025-01-14T10:33:56.323Z" }, { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, @@ -4573,80 +1234,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, ] -[[package]] -name = "xarray" -version = "2025.6.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pandas", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/ec/e50d833518f10b0c24feb184b209bb6856f25b919ba8c1f89678b930b1cd/xarray-2025.6.1.tar.gz", hash = "sha256:a84f3f07544634a130d7dc615ae44175419f4c77957a7255161ed99c69c7c8b0", size = 3003185, upload-time = "2025-06-12T03:04:09.099Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/8a/6b50c1dd2260d407c1a499d47cf829f59f07007e0dcebafdabb24d1d26a5/xarray-2025.6.1-py3-none-any.whl", hash = "sha256:8b988b47f67a383bdc3b04c5db475cd165e580134c1f1943d52aee4a9c97651b", size = 1314739, upload-time = "2025-06-12T03:04:06.708Z" }, -] - -[[package]] -name = "xarray" -version = "2025.8.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pandas", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d2/55/18055bc943029d25fb8f260b7e3b1485c30646ccf503a5e4a744d31a3b78/xarray-2025.8.0.tar.gz", hash = "sha256:323d4169ce72d4ef849de2b0bd122f9cd2905b82c7558169930dc16070982bab", size = 3034425, upload-time = "2025-08-14T16:52:13.872Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/c8/0f8db9d9478de8d70cbcae2056588401e26168e269d6d9919bf2ecb01f78/xarray-2025.8.0-py3-none-any.whl", hash = "sha256:1c454f32b38c93df68e450238c9473fe21248b8572d42ddd58c5170bb30934ee", size = 1342279, upload-time = "2025-08-14T16:52:10.956Z" }, -] - -[[package]] -name = "xpystac" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "pystac", marker = "python_full_version < '3.11'" }, - { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/ad4ba3747c1be7f476ebcd5afd2ca5bccba69c7e436700ac38283e770e68/xpystac-0.2.0.tar.gz", hash = "sha256:f3c6b98c8c70af37132c629c8e7d642a33df88ce99ae24d1b97ccdc46bb370f1", size = 121143, upload-time = "2025-03-11T15:22:29.614Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/67/9c44b4798cd5d0dbc45243318cf89482b043ed05dec12c59558b3a04aab8/xpystac-0.2.0-py3-none-any.whl", hash = "sha256:f1da44fb273b47235b027ff5749430e088525bea73a8d9e0955180eeb7f1334c", size = 7068, upload-time = "2025-03-11T15:22:28.753Z" }, -] - -[[package]] -name = "xpystac" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "pystac", marker = "python_full_version >= '3.11'" }, - { name = "xarray", version = "2025.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/c5/94a094c0f5c425e0cd036b8534a545e803eb3c7a4d4dc516bd5ef83d74eb/xpystac-0.3.0.tar.gz", hash = "sha256:e2d75483b10fbd6de32fa0c242633384151c9615630e182c390ca7cb063e2922", size = 118794, upload-time = "2025-08-20T17:15:59.214Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/62/0facd92075a4ed6207a1fe8f3b035067d5998c32397d1bec1e06aa4a059a/xpystac-0.3.0-py3-none-any.whl", hash = "sha256:7bada281cdfe1a005be129e96096994de1134bbfc9bd4cf2670401750487508a", size = 8846, upload-time = "2025-08-20T17:15:58.299Z" }, -] - [[package]] name = "yarl" version = "1.20.1" @@ -4658,40 +1245,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, - { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, - { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, - { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, - { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, - { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, - { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, - { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, - { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, - { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, - { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, - { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, - { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, - { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, - { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, - { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, - { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, - { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, - { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, - { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, @@ -4746,47 +1299,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, ] -[[package]] -name = "zarr" -version = "2.18.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "asciitree", marker = "python_full_version < '3.11'" }, - { name = "fasteners", marker = "python_full_version < '3.11' and sys_platform != 'emscripten'" }, - { name = "numcodecs", version = "0.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/23/c4/187a21ce7cf7c8f00c060dd0e04c2a81139bb7b1ab178bba83f2e1134ce2/zarr-2.18.3.tar.gz", hash = "sha256:2580d8cb6dd84621771a10d31c4d777dca8a27706a1a89b29f42d2d37e2df5ce", size = 3603224, upload-time = "2024-09-04T23:20:16.595Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/c9/142095e654c2b97133ff71df60979422717b29738b08bc8a1709a5d5e0d0/zarr-2.18.3-py3-none-any.whl", hash = "sha256:b1f7dfd2496f436745cdd4c7bcf8d3b4bc1dceef5fdd0d589c87130d842496dd", size = 210723, upload-time = "2024-09-04T23:20:14.491Z" }, -] - -[[package]] -name = "zarr" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", -] -dependencies = [ - { name = "donfig", marker = "python_full_version >= '3.11'" }, - { name = "numcodecs", version = "0.16.2", source = { registry = "https://pypi.org/simple" }, extra = ["crc32c"], marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/a9/29fe1800380092ae03ac6207d757f3e5affaf1fcd2e5ef074cf4fc68f0fa/zarr-3.1.1.tar.gz", hash = "sha256:17db72f37f2489452d2137ac891c4133b8f976f9189d8efd3e75f3b3add84e8c", size = 314075, upload-time = "2025-07-30T11:51:36.81Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/48/bde2f58cfbc9fd6ab844e2f2fd79d5e54195c12a17aa9b47c0b0e701a421/zarr-3.1.1-py3-none-any.whl", hash = "sha256:9a0b7e7c27bf62965b8eef6b8b8fdb9b47381f0738be35e40f37be6479b546be", size = 255373, upload-time = "2025-07-30T11:51:34.623Z" }, -] - [[package]] name = "zipp" version = "3.23.0" From ac1a8f984a225215a851aef0263b2a0b4705a6bb Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 7 Jan 2026 16:47:47 -0700 Subject: [PATCH 02/60] ci: switch to prek --- .github/workflows/continuous-integration.yml | 4 +-- pyproject.toml | 1 + uv.lock | 26 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index d4ec11f4b..df0c1ffbd 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -32,13 +32,13 @@ jobs: python-version: ${{ matrix.python-version }} - name: Lint if: runner.os != 'Windows' - run: uv run pre-commit run --all-files + run: uv run prek run --all-files - name: Test on windows if: runner.os == 'Windows' shell: bash env: TMPDIR: 'D:\\a\\_temp' - run: uv run pytest tests + run: uv run pytest tests --block-network --record-mode=none - name: Test if: runner.os != 'Windows' run: uv run pytest tests --block-network --record-mode=none diff --git a/pyproject.toml b/pyproject.toml index 94cba19a9..4a837e0d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ dev = [ "basedpyright>=1.36.1", "html5lib>=1.1", "jsonschema>=4.25.1", + "prek>=0.2.27", "pytest>=9.0.2", "pytest-mock>=3.15.1", "pytest-recording>=0.13.4", diff --git a/uv.lock b/uv.lock index 493758700..76231bd6b 100644 --- a/uv.lock +++ b/uv.lock @@ -660,6 +660,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prek" +version = "0.2.27" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/0b/2a0509d2d8881811e4505227df9ca31b3a4482497689b5c2b7f38faab1e5/prek-0.2.27.tar.gz", hash = "sha256:dfd2a1b040f55402c2449ae36ea28e8c1bb05ca900490d5c0996b1b72297cc0e", size = 283076, upload-time = "2026-01-07T14:23:17.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/03/01dd50c89aa38bc194bb14073468bcbd1fec1621150967b7d424d2f043a7/prek-0.2.27-py3-none-linux_armv6l.whl", hash = "sha256:3c7ce590289e4fc0119524d0f0f187133a883d6784279b6a3a4080f5851f1612", size = 4799872, upload-time = "2026-01-07T14:23:15.5Z" }, + { url = "https://files.pythonhosted.org/packages/51/86/807267659e4775c384e755274a214a45461266d6a1117ec059fbd245731b/prek-0.2.27-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:df35dee5dcf09a9613c8b9c6f3d79a3ec894eb13172f569773d529a5458887f8", size = 4903805, upload-time = "2026-01-07T14:23:35.199Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5b/cc3c13ed43e7523f27a2f9b14d18c9b557fb1090e7a74689f934cb24d721/prek-0.2.27-py3-none-macosx_11_0_arm64.whl", hash = "sha256:772d84ebe19b70eba1da0f347d7d486b9b03c0a33fe19c2d1bf008e72faa13b3", size = 4629083, upload-time = "2026-01-07T14:23:12.204Z" }, + { url = "https://files.pythonhosted.org/packages/34/d9/86eafc1d7bddf9236263d4428acca76b7bfc7564ccc2dc5e539d1be22b5e/prek-0.2.27-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:571aab2e9c0eace30a51b0667533862f4bdc0a81334d342f6f516796a63fd1e4", size = 4825005, upload-time = "2026-01-07T14:23:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/44/cf/83004be0a9e8ac3c8c927afab5948d9e31760e15442a0fff273f158cae51/prek-0.2.27-py3-none-manylinux_2_24_armv7l.whl", hash = "sha256:cc7a47f40f36c503e77eb6209f7ad5979772f9c7c5e88ba95cf20f0d24ece926", size = 4724850, upload-time = "2026-01-07T14:23:18.276Z" }, + { url = "https://files.pythonhosted.org/packages/73/8c/5c754f4787fc07e7fa6d2c25ac90931cd3692b51f03c45259aca2ea6fd3f/prek-0.2.27-py3-none-manylinux_2_24_i686.whl", hash = "sha256:cd87b034e56f610f9cafd3b7d554dca69f1269a511ad330544d696f08c656eb3", size = 5042584, upload-time = "2026-01-07T14:23:37.892Z" }, + { url = "https://files.pythonhosted.org/packages/4d/80/762283280ae3d2aa35385ed2db76c39518ed789fbaa0b6fb52352764d41c/prek-0.2.27-py3-none-manylinux_2_24_s390x.whl", hash = "sha256:638b4e942dd1cea6fc0ddf4ce5b877e5aa97c6c142b7bf28e9ce6db8f0d06a4a", size = 5511089, upload-time = "2026-01-07T14:23:23.121Z" }, + { url = "https://files.pythonhosted.org/packages/e0/78/1b53b604c188f4054346b237ec1652489718fedc0d465baadecf7907dc42/prek-0.2.27-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:769b13d7bd11fbb4a5fc5fffd2158aea728518ec9aca7b36723b10ad8b189810", size = 5100175, upload-time = "2026-01-07T14:23:19.643Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/a9dc29598e664e6e663da316338e1e980e885072107876a3ca8d697f4d65/prek-0.2.27-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6c0bc38806caf14d47d44980d936ee0cb153bccea703fb141c16bb9be49fb778", size = 4833004, upload-time = "2026-01-07T14:23:36.467Z" }, + { url = "https://files.pythonhosted.org/packages/04/b7/56ca9226f20375519d84a2728a985cc491536f0b872f10cb62bcc55ccea0/prek-0.2.27-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:77c8ac95a0bb1156159edcb3c52b5f852910a7d2ed53d6136ecc1d9d6dc39fe1", size = 4842559, upload-time = "2026-01-07T14:23:31.691Z" }, + { url = "https://files.pythonhosted.org/packages/87/20/71ef2c558daabbe2a4cfe6567597f7942dbbad1a3caca0d786b4ec1304cb/prek-0.2.27-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:5e8d56b386660266c2a31e12af8b52a0901fe21fb71ab05768fdd41b405794ac", size = 4709053, upload-time = "2026-01-07T14:23:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/e8/14/7376117d0e91e35ce0f6581d4427280f634b9564c86615f74b79f242fa79/prek-0.2.27-py3-none-musllinux_1_1_i686.whl", hash = "sha256:3fdeaa1b9f97e21d870ba091914bc7ccf85106a9ef74d81f362a92cdbfe33569", size = 4927803, upload-time = "2026-01-07T14:23:30Z" }, + { url = "https://files.pythonhosted.org/packages/fb/81/87f36898ec2ac1439468b20e9e7061b4956ce0cf518c7cc15ac0457f2971/prek-0.2.27-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:20dd04fe33b9fcfbc2069f4e523ec8d9b4813c1ca4ac9784fe2154dcab42dacb", size = 5210701, upload-time = "2026-01-07T14:23:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/50/5a/53f7828543c09cb70ed35291818ec145a42ef04246fa4f82c128b26abd4f/prek-0.2.27-py3-none-win32.whl", hash = "sha256:15948cacbbccd935f57ca164b36c4c5d7b03c58cd5a335a6113cdbd149b6e50d", size = 4623511, upload-time = "2026-01-07T14:23:33.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/21/3a079075a4d4db58f909eedfd7a79517ba90bb12f7b61f6e84c3c29d4d61/prek-0.2.27-py3-none-win_amd64.whl", hash = "sha256:8225dc8523e7a0e95767b3d3e8cfb3bc160fe6af0ee5115fc16c68428c4e0779", size = 5312713, upload-time = "2026-01-07T14:23:21.116Z" }, + { url = "https://files.pythonhosted.org/packages/39/79/d1c3d96ed4f7dff37ed11101d8336131e8108315c3078246007534dcdd27/prek-0.2.27-py3-none-win_arm64.whl", hash = "sha256:f9192bfb6710db2be10f0e28ff31706a2648c1eb8a450b20b2f55f70ba05e769", size = 4978272, upload-time = "2026-01-07T14:23:13.681Z" }, +] + [[package]] name = "propcache" version = "0.3.2" @@ -788,6 +812,7 @@ dev = [ { name = "basedpyright" }, { name = "html5lib" }, { name = "jsonschema" }, + { name = "prek" }, { name = "pytest" }, { name = "pytest-mock" }, { name = "pytest-recording" }, @@ -816,6 +841,7 @@ dev = [ { name = "basedpyright", specifier = ">=1.36.1" }, { name = "html5lib", specifier = ">=1.1" }, { name = "jsonschema", specifier = ">=4.25.1" }, + { name = "prek", specifier = ">=0.2.27" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-mock", specifier = ">=3.15.1" }, { name = "pytest-recording", specifier = ">=0.13.4" }, From e4cf09b0210d636244a88a0edac0607c025646ce Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 7 Jan 2026 16:51:10 -0700 Subject: [PATCH 03/60] fix: always return `None` for Link.owner --- .pre-commit-config.yaml | 61 ++++++++++------------------------------- src/pystac/link.py | 8 ++++++ 2 files changed, 23 insertions(+), 46 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82a014321..99d0ac904 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,50 +1,19 @@ -# Configuration file for pre-commit (https://pre-commit.com/). -# Please run `pre-commit run --all-files` when adding or changing entries. - repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 hooks: - - id: pyupgrade - args: - - "--py310-plus" - - - repo: local + - id: codespell + types_or: + - jupyter + - markdown + - python + - shell + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.9 hooks: - - id: ruff - name: ruff - entry: ruff check --force-exclude --fix --exit-non-zero-on-fix - language: system - types_or: [python, pyi, jupyter] - require_serial: true - + - id: ruff-check - id: ruff-format - name: ruff-format - entry: ruff format --force-exclude - language: system - stages: [pre-commit] - types_or: [python, pyi, jupyter] - require_serial: true - - - id: codespell - name: codespell - entry: codespell - language: system - stages: [pre-commit] - types_or: [jupyter, markdown, python, shell] - - - id: doc8 - name: doc8 - entry: doc8 - language: system - files: \.rst$ - require_serial: true - - - id: mypy - name: mypy - entry: mypy - args: [--no-incremental] - language: system - stages: [pre-commit] - types: [python] - require_serial: true + - repo: https://github.com/DetachHead/basedpyright-prek-mirror + rev: 1.36.1 + hooks: + - id: basedpyright diff --git a/src/pystac/link.py b/src/pystac/link.py index a991c0248..f376883a9 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -60,6 +60,14 @@ def __init__( self._href = href or target self.target = None + @override + def __getattribute__(self, name: str, /) -> Any: + if name == "owner": + warnings.warn("Link.owner is deprecated, and is always None in pystac v2") + return None + else: + return super().__getattribute__(name) + @classmethod def try_from(cls, data: dict[str, Any] | Link) -> Link: if isinstance(data, Link): From 142086f35a3a2c2cd729e4342f7983ea90e73720 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 7 Jan 2026 17:13:01 -0700 Subject: [PATCH 04/60] chore: fix more tests --- pyproject.toml | 1 + src/pystac/__init__.py | 12 +- src/pystac/stac_object.py | 13 +- .../test_item/test_null_geometry.yaml | 529 +++++++++--------- tests/v1/test_item.py | 5 + 5 files changed, 285 insertions(+), 275 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4a837e0d0..51d9ec63a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md" Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software" [tool.basedpyright] +ignore = ["tests/v1"] reportExplicitAny = false reportAny = false reportImportCycles = false diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py index ac495c907..f3f9f8c71 100644 --- a/src/pystac/__init__.py +++ b/src/pystac/__init__.py @@ -28,7 +28,9 @@ from .item_collection import ItemCollection from .link import HIERARCHICAL_LINKS, Link from .media_type import MediaType +from .rel_type import RelType from .stac_object import STACObject +from .version import __version__ def __getattr__(name: str) -> Any: @@ -39,13 +41,13 @@ def __getattr__(name: str) -> Any: return StacIO case "STACObjectType": - from .stac_object import STACObjectType + from .stac_object import STACObjectType # pyright: ignore[reportDeprecated] - return STACObjectType + return STACObjectType # pyright: ignore[reportDeprecated] case "CatalogType": - from .catalog import CatalogType + from .catalog import CatalogType # pyright: ignore[reportDeprecated] - return CatalogType + return CatalogType # pyright: ignore[reportDeprecated] case "get_stac_version": @deprecated("get_stac_version is deprecated") @@ -66,6 +68,7 @@ def get_stac_version() -> str: "ExtensionTypeError", "Extent", "HIERARCHICAL_LINKS", + "RelType", "DEFAULT_STAC_VERSION", "Item", "ItemAssetDefinition", @@ -82,4 +85,5 @@ def get_stac_version() -> str: "SpatialExtent", "TemporalExtent", "read_file", + "__version__", ] diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 972b5cdad..acd9b6762 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -99,7 +99,18 @@ def __init__( **kwargs: Any, ) -> None: if type != self.type: - raise STACTypeError(f"Expected {self.type}, got {type}") + d = { + "type": type, + "id": id, + "stac_version": stac_version, + "stac_extensions": stac_extensions, + "links": links, + } + d.update(kwargs) + raise STACTypeError( + d, + self.__class__, + ) self.id: str = id self.stac_version: str = stac_version diff --git a/tests/v1/cassettes/test_item/test_null_geometry.yaml b/tests/v1/cassettes/test_item/test_null_geometry.yaml index 726ddd0ef..aa24cdd26 100644 --- a/tests/v1/cassettes/test_item/test_null_geometry.yaml +++ b/tests/v1/cassettes/test_item/test_null_geometry.yaml @@ -3,7 +3,7 @@ interactions: body: null headers: User-Agent: - - pystac/1.14.1 + - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/item.json response: @@ -79,49 +79,7 @@ interactions: \ },\n \"roles\": {\n \"title\": \"Asset roles\",\n \ \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n}\n" - headers: - Accept-Ranges: - - bytes - Access-Control-Allow-Origin: - - '*' - Age: - - '0' - Cache-Control: - - max-age=600 - Connection: - - keep-alive - Content-Length: - - '5244' - Content-Type: - - application/json; charset=utf-8 - Date: - - Thu, 18 Sep 2025 14:55:43 GMT - ETag: - - '"66e1651c-147c"' - Last-Modified: - - Wed, 11 Sep 2024 09:38:36 GMT - Server: - - GitHub.com - Vary: - - Accept-Encoding - Via: - - 1.1 varnish - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Fastly-Request-ID: - - 43b14da1afb8316ddafcf9222c910c1a37743084 - X-GitHub-Request-Id: - - A3C0:BAEAA:C84169:E2AD23:68CC1D6E - X-Served-By: - - cache-bos4637-BOS - X-Timer: - - S1758207343.987241,VS0,VE27 - expires: - - Thu, 18 Sep 2025 15:05:43 GMT - x-proxy-cache: - - MISS + headers: {} status: code: 200 message: OK @@ -129,7 +87,7 @@ interactions: body: null headers: User-Agent: - - pystac/1.14.1 + - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/basics.json response: @@ -142,49 +100,7 @@ interactions: \ },\n \"description\": {\n \"title\": \"Item Description\",\n \"description\": \"Detailed multi-line description to fully explain the Item.\",\n \"type\": \"string\"\n }\n }\n}" - headers: - Accept-Ranges: - - bytes - Access-Control-Allow-Origin: - - '*' - Age: - - '0' - Cache-Control: - - max-age=600 - Connection: - - keep-alive - Content-Length: - - '540' - Content-Type: - - application/json; charset=utf-8 - Date: - - Thu, 18 Sep 2025 14:55:43 GMT - ETag: - - '"66e1651c-21c"' - Last-Modified: - - Wed, 11 Sep 2024 09:38:36 GMT - Server: - - GitHub.com - Vary: - - Accept-Encoding - Via: - - 1.1 varnish - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Fastly-Request-ID: - - 2778a2f91715f30004f3fc21f6d16fc4f78ab03a - X-GitHub-Request-Id: - - 2F53:A07A9:C8C99E:E32E77:68CC1D6E - X-Served-By: - - cache-bos4631-BOS - X-Timer: - - S1758207343.101100,VS0,VE53 - expires: - - Thu, 18 Sep 2025 15:05:43 GMT - x-proxy-cache: - - MISS + headers: {} status: code: 200 message: OK @@ -192,7 +108,7 @@ interactions: body: null headers: User-Agent: - - pystac/1.14.1 + - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/datetime.json response: @@ -235,51 +151,7 @@ interactions: \"date-time\"\n },\n \"updated\": {\n \"title\": \"Last Update Time\",\n \"type\": \"string\",\n \"format\": \"date-time\"\n }\n \ }\n}" - headers: - Accept-Ranges: - - bytes - Access-Control-Allow-Origin: - - '*' - Age: - - '0' - Cache-Control: - - max-age=600 - Connection: - - keep-alive - Content-Length: - - '2690' - Content-Type: - - application/json; charset=utf-8 - Date: - - Thu, 18 Sep 2025 14:55:43 GMT - ETag: - - '"66e1651c-a82"' - Last-Modified: - - Wed, 11 Sep 2024 09:38:36 GMT - Server: - - GitHub.com - Vary: - - Accept-Encoding - Via: - - 1.1 varnish - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Fastly-Request-ID: - - dbbe4905d6d5662785af1c608d96b7eaff9510ff - X-GitHub-Request-Id: - - 3E81:0D6F:C30426:DD705D:68CC1D6F - X-Served-By: - - cache-bos4645-BOS - X-Timer: - - S1758207344.581499,VS0,VE51 - expires: - - Thu, 18 Sep 2025 15:05:43 GMT - x-origin-cache: - - HIT - x-proxy-cache: - - MISS + headers: {} status: code: 200 message: OK @@ -287,7 +159,7 @@ interactions: body: null headers: User-Agent: - - pystac/1.14.1 + - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/instrument.json response: @@ -302,51 +174,7 @@ interactions: \"string\"\n },\n \"mission\": {\n \"title\": \"Mission\",\n \"type\": \"string\"\n },\n \"gsd\": {\n \"title\": \"Ground Sample Distance\",\n \ \"type\": \"number\"\n }\n }\n}" - headers: - Accept-Ranges: - - bytes - Access-Control-Allow-Origin: - - '*' - Age: - - '0' - Cache-Control: - - max-age=600 - Connection: - - keep-alive - Content-Length: - - '674' - Content-Type: - - application/json; charset=utf-8 - Date: - - Thu, 18 Sep 2025 14:55:43 GMT - ETag: - - '"66e1651c-2a2"' - Last-Modified: - - Wed, 11 Sep 2024 09:38:36 GMT - Server: - - GitHub.com - Vary: - - Accept-Encoding - Via: - - 1.1 varnish - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Fastly-Request-ID: - - 8e2eb7089916d25ce693b858775b4d497d146556 - X-GitHub-Request-Id: - - 7C03:3CD2C3:DA5DDA:F4BC38:68CC1D6F - X-Served-By: - - cache-bos4689-BOS - X-Timer: - - S1758207344.709054,VS0,VE47 - expires: - - Thu, 18 Sep 2025 15:05:43 GMT - x-origin-cache: - - HIT - x-proxy-cache: - - MISS + headers: {} status: code: 200 message: OK @@ -354,7 +182,7 @@ interactions: body: null headers: User-Agent: - - pystac/1.14.1 + - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/licensing.json response: @@ -364,51 +192,7 @@ interactions: \ \"title\": \"Licensing Fields\",\n \"type\": \"object\",\n \"properties\": {\n \"license\": {\n \"type\": \"string\",\n \"pattern\": \"^[\\\\w\\\\-\\\\.\\\\+]+$\"\n \ }\n }\n}" - headers: - Accept-Ranges: - - bytes - Access-Control-Allow-Origin: - - '*' - Age: - - '0' - Cache-Control: - - max-age=600 - Connection: - - keep-alive - Content-Length: - - '309' - Content-Type: - - application/json; charset=utf-8 - Date: - - Thu, 18 Sep 2025 14:55:43 GMT - ETag: - - '"66e1651c-135"' - Last-Modified: - - Wed, 11 Sep 2024 09:38:36 GMT - Server: - - GitHub.com - Vary: - - Accept-Encoding - Via: - - 1.1 varnish - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Fastly-Request-ID: - - 3ba2180eb41273637f48187edf1340062f64d288 - X-GitHub-Request-Id: - - C4E5:1D3617:CA3033:E4A206:68CC1D6F - X-Served-By: - - cache-bos4637-BOS - X-Timer: - - S1758207344.941399,VS0,VE41 - expires: - - Thu, 18 Sep 2025 15:05:43 GMT - x-origin-cache: - - HIT - x-proxy-cache: - - MISS + headers: {} status: code: 200 message: OK @@ -416,7 +200,7 @@ interactions: body: null headers: User-Agent: - - pystac/1.14.1 + - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/provider.json response: @@ -436,51 +220,256 @@ interactions: {\n \"title\": \"Organization homepage\",\n \"type\": \"string\",\n \"format\": \"url\"\n }\n }\n }\n \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - schemas.stacspec.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/item.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/item.json#\",\n + \ \"title\": \"STAC Item\",\n \"type\": \"object\",\n \"description\": \"This + object represents the metadata for an item in a SpatioTemporal Asset Catalog.\",\n + \ \"allOf\": [\n {\n \"$ref\": \"#/definitions/core\"\n }\n ],\n + \ \"definitions\": {\n \"common_metadata\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"basics.json\"\n },\n {\n \"$ref\": + \"datetime.json\"\n },\n {\n \"$ref\": \"instrument.json\"\n + \ },\n {\n \"$ref\": \"licensing.json\"\n },\n + \ {\n \"$ref\": \"provider.json\"\n }\n ]\n },\n + \ \"core\": {\n \"allOf\": [\n {\n \"$ref\": \"https://geojson.org/schema/Feature.json\"\n + \ },\n {\n \"oneOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"geometry\",\n + \ \"bbox\"\n ],\n \"properties\": + {\n \"geometry\": {\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n + \ },\n \"bbox\": {\n \"type\": + \"array\",\n \"oneOf\": [\n {\n \"minItems\": + 4,\n \"maxItems\": 4\n },\n {\n + \ \"minItems\": 6,\n \"maxItems\": + 6\n }\n ],\n \"items\": + {\n \"type\": \"number\"\n }\n }\n + \ }\n },\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"geometry\"\n ],\n + \ \"properties\": {\n \"geometry\": {\n \"type\": + \"null\"\n },\n \"bbox\": {\n \"not\": + {}\n }\n }\n }\n ]\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"stac_version\",\n + \ \"id\",\n \"links\",\n \"assets\",\n \"properties\"\n + \ ],\n \"properties\": {\n \"stac_version\": {\n + \ \"title\": \"STAC version\",\n \"type\": \"string\",\n + \ \"const\": \"1.0.0-beta.2\"\n },\n \"stac_extensions\": + {\n \"title\": \"STAC extensions\",\n \"type\": + \"array\",\n \"uniqueItems\": true,\n \"items\": + {\n \"anyOf\": [\n {\n \"title\": + \"Reference to a JSON Schema\",\n \"type\": \"string\",\n + \ \"format\": \"uri\"\n },\n {\n + \ \"title\": \"Reference to a core extension\",\n \"type\": + \"string\"\n }\n ]\n }\n },\n + \ \"id\": {\n \"title\": \"Provider ID\",\n \"description\": + \"Provider item ID\",\n \"type\": \"string\"\n },\n + \ \"links\": {\n \"title\": \"Item links\",\n \"description\": + \"Links to item relations\",\n \"type\": \"array\",\n \"items\": + {\n \"$ref\": \"#/definitions/link\"\n }\n },\n + \ \"assets\": {\n \"$ref\": \"#/definitions/assets\"\n + \ },\n \"properties\": {\n \"$ref\": \"#/definitions/common_metadata\"\n + \ },\n \"collection\": {\n \"title\": \"Collection + ID\",\n \"description\": \"The ID of the STAC Collection this + Item references to.\",\n \"type\": \"string\"\n }\n + \ }\n }\n ]\n },\n \"link\": {\n \"type\": + \"object\",\n \"required\": [\n \"rel\",\n \"href\"\n ],\n + \ \"properties\": {\n \"href\": {\n \"title\": \"Link + reference\",\n \"type\": \"string\"\n },\n \"rel\": + {\n \"title\": \"Link relation type\",\n \"type\": \"string\"\n + \ },\n \"type\": {\n \"title\": \"Link type\",\n \"type\": + \"string\"\n },\n \"title\": {\n \"title\": \"Link + title\",\n \"type\": \"string\"\n },\n \"created\": + {\n \"$ref\": \"datetime.json#/definitions/created\"\n },\n + \ \"updated\": {\n \"$ref\": \"datetime.json#/definitions/updated\"\n + \ }\n }\n },\n \"assets\": {\n \"title\": \"Asset links\",\n + \ \"description\": \"Links to assets\",\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/asset\"\n + \ }\n },\n \"asset\": {\n \"type\": \"object\",\n \"required\": + [\n \"href\"\n ],\n \"properties\": {\n \"href\": + {\n \"title\": \"Asset reference\",\n \"type\": \"string\"\n + \ },\n \"title\": {\n \"title\": \"Asset title\",\n + \ \"type\": \"string\"\n },\n \"description\": {\n \"title\": + \"Asset description\",\n \"type\": \"string\"\n },\n \"type\": + {\n \"title\": \"Asset type\",\n \"type\": \"string\"\n + \ },\n \"roles\": {\n \"title\": \"Asset roles\",\n + \ \"type\": \"array\",\n \"items\": {\n \"type\": + \"string\"\n }\n }\n }\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null headers: - Accept-Ranges: - - bytes - Access-Control-Allow-Origin: - - '*' - Age: - - '0' - Cache-Control: - - max-age=600 Connection: - - keep-alive - Content-Length: - - '1038' - Content-Type: - - application/json; charset=utf-8 - Date: - - Thu, 18 Sep 2025 14:55:44 GMT - ETag: - - '"66e1651c-40e"' - Last-Modified: - - Wed, 11 Sep 2024 09:38:36 GMT - Server: - - GitHub.com - Vary: - - Accept-Encoding - Via: - - 1.1 varnish - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Fastly-Request-ID: - - d53162fad8750abc20c1cd8fa2adceccd928f6be - X-GitHub-Request-Id: - - 5805:B21BA:AD266F:C78835:68CC1D6F - X-Served-By: - - cache-bos4677-BOS - X-Timer: - - S1758207344.061574,VS0,VE43 - expires: - - Thu, 18 Sep 2025 15:05:44 GMT - x-origin-cache: - - HIT - x-proxy-cache: - - MISS + - close + Host: + - schemas.stacspec.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/basics.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/basics.json#\",\n + \ \"title\": \"Basic Descriptive Fields\",\n \"type\": \"object\",\n \"properties\": + {\n \"title\": {\n \"title\": \"Item Title\",\n \"description\": + \"A human-readable title describing the Item.\",\n \"type\": \"string\"\n + \ },\n \"description\": {\n \"title\": \"Item Description\",\n \"description\": + \"Detailed multi-line description to fully explain the Item.\",\n \"type\": + \"string\"\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - schemas.stacspec.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/datetime.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/datetime.json#\",\n + \ \"title\": \"Date and Time Fields\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"properties\": {\n \"created\": {\n \"$ref\": + \"#/definitions/created\"\n },\n \"updated\": {\n \"$ref\": + \"#/definitions/updated\"\n }\n }\n },\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"datetime\"\n ],\n + \ \"properties\": {\n \"datetime\": {\n \"$ref\": + \"#/definitions/datetime\"\n },\n \"start_datetime\": + {\n \"$ref\": \"#/definitions/start_datetime\"\n }, + \n \"end_datetime\": {\n \"$ref\": \"#/definitions/end_datetime\"\n + \ }\n },\n \"dependencies\": {\n \"start_datetime\": + {\n \"required\": [\n \"end_datetime\"\n ]\n + \ },\n \"end_datetime\": {\n \"required\": + [\n \"start_datetime\"\n ]\n }\n }\n + \ },\n {\n \"required\": [\n \"datetime\",\n + \ \"start_datetime\",\n \"end_datetime\"\n ],\n + \ \"properties\": {\n \"datetime\": {\n \"oneOf\": + [\n {\n \"$ref\": \"#/definitions/datetime\"\n + \ },\n {\n \"type\": [\"null\"],\n + \ \"const\": null\n }\n ]\n },\n + \ \"start_datetime\": {\n \"$ref\": \"#/definitions/start_datetime\"\n + \ }, \n \"end_datetime\": {\n \"$ref\": + \"#/definitions/end_datetime\"\n }\n }\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"datetime\": {\n \"title\": \"Date + and Time\",\n \"description\": \"The searchable date/time of the assets, + in UTC (Formatted in RFC 3339) \",\n \"type\": \"string\",\n \"format\": + \"date-time\"\n },\n \"start_datetime\": {\n \"title\": \"Start + Date and Time\",\n \"description\": \"The searchable start date/time + of the assets, in UTC (Formatted in RFC 3339) \",\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }, \n \"end_datetime\": {\n \"title\": + \"End Date and Time\", \n \"description\": \"The searchable end date/time + of the assets, in UTC (Formatted in RFC 3339) \", \n \"type\": + \"string\",\n \"format\": \"date-time\"\n },\n \"created\": {\n + \ \"title\": \"Creation Time\",\n \"type\": \"string\",\n \"format\": + \"date-time\"\n },\n \"updated\": {\n \"title\": \"Last Update + Time\",\n \"type\": \"string\",\n \"format\": \"date-time\"\n }\n + \ }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - schemas.stacspec.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/instrument.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/instrument.json#\",\n + \ \"title\": \"Instrument Fields\",\n \"type\": \"object\",\n \"properties\": + {\n \"platform\": {\n \"title\": \"Platform\",\n \"type\": \"string\"\n + \ },\n \"instruments\": {\n \"title\": \"Instruments\",\n \"type\": + \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n + \ \"constellation\": {\n \"title\": \"Constellation\",\n \"type\": + \"string\"\n },\n \"mission\": {\n \"title\": \"Mission\",\n \"type\": + \"string\"\n },\n \"gsd\": {\n \"title\": \"Ground Sample Distance\",\n + \ \"type\": \"number\"\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - schemas.stacspec.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/licensing.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/licensing.json#\",\n + \ \"title\": \"Licensing Fields\",\n \"type\": \"object\",\n \"properties\": + {\n \"license\": {\n \"type\": \"string\",\n \"pattern\": \"^[\\\\w\\\\-\\\\.\\\\+]+$\"\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - schemas.stacspec.org + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/provider.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/provider.json#\",\n + \ \"title\": \"Provider Fields\",\n \"type\": \"object\",\n \"properties\": + {\n \"providers\": {\n \"title\": \"Providers\",\n \"type\": + \"array\",\n \"items\": {\n \"properties\": {\n \"name\": + {\n \"title\": \"Organization name\",\n \"type\": \"string\"\n + \ },\n \"description\": {\n \"title\": \"Organization + description\",\n \"type\": \"string\"\n },\n \"roles\": + {\n \"title\": \"Organization roles\",\n \"type\": \"array\",\n + \ \"items\": {\n \"type\": \"string\",\n \"enum\": + [\n \"producer\",\n \"licensor\",\n \"processor\",\n + \ \"host\"\n ]\n }\n },\n \"url\": + {\n \"title\": \"Organization homepage\",\n \"type\": + \"string\",\n \"format\": \"url\"\n }\n }\n }\n + \ }\n }\n}" + headers: {} status: code: 200 message: OK diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index 6ce2ff3cb..2f80a42f5 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -155,6 +155,7 @@ def test_datetime_ISO8601_format(sample_item: Item) -> None: @pytest.mark.vcr() +@pytest.mark.xfail(reason="Null datetimes are ok in pystac v2") def test_null_datetime() -> None: item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) @@ -200,6 +201,7 @@ def test_get_assets() -> None: @pytest.mark.vcr() +@pytest.mark.xfail(reason="Null datetimes are ok in pystac v2") def test_null_datetime_constructor() -> None: item = pystac.Item.from_file(TestCases.get_path("data-files/item/sample-item.json")) with pytest.raises(pystac.STACError): @@ -429,18 +431,21 @@ def from_dict( _ = CustomItem.from_dict(item.to_dict()) +@pytest.mark.xfail(reason="pystac v2 is more permissive on input") def test_item_from_dict_raises_useful_error() -> None: item_dict = {"type": "Feature", "stac_version": "1.1.0", "id": "lalalalala"} with pytest.raises(pystac.STACError, match="Invalid Item: "): Item.from_dict(item_dict) +@pytest.mark.xfail(reason="pystac v2 is more permissive on input") def test_item_from_dict_with_missing_stac_version_raises_useful_error() -> None: item_dict = {"type": "Feature", "id": "lalalalala"} with pytest.raises(pystac.STACTypeError, match="'stac_version' is missing"): Item.from_dict(item_dict, migrate=False) +@pytest.mark.xfail(reason="pystac v2 is more permissive on input") def test_item_from_dict_with_missing_type_raises_useful_error() -> None: item_dict = {"stac_version": "0.8.0", "id": "lalalalala"} with pytest.raises(pystac.STACTypeError, match="'type' is missing"): From 37dfd046c6f3b65d62a7d70ead98e2a48e45082e Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 7 Jan 2026 17:26:04 -0700 Subject: [PATCH 05/60] feat: STACObject repr --- src/pystac/jsonschema.py | 13 +++++++++++-- src/pystac/stac_object.py | 6 +++++- tests/v1/test_item.py | 7 ++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/pystac/jsonschema.py b/src/pystac/jsonschema.py index 2098de62f..47402ec30 100644 --- a/src/pystac/jsonschema.py +++ b/src/pystac/jsonschema.py @@ -8,10 +8,13 @@ from urllib.request import Request import referencing.retrieval +from jsonschema.exceptions import ValidationError from jsonschema.protocols import Validator from jsonschema.validators import Draft7Validator from referencing import Registry +from pystac.errors import STACValidationError + from .constants import STAC_OBJECT_TYPE from .utils import get_stac_type, get_user_agent @@ -40,14 +43,20 @@ def validate_core( self, type: STAC_OBJECT_TYPE, version: str, data: dict[str, Any] ) -> None: validator = self.get_validator(type, version) - validator.validate(data) + try: + validator.validate(data) + except ValidationError as e: + raise STACValidationError(str(e)) from e def validate_extension(self, extension: str, data: dict[str, Any]) -> None: if extension not in self.cache: schema_data = json.loads(get_text(extension)) self.cache[extension] = Draft7Validator(schema_data, registry=self.registry) validator = self.cache[extension] - validator.validate(data) + try: + validator.validate(data) + except ValidationError as e: + raise STACValidationError(str(e)) from e @referencing.retrieval.to_cached_resource() diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index acd9b6762..df8723a5c 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -6,7 +6,7 @@ from collections.abc import Iterator from enum import StrEnum from pathlib import Path -from typing import TYPE_CHECKING, Any, ClassVar, Protocol +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, override from typing_extensions import deprecated @@ -379,6 +379,10 @@ def save_object( def clone[T: STACObject](self: T) -> T: return self.from_dict(self.to_dict()) + @override + def __repr__(self) -> str: + return f"<{self.__class__} id={self.id}>" + def _make_link( self, rel: str, target: STACObject, href: str, make_absolute: bool ) -> Link: diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index 2f80a42f5..bfe25d33b 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -510,7 +510,7 @@ def test_get_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: item = next(test_case_1_catalog.get_items(recursive=True)) item.add_derived_from("foo") with pytest.raises( - pystac.STACError, match="Link failed to resolve. Use get_links instead" + pystac.STACError # , match="Link failed to resolve. Use get_links instead" ): item.get_derived_from() @@ -522,7 +522,7 @@ def test_remove_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: item = next(test_case_1_catalog.get_items(recursive=True)) item.add_derived_from("foo") with pytest.raises( - pystac.STACError, match="Link failed to resolve. Use remove_links instead" + pystac.STACError # , match="Link failed to resolve. Use remove_links instead" ): item.remove_derived_from("foo") @@ -616,7 +616,8 @@ def test_non_hierarchical_relative_link() -> None: related_href = [link for link in a.links if link.rel == "related"][0].get_href() assert related_href is not None and not is_absolute_href(related_href) - assert a.target_in_hierarchy(b) + # @gadomski thinks this is a bad assertion, and has removed it for v2 + # assert a.target_in_hierarchy(b) assert root.target_in_hierarchy(next(b.get_items())) assert root.target_in_hierarchy(root) From d02e0ba51997a68e4dd61ef17e6537a2d87e60f6 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 06:27:48 -0700 Subject: [PATCH 06/60] docs: add some basic docs --- .readthedocs.yaml | 9 +- docs/api/asset.md | 8 + docs/api/catalog.md | 8 + docs/api/collection.md | 8 + docs/api/io.md | 34 ++++ docs/api/item.md | 8 + docs/api/link.md | 8 + docs/api/stac-object.md | 21 +++ docs/contributing.md | 180 ++++++++++++++++++++ docs/getting-started/installation.md | 100 +++++++++++ docs/getting-started/quickstart.md | 204 ++++++++++++++++++++++ docs/index.md | 81 +++++++++ docs/user-guide/catalogs.md | 175 +++++++++++++++++++ docs/user-guide/collections.md | 205 ++++++++++++++++++++++ docs/user-guide/concepts.md | 221 ++++++++++++++++++++++++ docs/user-guide/io.md | 244 +++++++++++++++++++++++++++ docs/user-guide/items.md | 188 +++++++++++++++++++++ mkdocs.yml | 93 ++++++++++ 18 files changed, 1790 insertions(+), 5 deletions(-) create mode 100644 docs/api/asset.md create mode 100644 docs/api/catalog.md create mode 100644 docs/api/collection.md create mode 100644 docs/api/io.md create mode 100644 docs/api/item.md create mode 100644 docs/api/link.md create mode 100644 docs/api/stac-object.md create mode 100644 docs/contributing.md create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/quickstart.md create mode 100644 docs/index.md create mode 100644 docs/user-guide/catalogs.md create mode 100644 docs/user-guide/collections.md create mode 100644 docs/user-guide/concepts.md create mode 100644 docs/user-guide/io.md create mode 100644 docs/user-guide/items.md create mode 100644 mkdocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 991e44fe0..d7b5cf968 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,7 +7,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.10" + python: "3.12" commands: # https://docs.readthedocs.io/en/stable/build-customization.html#install-dependencies-with-uv # with adaptations to use workspaces+projects instead of `uv pip` @@ -15,12 +15,11 @@ build: - asdf install uv latest - asdf global uv latest - uv sync --group docs - - uv run sphinx-build -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html + - uv run mkdocs build --site-dir $READTHEDOCS_OUTPUT/html formats: - # Temporarily disabling PDF downloads due to problem with nbsphinx in LateX builds - # - pdf - htmlzip -sphinx: +mkdocs: + configuration: mkdocs.yml fail_on_warning: false diff --git a/docs/api/asset.md b/docs/api/asset.md new file mode 100644 index 000000000..13814c285 --- /dev/null +++ b/docs/api/asset.md @@ -0,0 +1,8 @@ +# Asset + +Assets represent data files associated with Items. + +::: pystac.Asset + options: + show_root_heading: true + show_source: true diff --git a/docs/api/catalog.md b/docs/api/catalog.md new file mode 100644 index 000000000..6bb2d77a9 --- /dev/null +++ b/docs/api/catalog.md @@ -0,0 +1,8 @@ +# Catalog + +A STAC Catalog provides organizational structure for Items and other Catalogs. + +::: pystac.Catalog + options: + show_root_heading: true + show_source: true diff --git a/docs/api/collection.md b/docs/api/collection.md new file mode 100644 index 000000000..3f73b7830 --- /dev/null +++ b/docs/api/collection.md @@ -0,0 +1,8 @@ +# Collection + +A STAC Collection extends Catalog with additional metadata. + +::: pystac.Collection + options: + show_root_heading: true + show_source: true diff --git a/docs/api/io.md b/docs/api/io.md new file mode 100644 index 000000000..e51c1e345 --- /dev/null +++ b/docs/api/io.md @@ -0,0 +1,34 @@ +# I/O + +I/O functionality for reading and writing STAC objects. + +## Reading + +::: pystac.io.read_file + options: + show_root_heading: true + show_source: true + +## Reader Protocol + +::: pystac.reader.Reader + options: + show_root_heading: true + show_source: true + +::: pystac.reader.StandardLibraryReader + options: + show_root_heading: true + show_source: true + +## Writer Protocol + +::: pystac.writer.Writer + options: + show_root_heading: true + show_source: true + +::: pystac.writer.StandardLibraryWriter + options: + show_root_heading: true + show_source: true diff --git a/docs/api/item.md b/docs/api/item.md new file mode 100644 index 000000000..c8282a523 --- /dev/null +++ b/docs/api/item.md @@ -0,0 +1,8 @@ +# Item + +A STAC Item is a GeoJSON Feature with additional STAC properties. + +::: pystac.Item + options: + show_root_heading: true + show_source: true diff --git a/docs/api/link.md b/docs/api/link.md new file mode 100644 index 000000000..381bf1898 --- /dev/null +++ b/docs/api/link.md @@ -0,0 +1,8 @@ +# Link + +Links represent relationships between STAC objects. + +::: pystac.Link + options: + show_root_heading: true + show_source: true diff --git a/docs/api/stac-object.md b/docs/api/stac-object.md new file mode 100644 index 000000000..9d6e3d9e2 --- /dev/null +++ b/docs/api/stac-object.md @@ -0,0 +1,21 @@ +# STACObject + +`STACObject` is the abstract base class for all STAC objects (Item, Catalog, Collection). + +::: pystac.STACObject + options: + show_root_heading: true + show_source: true + members: + - from_dict + - from_file + - to_dict + - save + - render + - validate + - get_links + - add_link + - remove_links + - clear_links + - get_single_link + - get_root_link diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 000000000..5d08ccf79 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,180 @@ +# Contributing + +Thank you for your interest in contributing to PySTAC! + +## Development Setup + +PySTAC uses [uv](https://docs.astral.sh/uv/) for dependency management. + +### Clone and Install + +```bash +git clone https://github.com/stac-utils/pystac.git +cd pystac +uv sync +``` + +This installs PySTAC in editable mode along with all development and documentation dependencies. + +## Development Workflow + +### Running Tests + +```bash +# Run all tests +uv run pytest + +# Run specific test file +uv run pytest tests/test_item.py + +# Run tests matching a pattern +uv run pytest -k test_item_creation + +# Run with coverage +uv run pytest --cov=pystac +``` + +### Linting and Formatting + +PySTAC uses [ruff](https://docs.astral.sh/ruff/) for linting and formatting: + +```bash +# Check code +uv run ruff check + +# Format code +uv run ruff format + +# Fix auto-fixable issues +uv run ruff check --fix +``` + +### Type Checking + +PySTAC uses [basedpyright](https://docs.basedpyright.com/) with strict mode: + +```bash +uv run basedpyright +``` + +All code must pass type checking with strict mode enabled. + +### Pre-commit Hooks + +PySTAC uses pre-commit hooks to ensure code quality: + +```bash +# Install pre-commit hooks +uv run pre-commit install + +# Run hooks manually +uv run pre-commit run --all-files +``` + +Hooks include: +- `ruff` for linting and formatting +- `basedpyright` for type checking +- `codespell` for spell checking + +## Documentation + +### Building Documentation + +```bash +# Serve docs locally +uv run mkdocs serve + +# Build docs +uv run mkdocs build +``` + +The documentation uses mkdocs-material and mkdocstrings for API documentation. + +### Writing Documentation + +- User guides go in `docs/user-guide/` +- API documentation is auto-generated from docstrings +- Use Google-style docstrings + +Example docstring: + +```python +def example_function(param: str) -> int: + """Short description. + + Longer description with more details. + + Args: + param: Description of parameter. + + Returns: + Description of return value. + + Raises: + ValueError: When param is invalid. + """ + pass +``` + +## Pull Request Process + +1. **Fork and clone** the repository +2. **Create a branch** for your changes +3. **Make your changes** with tests +4. **Run tests and linting** locally +5. **Push and create a pull request** +6. **Respond to review feedback** + +### PR Guidelines + +- Write clear commit messages +- Include tests for new functionality +- Update documentation as needed +- Ensure all tests pass +- Keep PRs focused on a single concern + +## Code Style + +- Follow PEP 8 (enforced by ruff) +- Use type hints for all public APIs +- Write descriptive variable and function names +- Keep functions focused and small +- Add docstrings for public APIs + +## Testing Guidelines + +- Write tests for all new features +- Test edge cases and error conditions +- Use descriptive test names +- Keep tests simple and focused +- Mock external dependencies + +Example test: + +```python +def test_item_creation(): + """Test that items can be created with required fields.""" + item = pystac.Item( + id="test-item", + geometry={"type": "Point", "coordinates": [0, 0]}, + bbox=[0, 0, 0, 0], + datetime=datetime.now(timezone.utc), + properties={} + ) + assert item.id == "test-item" + assert item.geometry is not None +``` + +## Version Support + +PySTAC requires Python 3.12 or later. + +## Questions? + +- Open an [issue](https://github.com/stac-utils/pystac/issues) +- Ask in [discussions](https://github.com/radiantearth/stac-spec/discussions/categories/stac-software) +- Join the [Gitter chat](https://gitter.im/SpatioTemporal-Asset-Catalog/python) + +## Code of Conduct + +This project adheres to the Contributor Covenant [Code of Conduct](https://github.com/stac-utils/pystac/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 000000000..ef3059ea8 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,100 @@ +# Installation + +## Requirements + +PySTAC requires Python 3.12 or later. + +## Install from PyPI (Recommended) + +The easiest way to install PySTAC is from PyPI using pip: + +```bash +python -m pip install pystac +``` + +### Optional Dependencies + +PySTAC has several optional dependency groups for additional features: + +#### Validation + +If you want to validate STAC objects against JSON schemas: + +```bash +python -m pip install 'pystac[validation]' +``` + +This installs: +- `jsonschema` - For JSON schema validation +- `referencing` - For schema reference resolution + +#### Jinja2 (Jupyter Display) + +If you're using Jupyter notebooks and want pretty display of STAC objects: + +```bash +python -m pip install 'pystac[jinja2]' +``` + +#### Object Storage (obstore) + +For working with cloud object storage (S3, GCS, Azure): + +```bash +python -m pip install 'pystac[obstore]' +``` + +This installs: +- `obstore` - For object storage access +- `obspec` - For object storage specifications + +#### All Optional Dependencies + +To install all optional dependencies: + +```bash +python -m pip install 'pystac[validation,jinja2,obstore]' +``` + +## Install from Source + +To install the latest development version from GitHub: + +```bash +git clone https://github.com/stac-utils/pystac.git +cd pystac +python -m pip install . +``` + +### Development Installation + +If you're contributing to PySTAC, install with development dependencies using [uv](https://docs.astral.sh/uv/): + +```bash +git clone https://github.com/stac-utils/pystac.git +cd pystac +uv sync +``` + +This installs PySTAC in editable mode along with all development and documentation dependencies. + +## Verifying Installation + +You can verify your installation by checking the version: + +```python +import pystac +print(pystac.__version__) +``` + +Or by running the test suite: + +```bash +uv run pytest # if installed with uv +# or +python -m pytest # if installed with pip +``` + +## Next Steps + +Now that you have PySTAC installed, check out the [Quickstart Guide](quickstart.md) to learn how to use it. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 000000000..0687d8964 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,204 @@ +# Quickstart + +This guide will walk you through the basics of using PySTAC to work with STAC catalogs, collections, and items. + +## Reading STAC Objects + +### Reading an Item + +```python +import pystac + +# Read a STAC item from a file +item = pystac.Item.from_file("path/to/item.json") + +# Access basic properties +print(f"Item ID: {item.id}") +print(f"Datetime: {item.datetime}") +print(f"Geometry: {item.geometry}") +print(f"Bounding Box: {item.bbox}") + +# Access custom properties +print(f"Properties: {item.properties}") +``` + +### Reading a Catalog + +```python +# Read a catalog +catalog = pystac.Catalog.from_file("path/to/catalog.json") + +print(f"Catalog ID: {catalog.id}") +print(f"Description: {catalog.description}") + +# Iterate through items +for item in catalog.get_items(): + print(f"Item: {item.id}") + +# Recursively walk through the catalog +for child in catalog.walk(): + if isinstance(child, pystac.Item): + print(f"Found item: {child.id}") + elif isinstance(child, pystac.Collection): + print(f"Found collection: {child.id}") + else: + print(f"Found catalog: {child.id}") +``` + +### Reading a Collection + +```python +# Read a collection +collection = pystac.Collection.from_file("path/to/collection.json") + +print(f"Collection ID: {collection.id}") +print(f"License: {collection.license}") +print(f"Extent: {collection.extent}") + +# Access items in the collection +for item in collection.get_items(): + print(f"Item: {item.id}") +``` + +## Creating STAC Objects + +### Creating an Item + +```python +import pystac +from datetime import datetime, timezone + +# Create a new item +item = pystac.Item( + id="my-item", + geometry={ + "type": "Point", + "coordinates": [-105.0, 40.0] + }, + bbox=[-105.0, 40.0, -105.0, 40.0], + datetime=datetime.now(timezone.utc), + properties={"key": "value"} +) + +# Add an asset +item.assets["thumbnail"] = pystac.Asset( + href="https://example.com/thumbnail.png", + type="image/png", + roles=["thumbnail"] +) +``` + +### Creating a Catalog + +```python +# Create a catalog +catalog = pystac.Catalog( + id="my-catalog", + description="My STAC catalog" +) + +# Add items +catalog.add_item(item) + +# Add a child catalog +child_catalog = pystac.Catalog( + id="child-catalog", + description="A child catalog" +) +catalog.add_child(child_catalog) +``` + +### Creating a Collection + +```python +# Create a collection +collection = pystac.Collection( + id="my-collection", + description="My STAC collection", + license="CC-BY-4.0", + extent=pystac.Extent( + spatial=pystac.SpatialExtent([[-180, -90, 180, 90]]), + temporal=pystac.TemporalExtent([[datetime.now(timezone.utc), None]]) + ) +) + +# Add items +collection.add_item(item) +``` + +## Working with Links + +```python +# Access links +for link in item.links: + print(f"Link rel: {link.rel}, href: {link.href}") + +# Add a link +item.add_link(pystac.Link( + rel="related", + target="https://example.com/related.json" +)) + +# Get specific links +parent_link = item.get_single_link("parent") +if parent_link: + print(f"Parent: {parent_link.href}") +``` + +## Working with Assets + +```python +# Access assets +for key, asset in item.assets.items(): + print(f"Asset {key}:") + print(f" href: {asset.href}") + print(f" type: {asset.type}") + print(f" roles: {asset.roles}") + +# Add an asset +item.assets["data"] = pystac.Asset( + href="https://example.com/data.tif", + type="image/tiff; application=geotiff", + roles=["data"] +) + +# Remove an asset +del item.assets["thumbnail"] +``` + +## Saving STAC Objects + +```python +# Render sets hrefs for the catalog and all children +catalog.render(root="/path/to/output") + +# Save writes all objects to disk +catalog.save() + +# Or save a single object +item.save("/path/to/item.json") +``` + +## Validation + +If you've installed the validation dependencies, you can validate STAC objects: + +```python +from pystac.validate import JsonschemaValidator + +validator = JsonschemaValidator() + +# Validate an item +errors = validator.validate(item.to_dict()) +if errors: + for error in errors: + print(f"Validation error: {error}") +else: + print("Item is valid!") +``` + +## Next Steps + +- Learn more about core concepts in the [User Guide](../user-guide/concepts.md) +- Explore the [API Reference](../api/stac-object.md) for detailed documentation +- Check out specific guides for [Items](../user-guide/items.md), [Catalogs](../user-guide/catalogs.md), and [Collections](../user-guide/collections.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..553e31b51 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,81 @@ +# PySTAC + +[![Build Status](https://github.com/stac-utils/pystac/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/stac-utils/pystac/actions/workflows/continuous-integration.yml) +[![PyPI version](https://badge.fury.io/py/pystac.svg)](https://badge.fury.io/py/pystac) +[![Documentation](https://readthedocs.org/projects/pystac/badge/?version=latest)](https://pystac.readthedocs.io/en/latest/) + +PySTAC is a Python library for working with the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org) specification. + +## What is STAC? + +The SpatioTemporal Asset Catalog (STAC) specification provides a common language to describe geospatial information, so it can more easily be worked with, indexed, and discovered. STAC is a specification for describing geospatial data using JSON and GeoJSON. + +## What is PySTAC? + +PySTAC is a library that provides a Python API for working with STAC catalogs, collections, and items. It allows you to: + +- **Read** STAC catalogs, collections, and items from files or URLs +- **Create** new STAC objects programmatically +- **Modify** existing STAC objects +- **Write** STAC objects to disk +- **Validate** STAC objects against the specification +- **Traverse** STAC hierarchies + +## PySTAC v2.0 + +This is the v2.0 rewrite of PySTAC with improved design principles: + +- **Stable core APIs**: Keep data structure APIs (Item, Catalog, Collection) consistent +- **Flexible validation**: Accept almost anything with warnings for corrections +- **Simplified implementation**: Do fewer things at once, single responsibility methods +- **Low dependencies**: Minimal dependencies by default, optional extras for additional features + +## Quick Example + +```python +import pystac + +# Read a STAC item +item = pystac.Item.from_file("path/to/item.json") + +# Access item properties +print(f"Item ID: {item.id}") +print(f"Datetime: {item.datetime}") + +# Access assets +for key, asset in item.assets.items(): + print(f"Asset {key}: {asset.href}") + +# Create a new catalog +catalog = pystac.Catalog( + id="my-catalog", + description="A catalog of imagery" +) + +# Add the item to the catalog +catalog.add_item(item) + +# Save the catalog +catalog.render(root="/path/to/output") +catalog.save() +``` + +## Features + +- **Core STAC Types**: Full support for Item, Catalog, and Collection +- **Links and Assets**: Complete link and asset management +- **I/O**: Flexible I/O system with pluggable readers and writers +- **Validation**: Optional validation against STAC JSON schemas +- **Extensions**: Preserve extension data through extra_fields +- **Type Safety**: Full type hints with strict type checking + +## Get Started + +Check out the [Installation Guide](getting-started/installation.md) to install PySTAC, then follow the [Quickstart](getting-started/quickstart.md) to learn the basics. + +## Links + +- [GitHub Repository](https://github.com/stac-utils/pystac) +- [PyPI Package](https://pypi.org/project/pystac/) +- [STAC Specification](https://stacspec.org) +- [Issue Tracker](https://github.com/stac-utils/pystac/issues) diff --git a/docs/user-guide/catalogs.md b/docs/user-guide/catalogs.md new file mode 100644 index 000000000..921f26cf7 --- /dev/null +++ b/docs/user-guide/catalogs.md @@ -0,0 +1,175 @@ +# Working with Catalogs + +A STAC Catalog provides organizational structure for Items and other Catalogs. This guide covers creating, traversing, and managing STAC Catalogs. + +## Creating Catalogs + +```python +import pystac + +catalog = pystac.Catalog( + id="my-catalog", + description="A catalog of satellite imagery", + title="My Satellite Catalog" # Optional +) +``` + +## Adding Items and Children + +```python +# Add an item +item = pystac.Item(...) +catalog.add_item(item) + +# Add a child catalog +child = pystac.Catalog( + id="child-catalog", + description="A subcatalog" +) +catalog.add_child(child) + +# Add a collection +collection = pystac.Collection(...) +catalog.add_child(collection) +``` + +## Traversing Catalogs + +### Get Direct Children + +```python +# Get all direct children (catalogs and collections) +for child in catalog.get_children(): + print(f"Child: {child.id}") + +# Get all direct items +for item in catalog.get_items(): + print(f"Item: {item.id}") +``` + +### Recursive Traversal + +```python +# Walk the entire catalog tree +for obj in catalog.walk(): + if isinstance(obj, pystac.Item): + print(f"Item: {obj.id}") + elif isinstance(obj, pystac.Collection): + print(f"Collection: {obj.id}") + elif isinstance(obj, pystac.Catalog): + print(f"Catalog: {obj.id}") +``` + +## Reading Catalogs + +```python +# Read from file +catalog = pystac.Catalog.from_file("path/to/catalog.json") + +# Read from URL +catalog = pystac.Catalog.from_file("https://example.com/catalog.json") + +# Traverse after reading +for item in catalog.get_items(recursive=True): + print(f"Found item: {item.id}") +``` + +## Catalog Structure + +### Links + +Catalogs use links to connect to other objects: + +- `root`: Link to the root catalog +- `parent`: Link to parent catalog (if not root) +- `child`: Links to child catalogs/collections +- `item`: Links to items +- `self`: Canonical location + +```python +# Get root +root_link = catalog.get_root_link() +if root_link: + root = root_link.get_stac_object() + +# Get parent +parent_link = catalog.get_single_link("parent") +if parent_link: + parent = parent_link.get_stac_object() +``` + +## Saving Catalogs + +```python +# Render sets hrefs for catalog and all children +catalog.render(root="/path/to/output") + +# Save writes all objects to disk +catalog.save() +``` + +### Render Strategies + +The default renderer creates a directory structure: + +``` +output/ +├── catalog.json +├── child-catalog/ +│ ├── catalog.json +│ └── item-1/ +│ └── item-1.json +└── collection-1/ + ├── collection.json + └── item-2/ + └── item-2.json +``` + +## Catalog Organization Patterns + +### Geographic Organization + +```python +root = pystac.Catalog(id="root", description="Root catalog") + +# Organize by region +north_america = pystac.Catalog(id="north-america", description="North America") +europe = pystac.Catalog(id="europe", description="Europe") + +root.add_child(north_america) +root.add_child(europe) + +# Add items to appropriate regions +north_america.add_item(usa_item) +europe.add_item(france_item) +``` + +### Temporal Organization + +```python +root = pystac.Catalog(id="root", description="Root catalog") + +# Organize by year +year_2023 = pystac.Catalog(id="2023", description="2023 imagery") +year_2024 = pystac.Catalog(id="2024", description="2024 imagery") + +root.add_child(year_2023) +root.add_child(year_2024) +``` + +## Batch Operations + +```python +# Set STAC version for all objects +catalog.set_stac_version("1.1.0") + +# Clear all links of a specific type +for child in catalog.walk(): + child.clear_links("derived_from") +``` + +## Next Steps + +- Learn about [Collections](collections.md) +- Learn about [Items](items.md) +- Explore the [Catalog API Reference](../api/catalog.md) diff --git a/docs/user-guide/collections.md b/docs/user-guide/collections.md new file mode 100644 index 000000000..7572b2157 --- /dev/null +++ b/docs/user-guide/collections.md @@ -0,0 +1,205 @@ +# Working with Collections + +A STAC Collection extends Catalog with additional metadata about a group of related Items. This guide covers creating and managing Collections. + +## Creating Collections + +```python +import pystac +from datetime import datetime, timezone + +collection = pystac.Collection( + id="my-collection", + description="Satellite imagery collection", + title="My Satellite Collection", # Optional + license="CC-BY-4.0", + extent=pystac.Extent( + spatial=pystac.SpatialExtent([[-180, -90, 180, 90]]), + temporal=pystac.TemporalExtent([ + [datetime(2020, 1, 1, tzinfo=timezone.utc), None] + ]) + ) +) +``` + +## Collection Properties + +### Required Properties + +- **id**: Unique identifier +- **description**: Detailed description +- **license**: License identifier or "proprietary" +- **extent**: Spatial and temporal extent of all items + +### Optional Properties + +```python +collection.title = "Human-readable title" +collection.keywords = ["satellite", "imagery", "earth observation"] +collection.providers = [ + pystac.Provider( + name="Example Org", + roles=["producer", "licensor"], + url="https://example.com" + ) +] +``` + +## Extent + +The extent describes the spatial and temporal coverage: + +### Spatial Extent + +```python +# Single bounding box +spatial_extent = pystac.SpatialExtent( + bboxes=[[-180, -90, 180, 90]] +) + +# Multiple bounding boxes +spatial_extent = pystac.SpatialExtent( + bboxes=[ + [-180, -90, -90, 0], # Western hemisphere, southern half + [-90, 0, 0, 90] # Northwestern quadrant + ] +) +``` + +### Temporal Extent + +```python +# Closed time range +temporal_extent = pystac.TemporalExtent( + intervals=[ + [ + datetime(2020, 1, 1, tzinfo=timezone.utc), + datetime(2024, 12, 31, tzinfo=timezone.utc) + ] + ] +) + +# Open-ended (still collecting) +temporal_extent = pystac.TemporalExtent( + intervals=[ + [datetime(2020, 1, 1, tzinfo=timezone.utc), None] + ] +) +``` + +## Summaries + +Summaries provide aggregate information about item properties: + +```python +collection.summaries = pystac.Summaries({ + "platform": ["satellite-1", "satellite-2"], + "instruments": ["camera-1", "camera-2"], + "gsd": {"minimum": 10, "maximum": 30}, + "eo:cloud_cover": {"minimum": 0, "maximum": 50} +}) +``` + +## Item Assets + +Define common assets that appear in items: + +```python +collection.item_assets = { + "data": pystac.extensions.item_assets.AssetDefinition({ + "type": "image/tiff; application=geotiff", + "roles": ["data"], + "title": "Data" + }), + "thumbnail": pystac.extensions.item_assets.AssetDefinition({ + "type": "image/png", + "roles": ["thumbnail"], + "title": "Thumbnail" + }) +} +``` + +## Adding Items + +```python +# Create and add an item +item = pystac.Item( + id="item-1", + geometry=geometry, + bbox=bbox, + datetime=datetime.now(timezone.utc), + properties={} +) + +# Add to collection (sets item.collection_id automatically) +collection.add_item(item) + +# Update extent to include new items +collection.update_extent_from_items() +``` + +## Reading Collections + +```python +# From file +collection = pystac.Collection.from_file("path/to/collection.json") + +# From URL +collection = pystac.Collection.from_file("https://example.com/collection.json") + +# Access items +for item in collection.get_items(): + print(f"Item: {item.id}") + print(f"Collection: {item.collection_id}") +``` + +## Collection vs Catalog + +When to use Collection vs Catalog: + +**Use Collection when:** +- Items are related and share characteristics +- You want to provide aggregate metadata (extent, summaries) +- You need license information +- Items should be discoverable as a cohesive dataset + +**Use Catalog when:** +- You only need organizational structure +- Items are heterogeneous +- You don't need collection-specific metadata + +## Updating Extent + +```python +# Manually update after adding items +collection.update_extent_from_items() + +# Or calculate from specific items +items = [item1, item2, item3] +spatial_extent = pystac.SpatialExtent.from_items(items) +temporal_extent = pystac.TemporalExtent.from_items(items) + +collection.extent = pystac.Extent( + spatial=spatial_extent, + temporal=temporal_extent +) +``` + +## Saving Collections + +```python +# Render and save +collection.render(root="/path/to/output") +collection.save() + +# Or add to catalog and save together +catalog.add_child(collection) +catalog.render(root="/path/to/output") +catalog.save() +``` + +## Next Steps + +- Learn about [Items](items.md) +- Learn about [Catalogs](catalogs.md) +- Explore the [Collection API Reference](../api/collection.md) diff --git a/docs/user-guide/concepts.md b/docs/user-guide/concepts.md new file mode 100644 index 000000000..d5db8176d --- /dev/null +++ b/docs/user-guide/concepts.md @@ -0,0 +1,221 @@ +# Core Concepts + +This guide introduces the core concepts and architecture of PySTAC. + +## STAC Objects + +PySTAC provides Python classes for the main STAC specification types: + +### Item + +A **STAC Item** is a GeoJSON Feature with additional STAC-specific properties. It represents a single spatiotemporal asset or scene. + +Key properties: +- `id`: Unique identifier +- `geometry`: GeoJSON geometry +- `bbox`: Bounding box +- `datetime` or `start_datetime`/`end_datetime`: Temporal information +- `properties`: Additional metadata +- `assets`: Dictionary of associated files +- `links`: Relationships to other STAC objects + +### Catalog + +A **STAC Catalog** is a collection of STAC Items and/or other Catalogs. It provides organizational structure. + +Key properties: +- `id`: Unique identifier +- `description`: Human-readable description +- `links`: Relationships to children, items, and parent + +### Collection + +A **STAC Collection** extends Catalog with additional metadata about a group of related Items. + +Key properties: +- All Catalog properties, plus: +- `extent`: Spatial and temporal extent of all items +- `license`: License information +- `providers`: Organizations involved +- `summaries`: Aggregate information about item properties +- `item_assets`: Common asset definitions + +## Object Hierarchy + +``` +STACObject (Abstract Base) +├── Container (Abstract Base) +│ ├── Catalog +│ └── Collection +└── Item +``` + +All STAC objects inherit from `STACObject`, which provides: +- Serialization/deserialization (`from_dict()`, `to_dict()`, `from_file()`) +- Link management +- File I/O operations + +`Container` is an abstract base for Catalog and Collection, providing: +- Hierarchy traversal (`walk()`, `get_children()`, `get_items()`) +- Child-parent relationship management +- Batch operations + +## Links + +Links connect STAC objects together, forming a graph structure. PySTAC uses the `Link` class to represent relationships. + +Common link relation types: +- `root`: Link to the root catalog +- `parent`: Link to the parent catalog/collection +- `child`: Link to a child catalog/collection +- `item`: Link to an item +- `self`: Canonical location of the object +- `collection`: Link from an item to its collection + +### Lazy Loading + +Links support lazy loading - the target object is only loaded when accessed via `get_stac_object()`: + +```python +parent_link = item.get_single_link("parent") +if parent_link: + # Object is loaded only when needed + parent = parent_link.get_stac_object() +``` + +## Assets + +Assets represent the actual data files associated with an Item. Each asset has: + +- `href`: URL or path to the file +- `type`: Media type (MIME type) +- `title`: Human-readable title +- `description`: Detailed description +- `roles`: Semantic roles (e.g., "data", "thumbnail", "metadata") + +```python +asset = pystac.Asset( + href="https://example.com/image.tif", + type="image/tiff; application=geotiff", + roles=["data"] +) +``` + +## I/O System + +PySTAC v2.0 uses a protocol-based I/O system for flexibility: + +### Read Protocol + +- `read_json_from_path(path)`: Read from filesystem +- `read_json_from_url(url)`: Read from HTTP/HTTPS + +### Write Protocol + +- `write_json_to_path(data, path)`: Write to filesystem +- `write_json_to_url(data, url)`: Write to URL (custom implementations) + +The default implementation uses Python's standard library for filesystem operations and `urllib` for HTTP. + +### Custom I/O + +You can provide custom readers/writers for cloud storage or other backends: + +```python +# Custom implementation using duck typing +class S3Reader: + def read_json_from_path(self, path): + # S3 implementation + pass + + def read_json_from_url(self, url): + # S3 implementation + pass + +# Use with PySTAC +item = pystac.Item.from_file("s3://bucket/item.json", reader=S3Reader()) +``` + +## Rendering + +Before saving, STAC objects need their `href` properties set. The `render()` method handles this using a rendering strategy. + +```python +# Set hrefs using best practices layout +catalog.render(root="/path/to/output") + +# Then save all objects +catalog.save() +``` + +The default `BestPracticesRenderer` creates a standard layout: +- Items: `{id}/{id}.json` +- Catalogs: `{id}/catalog.json` +- Collections: `{id}/collection.json` + +## Extensions + +STAC Extensions add additional fields and functionality. PySTAC v2.0 uses a simplified approach: + +- `stac_extensions`: List of extension URLs +- `extra_fields`: Dictionary for extension-specific fields + +```python +# Extension data is preserved automatically +item = pystac.Item.from_dict({ + "type": "Feature", + "stac_extensions": ["https://stac-extensions.github.io/eo/v1.0.0/schema.json"], + "properties": { + "eo:cloud_cover": 10.5 + }, + # ... other fields +}) + +# Access extension data +cloud_cover = item.properties.get("eo:cloud_cover") + +# Or via extra_fields for root-level extension properties +# item.extra_fields["custom:field"] +``` + +## Validation + +PySTAC provides optional validation against JSON schemas: + +```python +from pystac.validate import JsonschemaValidator + +validator = JsonschemaValidator() +errors = validator.validate(item.to_dict()) +``` + +Validation requires the `validation` optional dependencies: + +```bash +pip install 'pystac[validation]' +``` + +## PySTAC v2.0 Design Principles + +### Stable Core APIs + +Core data structure APIs (Item, Catalog, Collection) remain stable across versions. + +### Relaxed Validation + +Initializers accept almost anything, with warnings for corrections. This makes it easier to work with imperfect real-world data. + +### Single Responsibility + +Methods do one thing well, rather than combining multiple operations. + +### Low Dependencies + +Minimal required dependencies. Additional features available via optional dependency groups. + +## Next Steps + +- [Working with Items](items.md) +- [Working with Catalogs](catalogs.md) +- [Working with Collections](collections.md) +- [Reading and Writing](io.md) diff --git a/docs/user-guide/io.md b/docs/user-guide/io.md new file mode 100644 index 000000000..9756a16c1 --- /dev/null +++ b/docs/user-guide/io.md @@ -0,0 +1,244 @@ +# Reading and Writing + +This guide covers PySTAC's I/O system for reading and writing STAC objects. + +## Reading STAC Objects + +### From Files + +```python +import pystac + +# Read any STAC object (auto-detects type) +obj = pystac.STACObject.from_file("path/to/stac.json") + +# Read specific types +item = pystac.Item.from_file("path/to/item.json") +catalog = pystac.Catalog.from_file("path/to/catalog.json") +collection = pystac.Collection.from_file("path/to/collection.json") +``` + +### From URLs + +```python +# Read from HTTP/HTTPS +item = pystac.Item.from_file("https://example.com/item.json") + +# Works with any accessible URL +catalog = pystac.Catalog.from_file("https://example.com/catalog.json") +``` + +### From Dictionaries + +```python +# From Python dict +item_dict = { + "type": "Feature", + "stac_version": "1.1.0", + "id": "example", + # ... other fields +} + +item = pystac.Item.from_dict(item_dict) +``` + +## Writing STAC Objects + +### Save Individual Objects + +```python +# Save to specific path +item.save("/path/to/output/item.json") + +# Save with pretty printing (default) +item.save("/path/to/output/item.json") +``` + +### Render and Save Hierarchies + +When working with catalogs, use `render()` to set hrefs before saving: + +```python +# Create catalog structure +catalog = pystac.Catalog(id="root", description="Root catalog") +catalog.add_item(item1) +catalog.add_item(item2) + +# Render sets hrefs for all objects +catalog.render(root="/path/to/output") + +# Save writes all objects +catalog.save() +``` + +## Custom I/O + +PySTAC v2.0 uses protocol-based I/O for flexibility. + +### Read Protocol + +Implement these methods for custom reading: + +```python +class CustomReader: + def read_json_from_path(self, path: str) -> dict: + """Read JSON from a path.""" + # Custom implementation + pass + + def read_json_from_url(self, url: str) -> dict: + """Read JSON from a URL.""" + # Custom implementation + pass +``` + +### Write Protocol + +Implement these methods for custom writing: + +```python +class CustomWriter: + def write_json_to_path(self, data: dict, path: str) -> None: + """Write JSON to a path.""" + # Custom implementation + pass + + def write_json_to_url(self, data: dict, url: str) -> None: + """Write JSON to a URL.""" + # Custom implementation + pass +``` + +### Using Custom I/O + +```python +# Use custom reader +reader = CustomReader() +item = pystac.Item.from_file("custom://path", reader=reader) + +# Use custom writer +writer = CustomWriter() +item.save("custom://path", writer=writer) +``` + +## Cloud Storage + +### Using obstore + +For S3, GCS, and Azure Blob Storage: + +```bash +pip install 'pystac[obstore]' +``` + +```python +from pystac.io import ObstoreReader, ObstoreWriter + +# Configure for S3 +reader = ObstoreReader("s3://bucket-name") +writer = ObstoreWriter("s3://bucket-name") + +# Read from S3 +item = pystac.Item.from_file("s3://bucket-name/item.json", reader=reader) + +# Write to S3 +item.save("s3://bucket-name/output/item.json", writer=writer) +``` + +## Rendering Strategies + +The renderer controls how hrefs are set when saving. + +### Best Practices Renderer (Default) + +```python +from pystac.render import BestPracticesRenderer + +renderer = BestPracticesRenderer() +catalog.render(root="/path/to/output", renderer=renderer) +``` + +Creates this structure: +``` +output/ +├── catalog.json +├── collection-1/ +│ ├── collection.json +│ └── item-1/ +│ └── item-1.json +└── catalog-2/ + └── catalog.json +``` + +### Custom Renderer + +```python +class CustomRenderer: + def render(self, obj, root): + """Set href for object and render children.""" + # Custom layout logic + pass + +# Use custom renderer +catalog.render(root="/path/to/output", renderer=CustomRenderer()) +``` + +## Lazy Loading + +Links support lazy loading to avoid loading entire hierarchies: + +```python +# Read catalog +catalog = pystac.Catalog.from_file("path/to/catalog.json") + +# Links are not resolved until accessed +for link in catalog.links: + if link.rel == "child": + # Object loaded only when needed + child = link.get_stac_object() +``` + +## Href Resolution + +PySTAC handles absolute and relative hrefs: + +```python +from pystac.utils import make_absolute_href, make_relative_href + +# Make absolute +absolute = make_absolute_href("./item.json", "/base/path/catalog.json") +# Result: "/base/path/item.json" + +# Make relative +relative = make_relative_href("/path/to/item.json", "/path/catalog.json") +# Result: "./to/item.json" +``` + +## Error Handling + +```python +try: + item = pystac.Item.from_file("path/to/item.json") +except FileNotFoundError: + print("File not found") +except pystac.STACError as e: + print(f"STAC error: {e}") +``` + +## Performance Tips + +1. **Use lazy loading** for large catalogs +2. **Limit recursion depth** when traversing +3. **Use specific type methods** instead of auto-detection +4. **Consider caching** for remote catalogs + +```python +# Specify recursion limit +for item in catalog.get_items(recursive=True, max_depth=2): + process(item) +``` + +## Next Steps + +- Learn about [Core Concepts](concepts.md) +- Explore the [I/O API Reference](../api/io.md) diff --git a/docs/user-guide/items.md b/docs/user-guide/items.md new file mode 100644 index 000000000..e2d018a1a --- /dev/null +++ b/docs/user-guide/items.md @@ -0,0 +1,188 @@ +# Working with Items + +A STAC Item is a GeoJSON Feature that represents a single spatiotemporal asset or scene. This guide covers creating, reading, and manipulating STAC Items. + +## Creating Items + +```python +import pystac +from datetime import datetime, timezone + +item = pystac.Item( + id="example-item", + geometry={ + "type": "Polygon", + "coordinates": [[ + [-105.0, 40.0], + [-104.0, 40.0], + [-104.0, 39.0], + [-105.0, 39.0], + [-105.0, 40.0] + ]] + }, + bbox=[-105.0, 39.0, -104.0, 40.0], + datetime=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc), + properties={ + "platform": "satellite-1", + "instruments": ["camera"] + } +) +``` + +## Item Properties + +### Required Properties + +- **id**: Unique identifier for the item +- **geometry**: GeoJSON geometry object (or `None` for non-spatial items) +- **bbox**: Bounding box `[min_lon, min_lat, max_lon, max_lat]` +- **datetime**: Single datetime, or `None` if using datetime range +- **properties**: Dictionary of additional metadata + +### Datetime Handling + +Items can have either a single datetime or a datetime range: + +```python +# Single datetime +item = pystac.Item( + id="single-time", + geometry=geometry, + bbox=bbox, + datetime=datetime.now(timezone.utc), + properties={} +) + +# Datetime range +item = pystac.Item( + id="time-range", + geometry=geometry, + bbox=bbox, + datetime=None, # Must be None for ranges + properties={ + "start_datetime": "2024-01-01T00:00:00Z", + "end_datetime": "2024-01-02T00:00:00Z" + } +) +``` + +## Adding Assets + +Assets represent the actual data files: + +```python +# Add a data asset +item.assets["data"] = pystac.Asset( + href="https://example.com/data.tif", + type="image/tiff; application=geotiff", + title="COG Data", + roles=["data"] +) + +# Add a thumbnail +item.assets["thumbnail"] = pystac.Asset( + href="https://example.com/thumbnail.png", + type="image/png", + roles=["thumbnail"] +) + +# Add metadata +item.assets["metadata"] = pystac.Asset( + href="https://example.com/metadata.xml", + type="application/xml", + roles=["metadata"] +) +``` + +## Links + +Items typically have links to: +- Parent catalog or collection +- Self (canonical location) +- Collection (if part of a collection) + +```python +# Link to collection +item.add_link(pystac.Link( + rel="collection", + target="https://example.com/collection.json" +)) + +# Access links +collection_link = item.get_single_link("collection") +if collection_link: + collection = collection_link.get_stac_object() +``` + +## Collection Membership + +Items can reference their collection: + +```python +# Set collection reference +item.collection_id = "my-collection" + +# Or when adding to a collection +collection.add_item(item) # Automatically sets collection_id +``` + +## Reading Items + +```python +# From file +item = pystac.Item.from_file("path/to/item.json") + +# From dict +item = pystac.Item.from_dict(item_dict) + +# From URL +item = pystac.Item.from_file("https://example.com/item.json") +``` + +## Saving Items + +```python +# Save to specific path +item.save("/path/to/output/item.json") + +# Or render with parent and save +catalog.add_item(item) +catalog.render(root="/path/to/output") +catalog.save() # Saves catalog and all items +``` + +## Item Validation + +```python +from pystac.validate import JsonschemaValidator + +validator = JsonschemaValidator() +errors = validator.validate(item.to_dict()) + +if errors: + for error in errors: + print(f"Error: {error}") +else: + print("Item is valid!") +``` + +## Common Metadata + +Items often include common metadata fields: + +```python +item.properties.update({ + "title": "Example Scene", + "description": "An example satellite scene", + "platform": "satellite-1", + "instruments": ["camera-1"], + "constellation": "sat-constellation", + "gsd": 10.0 # Ground sample distance in meters +}) +``` + +## Next Steps + +- Learn about [Catalogs](catalogs.md) +- Learn about [Collections](collections.md) +- Explore the [Item API Reference](../api/item.md) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..28a0b91e2 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,93 @@ +site_name: PySTAC +site_description: Python library for working with the SpatioTemporal Asset Catalog (STAC) specification +site_url: https://pystac.readthedocs.io +repo_url: https://github.com/stac-utils/pystac +repo_name: stac-utils/pystac +edit_uri: edit/main/docs/ + +theme: + name: material + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - content.code.copy + - content.code.annotate + - navigation.tabs + - navigation.sections + - navigation.top + - navigation.tracking + - search.suggest + - search.highlight + - toc.follow + +plugins: + - search + - mkdocstrings: + handlers: + python: + options: + docstring_style: google + show_source: true + show_root_heading: true + show_root_full_path: false + show_symbol_type_heading: true + show_symbol_type_toc: true + members_order: source + inherited_members: false + filters: + - "!^_" # exclude private members + merge_init_into_class: true + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - toc: + permalink: true + +nav: + - Home: index.md + - Getting Started: + - Installation: getting-started/installation.md + - Quickstart: getting-started/quickstart.md + - User Guide: + - Core Concepts: user-guide/concepts.md + - Working with Items: user-guide/items.md + - Working with Catalogs: user-guide/catalogs.md + - Working with Collections: user-guide/collections.md + - Reading and Writing: user-guide/io.md + - API Reference: + - STACObject: api/stac-object.md + - Item: api/item.md + - Catalog: api/catalog.md + - Collection: api/collection.md + - Link: api/link.md + - Asset: api/asset.md + - I/O: api/io.md + - Contributing: contributing.md + +watch: + - src/pystac From 4108f5e85e18c11f2209b5ab79bd495bd3bd7cf9 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 06:39:49 -0700 Subject: [PATCH 07/60] fix: reset validator on each test --- src/pystac/jsonschema.py | 5 + src/pystac/link.py | 23 +- src/pystac/stac_object.py | 12 +- .../test_collection_from_dict[v1.1.0].yaml | 64 +++ .../test_example_from_dict[path10].yaml | 66 +++ .../test_example_from_dict[path12].yaml | 142 ++++++ .../test_example_from_dict[path13].yaml | 222 +++++++++ .../test_example_from_dict[path15].yaml | 156 +++++++ .../test_example_from_dict[path16].yaml | 261 +++++++++++ .../test_example_from_dict[path17].yaml | 269 +++++++++++ .../test_example_from_dict[path19].yaml | 203 ++++++++ .../test_example_from_dict[path2].yaml | 222 +++++++++ .../test_example_from_dict[path3].yaml | 220 +++++++++ .../test_example_from_dict[path4].yaml | 144 ++++++ .../test_example_from_dict[path8].yaml | 269 +++++++++++ .../test_example_from_dict[path9].yaml | 432 ++++++++++++++++++ .../test_example_read_file[path10].yaml | 66 +++ .../test_example_read_file[path12].yaml | 210 +++++++++ .../test_example_read_file[path13].yaml | 222 +++++++++ .../test_example_read_file[path15].yaml | 158 +++++++ .../test_example_read_file[path16].yaml | 261 +++++++++++ .../test_example_read_file[path17].yaml | 269 +++++++++++ .../test_example_read_file[path19].yaml | 205 +++++++++ .../test_example_read_file[path2].yaml | 222 +++++++++ .../test_example_read_file[path3].yaml | 385 ++++++++++++++++ .../test_example_read_file[path4].yaml | 144 ++++++ .../test_example_read_file[path8].yaml | 269 +++++++++++ .../test_example_read_file[path9].yaml | 432 ++++++++++++++++++ .../test_validate_extension[v1.0.0].yaml | 158 +++++++ .../test_validate_extension[v1.1.0].yaml | 205 +++++++++ tests/conftest.py | 8 + 31 files changed, 5905 insertions(+), 19 deletions(-) create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path10].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path13].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path16].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path17].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path2].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path4].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path8].yaml create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path9].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path10].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path12].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path13].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path15].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path16].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path17].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path19].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path2].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path3].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path4].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path8].yaml create mode 100644 tests/cassettes/test_examples/test_example_read_file[path9].yaml create mode 100644 tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml create mode 100644 tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml diff --git a/src/pystac/jsonschema.py b/src/pystac/jsonschema.py index 47402ec30..97fe25f3f 100644 --- a/src/pystac/jsonschema.py +++ b/src/pystac/jsonschema.py @@ -116,4 +116,9 @@ def read_schema(path: str) -> dict[str, Any]: return json.load(f) +def set_default_json_schema_validator(validator: JSONSchemaValidator) -> None: + global DEFAULT_JSON_SCHEMA_VALIDATOR + DEFAULT_JSON_SCHEMA_VALIDATOR = validator # pyright: ignore[reportConstantRedefinition] + + DEFAULT_JSON_SCHEMA_VALIDATOR = JSONSchemaValidator() diff --git a/src/pystac/link.py b/src/pystac/link.py index f376883a9..3cc9ef028 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -4,9 +4,8 @@ import warnings from typing import TYPE_CHECKING, Any, override -from typing_extensions import deprecated - from pystac.errors import STACError +from pystac.media_type import MediaType from pystac.rel_type import RelType from pystac.utils import make_absolute_href @@ -28,11 +27,9 @@ class Link: def __init__( self, rel: str, - # target is mostly for backwards compatibility, if we were to design it - # from scratch we wouldn't do it this way, target: str | STACObject | None = None, href: str | None = None, - type: str | None = None, + media_type: str | None = None, title: str | None = None, method: str | None = None, headers: dict[str, str | list[str]] | None = None, @@ -42,7 +39,7 @@ def __init__( from .stac_object import STACObject self.rel: str = rel - self.type: str | None = type + self.media_type: str | None = media_type self.title: str | None = title self.method: str | None = method self.headers: dict[str, str | list[str]] | None = headers @@ -79,11 +76,6 @@ def try_from(cls, data: dict[str, Any] | Link) -> Link: def from_dict(cls, data: dict[str, Any]) -> Link: return cls(**data) - @property - @deprecated("media_type is now called type") - def media_type(self) -> str | None: - return self.type - def is_hierarchical(self) -> bool: return self.rel in HIERARCHICAL_LINKS @@ -94,11 +86,14 @@ def is_item(self) -> bool: return self.rel == RelType.ITEM def is_child(self) -> bool: - return self.rel == RelType.CHILD + return self.rel == RelType.CHILD and self.media_type is None or self.is_json() def is_derived_from(self) -> bool: return self.rel == RelType.DERIVED_FROM + def is_json(self) -> bool: + return self.media_type in (MediaType.JSON, MediaType.GEOJSON) + def get_href(self) -> str | None: return self._href or self.target and self.target.get_self_href() @@ -134,8 +129,8 @@ def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: else: raise ValueError("No href or target self href on the link") data["rel"] = self.rel - if self.type is not None: - data["type"] = self.type + if self.media_type is not None: + data["type"] = self.media_type if self.title is not None: data["title"] = self.title if self.method is not None: diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index df8723a5c..dd35b4e2f 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -418,16 +418,18 @@ def add_item(self, item: Item) -> None: self.links.append(Link(target=item, rel=RelType.ITEM)) def get_child(self, id: str) -> Container | None: - for link in self.links: - if link.is_child(): - stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Container) and stac_object.id == id: - return stac_object + for link in self.get_child_links(): + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Container) and stac_object.id == id: + return stac_object def add_child(self, child: Container) -> None: link = Link(target=child, rel=RelType.CHILD) self.links.append(link) + def get_child_links(self) -> list[Link]: + return [link for link in self.links if link.is_child()] + @deprecated("Use render instead") def normalize_hrefs(self, root_href: str) -> None: href_generator = DEFAULT_HREF_GENERATOR diff --git a/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml b/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml index 6a841f075..6f78bf269 100644 --- a/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml +++ b/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml @@ -202,4 +202,68 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path10].yaml b/tests/cassettes/test_examples/test_example_from_dict[path10].yaml new file mode 100644 index 000000000..b60cc96ec --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path10].yaml @@ -0,0 +1,66 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml index 322c5dada..edd89ac14 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml @@ -1,4 +1,82 @@ interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: @@ -65,4 +143,68 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path13].yaml b/tests/cassettes/test_examples/test_example_from_dict[path13].yaml new file mode 100644 index 000000000..cb75f6e95 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path13].yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path15].yaml b/tests/cassettes/test_examples/test_example_from_dict[path15].yaml index 61eaec859..cf86e117f 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path15].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path15].yaml @@ -1,4 +1,160 @@ interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path16].yaml b/tests/cassettes/test_examples/test_example_from_dict[path16].yaml new file mode 100644 index 000000000..6e35922c0 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path16].yaml @@ -0,0 +1,261 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path17].yaml b/tests/cassettes/test_examples/test_example_from_dict[path17].yaml new file mode 100644 index 000000000..6f78bf269 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path17].yaml @@ -0,0 +1,269 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path19].yaml b/tests/cassettes/test_examples/test_example_from_dict[path19].yaml index a43a639fb..667795967 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path19].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path19].yaml @@ -1,4 +1,207 @@ interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path2].yaml b/tests/cassettes/test_examples/test_example_from_dict[path2].yaml new file mode 100644 index 000000000..cb75f6e95 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path2].yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path3].yaml b/tests/cassettes/test_examples/test_example_from_dict[path3].yaml index df9d3a935..9f12e87bf 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path3].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path3].yaml @@ -1,4 +1,160 @@ interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: @@ -91,6 +247,70 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path4].yaml b/tests/cassettes/test_examples/test_example_from_dict[path4].yaml new file mode 100644 index 000000000..e27d3e227 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path4].yaml @@ -0,0 +1,144 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path8].yaml b/tests/cassettes/test_examples/test_example_from_dict[path8].yaml new file mode 100644 index 000000000..6f78bf269 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path8].yaml @@ -0,0 +1,269 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path9].yaml b/tests/cassettes/test_examples/test_example_from_dict[path9].yaml new file mode 100644 index 000000000..23fc5b18f --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path9].yaml @@ -0,0 +1,432 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path10].yaml b/tests/cassettes/test_examples/test_example_read_file[path10].yaml new file mode 100644 index 000000000..b60cc96ec --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path10].yaml @@ -0,0 +1,66 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path12].yaml b/tests/cassettes/test_examples/test_example_read_file[path12].yaml new file mode 100644 index 000000000..edd89ac14 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path12].yaml @@ -0,0 +1,210 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path13].yaml b/tests/cassettes/test_examples/test_example_read_file[path13].yaml new file mode 100644 index 000000000..cb75f6e95 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path13].yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path15].yaml b/tests/cassettes/test_examples/test_example_read_file[path15].yaml new file mode 100644 index 000000000..f7f2e4475 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path15].yaml @@ -0,0 +1,158 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path16].yaml b/tests/cassettes/test_examples/test_example_read_file[path16].yaml new file mode 100644 index 000000000..6e35922c0 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path16].yaml @@ -0,0 +1,261 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path17].yaml b/tests/cassettes/test_examples/test_example_read_file[path17].yaml new file mode 100644 index 000000000..6f78bf269 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path17].yaml @@ -0,0 +1,269 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path19].yaml b/tests/cassettes/test_examples/test_example_read_file[path19].yaml new file mode 100644 index 000000000..6a841f075 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path19].yaml @@ -0,0 +1,205 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path2].yaml b/tests/cassettes/test_examples/test_example_read_file[path2].yaml new file mode 100644 index 000000000..cb75f6e95 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path2].yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path3].yaml b/tests/cassettes/test_examples/test_example_read_file[path3].yaml new file mode 100644 index 000000000..9f12e87bf --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path3].yaml @@ -0,0 +1,385 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path4].yaml b/tests/cassettes/test_examples/test_example_read_file[path4].yaml new file mode 100644 index 000000000..e27d3e227 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path4].yaml @@ -0,0 +1,144 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path8].yaml b/tests/cassettes/test_examples/test_example_read_file[path8].yaml new file mode 100644 index 000000000..6f78bf269 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path8].yaml @@ -0,0 +1,269 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path9].yaml b/tests/cassettes/test_examples/test_example_read_file[path9].yaml new file mode 100644 index 000000000..23fc5b18f --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path9].yaml @@ -0,0 +1,432 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml b/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml new file mode 100644 index 000000000..f7f2e4475 --- /dev/null +++ b/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml @@ -0,0 +1,158 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml b/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml new file mode 100644 index 000000000..6a841f075 --- /dev/null +++ b/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml @@ -0,0 +1,205 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index a8dd37378..30bfe5324 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,9 @@ import pytest from pytest import FixtureRequest +import pystac.jsonschema from pystac import Catalog, Item +from pystac.jsonschema import JSONSchemaValidator @pytest.fixture(scope="module") @@ -21,6 +23,12 @@ def scrub_headers(response: dict[str, Any]) -> dict[str, Any]: return {"before_record_response": scrub_headers} +@pytest.fixture(autouse=True) +def reset_json_schema_validator() -> None: + # Needed to make sure each test records a cassette for validation requests + pystac.jsonschema.set_default_json_schema_validator(JSONSchemaValidator()) + + @pytest.fixture def data_files_path() -> Path: return Path(__file__).parent / "data-files" From 98ab027c3755d2334053808548349ea37d11074e Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 06:42:56 -0700 Subject: [PATCH 08/60] fix: some catalog tests --- src/pystac/link.py | 5 ++++- src/pystac/stac_object.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pystac/link.py b/src/pystac/link.py index 3cc9ef028..e5a70040c 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -82,8 +82,11 @@ def is_hierarchical(self) -> bool: def is_self(self) -> bool: return self.rel == RelType.SELF + def is_root(self) -> bool: + return self.rel == RelType.ROOT + def is_item(self) -> bool: - return self.rel == RelType.ITEM + return self.rel == RelType.ITEM and self.media_type is None or self.is_json() def is_child(self) -> bool: return self.rel == RelType.CHILD and self.media_type is None or self.is_json() diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index dd35b4e2f..35654523b 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -253,6 +253,9 @@ def get_root(self) -> Container | None: ) return self._root + def get_root_link(self) -> Link | None: + return next((link for link in self.links if link.is_root()), None) + def set_root(self, root: Container) -> None: self._root = root @@ -430,6 +433,9 @@ def add_child(self, child: Container) -> None: def get_child_links(self) -> list[Link]: return [link for link in self.links if link.is_child()] + def get_item_links(self) -> list[Link]: + return [link for link in self.links if link.is_item()] + @deprecated("Use render instead") def normalize_hrefs(self, root_href: str) -> None: href_generator = DEFAULT_HREF_GENERATOR From 643d08cbf8e561c3dd41e013d1ac6e4ad8195e24 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 06:44:36 -0700 Subject: [PATCH 09/60] docs: bring back the old ones No AI slop here! --- docs/api/asset.md | 8 - docs/api/catalog.md | 8 - docs/api/collection.md | 8 - docs/api/io.md | 34 ---- docs/api/item.md | 8 - docs/api/link.md | 8 - docs/api/stac-object.md | 21 --- docs/contributing.md | 180 -------------------- docs/getting-started/installation.md | 100 ----------- docs/getting-started/quickstart.md | 204 ---------------------- docs/img | 1 + docs/index.md | 100 +++++------ docs/overrides/main.html | 8 + docs/overrides/stylesheets/extra.css | 4 + docs/pystac-v2.0.md | 22 +++ docs/user-guide/catalogs.md | 175 ------------------- docs/user-guide/collections.md | 205 ---------------------- docs/user-guide/concepts.md | 221 ------------------------ docs/user-guide/io.md | 244 --------------------------- docs/user-guide/items.md | 188 --------------------- img/stac-python.png | Bin 0 -> 63684 bytes img/stac-python.xcf | Bin 0 -> 259935 bytes mkdocs.yml | 106 +++++------- 23 files changed, 118 insertions(+), 1735 deletions(-) delete mode 100644 docs/api/asset.md delete mode 100644 docs/api/catalog.md delete mode 100644 docs/api/collection.md delete mode 100644 docs/api/io.md delete mode 100644 docs/api/item.md delete mode 100644 docs/api/link.md delete mode 100644 docs/api/stac-object.md delete mode 100644 docs/contributing.md delete mode 100644 docs/getting-started/installation.md delete mode 100644 docs/getting-started/quickstart.md create mode 120000 docs/img create mode 100644 docs/overrides/main.html create mode 100644 docs/overrides/stylesheets/extra.css create mode 100644 docs/pystac-v2.0.md delete mode 100644 docs/user-guide/catalogs.md delete mode 100644 docs/user-guide/collections.md delete mode 100644 docs/user-guide/concepts.md delete mode 100644 docs/user-guide/io.md delete mode 100644 docs/user-guide/items.md create mode 100644 img/stac-python.png create mode 100644 img/stac-python.xcf diff --git a/docs/api/asset.md b/docs/api/asset.md deleted file mode 100644 index 13814c285..000000000 --- a/docs/api/asset.md +++ /dev/null @@ -1,8 +0,0 @@ -# Asset - -Assets represent data files associated with Items. - -::: pystac.Asset - options: - show_root_heading: true - show_source: true diff --git a/docs/api/catalog.md b/docs/api/catalog.md deleted file mode 100644 index 6bb2d77a9..000000000 --- a/docs/api/catalog.md +++ /dev/null @@ -1,8 +0,0 @@ -# Catalog - -A STAC Catalog provides organizational structure for Items and other Catalogs. - -::: pystac.Catalog - options: - show_root_heading: true - show_source: true diff --git a/docs/api/collection.md b/docs/api/collection.md deleted file mode 100644 index 3f73b7830..000000000 --- a/docs/api/collection.md +++ /dev/null @@ -1,8 +0,0 @@ -# Collection - -A STAC Collection extends Catalog with additional metadata. - -::: pystac.Collection - options: - show_root_heading: true - show_source: true diff --git a/docs/api/io.md b/docs/api/io.md deleted file mode 100644 index e51c1e345..000000000 --- a/docs/api/io.md +++ /dev/null @@ -1,34 +0,0 @@ -# I/O - -I/O functionality for reading and writing STAC objects. - -## Reading - -::: pystac.io.read_file - options: - show_root_heading: true - show_source: true - -## Reader Protocol - -::: pystac.reader.Reader - options: - show_root_heading: true - show_source: true - -::: pystac.reader.StandardLibraryReader - options: - show_root_heading: true - show_source: true - -## Writer Protocol - -::: pystac.writer.Writer - options: - show_root_heading: true - show_source: true - -::: pystac.writer.StandardLibraryWriter - options: - show_root_heading: true - show_source: true diff --git a/docs/api/item.md b/docs/api/item.md deleted file mode 100644 index c8282a523..000000000 --- a/docs/api/item.md +++ /dev/null @@ -1,8 +0,0 @@ -# Item - -A STAC Item is a GeoJSON Feature with additional STAC properties. - -::: pystac.Item - options: - show_root_heading: true - show_source: true diff --git a/docs/api/link.md b/docs/api/link.md deleted file mode 100644 index 381bf1898..000000000 --- a/docs/api/link.md +++ /dev/null @@ -1,8 +0,0 @@ -# Link - -Links represent relationships between STAC objects. - -::: pystac.Link - options: - show_root_heading: true - show_source: true diff --git a/docs/api/stac-object.md b/docs/api/stac-object.md deleted file mode 100644 index 9d6e3d9e2..000000000 --- a/docs/api/stac-object.md +++ /dev/null @@ -1,21 +0,0 @@ -# STACObject - -`STACObject` is the abstract base class for all STAC objects (Item, Catalog, Collection). - -::: pystac.STACObject - options: - show_root_heading: true - show_source: true - members: - - from_dict - - from_file - - to_dict - - save - - render - - validate - - get_links - - add_link - - remove_links - - clear_links - - get_single_link - - get_root_link diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 100644 index 5d08ccf79..000000000 --- a/docs/contributing.md +++ /dev/null @@ -1,180 +0,0 @@ -# Contributing - -Thank you for your interest in contributing to PySTAC! - -## Development Setup - -PySTAC uses [uv](https://docs.astral.sh/uv/) for dependency management. - -### Clone and Install - -```bash -git clone https://github.com/stac-utils/pystac.git -cd pystac -uv sync -``` - -This installs PySTAC in editable mode along with all development and documentation dependencies. - -## Development Workflow - -### Running Tests - -```bash -# Run all tests -uv run pytest - -# Run specific test file -uv run pytest tests/test_item.py - -# Run tests matching a pattern -uv run pytest -k test_item_creation - -# Run with coverage -uv run pytest --cov=pystac -``` - -### Linting and Formatting - -PySTAC uses [ruff](https://docs.astral.sh/ruff/) for linting and formatting: - -```bash -# Check code -uv run ruff check - -# Format code -uv run ruff format - -# Fix auto-fixable issues -uv run ruff check --fix -``` - -### Type Checking - -PySTAC uses [basedpyright](https://docs.basedpyright.com/) with strict mode: - -```bash -uv run basedpyright -``` - -All code must pass type checking with strict mode enabled. - -### Pre-commit Hooks - -PySTAC uses pre-commit hooks to ensure code quality: - -```bash -# Install pre-commit hooks -uv run pre-commit install - -# Run hooks manually -uv run pre-commit run --all-files -``` - -Hooks include: -- `ruff` for linting and formatting -- `basedpyright` for type checking -- `codespell` for spell checking - -## Documentation - -### Building Documentation - -```bash -# Serve docs locally -uv run mkdocs serve - -# Build docs -uv run mkdocs build -``` - -The documentation uses mkdocs-material and mkdocstrings for API documentation. - -### Writing Documentation - -- User guides go in `docs/user-guide/` -- API documentation is auto-generated from docstrings -- Use Google-style docstrings - -Example docstring: - -```python -def example_function(param: str) -> int: - """Short description. - - Longer description with more details. - - Args: - param: Description of parameter. - - Returns: - Description of return value. - - Raises: - ValueError: When param is invalid. - """ - pass -``` - -## Pull Request Process - -1. **Fork and clone** the repository -2. **Create a branch** for your changes -3. **Make your changes** with tests -4. **Run tests and linting** locally -5. **Push and create a pull request** -6. **Respond to review feedback** - -### PR Guidelines - -- Write clear commit messages -- Include tests for new functionality -- Update documentation as needed -- Ensure all tests pass -- Keep PRs focused on a single concern - -## Code Style - -- Follow PEP 8 (enforced by ruff) -- Use type hints for all public APIs -- Write descriptive variable and function names -- Keep functions focused and small -- Add docstrings for public APIs - -## Testing Guidelines - -- Write tests for all new features -- Test edge cases and error conditions -- Use descriptive test names -- Keep tests simple and focused -- Mock external dependencies - -Example test: - -```python -def test_item_creation(): - """Test that items can be created with required fields.""" - item = pystac.Item( - id="test-item", - geometry={"type": "Point", "coordinates": [0, 0]}, - bbox=[0, 0, 0, 0], - datetime=datetime.now(timezone.utc), - properties={} - ) - assert item.id == "test-item" - assert item.geometry is not None -``` - -## Version Support - -PySTAC requires Python 3.12 or later. - -## Questions? - -- Open an [issue](https://github.com/stac-utils/pystac/issues) -- Ask in [discussions](https://github.com/radiantearth/stac-spec/discussions/categories/stac-software) -- Join the [Gitter chat](https://gitter.im/SpatioTemporal-Asset-Catalog/python) - -## Code of Conduct - -This project adheres to the Contributor Covenant [Code of Conduct](https://github.com/stac-utils/pystac/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md deleted file mode 100644 index ef3059ea8..000000000 --- a/docs/getting-started/installation.md +++ /dev/null @@ -1,100 +0,0 @@ -# Installation - -## Requirements - -PySTAC requires Python 3.12 or later. - -## Install from PyPI (Recommended) - -The easiest way to install PySTAC is from PyPI using pip: - -```bash -python -m pip install pystac -``` - -### Optional Dependencies - -PySTAC has several optional dependency groups for additional features: - -#### Validation - -If you want to validate STAC objects against JSON schemas: - -```bash -python -m pip install 'pystac[validation]' -``` - -This installs: -- `jsonschema` - For JSON schema validation -- `referencing` - For schema reference resolution - -#### Jinja2 (Jupyter Display) - -If you're using Jupyter notebooks and want pretty display of STAC objects: - -```bash -python -m pip install 'pystac[jinja2]' -``` - -#### Object Storage (obstore) - -For working with cloud object storage (S3, GCS, Azure): - -```bash -python -m pip install 'pystac[obstore]' -``` - -This installs: -- `obstore` - For object storage access -- `obspec` - For object storage specifications - -#### All Optional Dependencies - -To install all optional dependencies: - -```bash -python -m pip install 'pystac[validation,jinja2,obstore]' -``` - -## Install from Source - -To install the latest development version from GitHub: - -```bash -git clone https://github.com/stac-utils/pystac.git -cd pystac -python -m pip install . -``` - -### Development Installation - -If you're contributing to PySTAC, install with development dependencies using [uv](https://docs.astral.sh/uv/): - -```bash -git clone https://github.com/stac-utils/pystac.git -cd pystac -uv sync -``` - -This installs PySTAC in editable mode along with all development and documentation dependencies. - -## Verifying Installation - -You can verify your installation by checking the version: - -```python -import pystac -print(pystac.__version__) -``` - -Or by running the test suite: - -```bash -uv run pytest # if installed with uv -# or -python -m pytest # if installed with pip -``` - -## Next Steps - -Now that you have PySTAC installed, check out the [Quickstart Guide](quickstart.md) to learn how to use it. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md deleted file mode 100644 index 0687d8964..000000000 --- a/docs/getting-started/quickstart.md +++ /dev/null @@ -1,204 +0,0 @@ -# Quickstart - -This guide will walk you through the basics of using PySTAC to work with STAC catalogs, collections, and items. - -## Reading STAC Objects - -### Reading an Item - -```python -import pystac - -# Read a STAC item from a file -item = pystac.Item.from_file("path/to/item.json") - -# Access basic properties -print(f"Item ID: {item.id}") -print(f"Datetime: {item.datetime}") -print(f"Geometry: {item.geometry}") -print(f"Bounding Box: {item.bbox}") - -# Access custom properties -print(f"Properties: {item.properties}") -``` - -### Reading a Catalog - -```python -# Read a catalog -catalog = pystac.Catalog.from_file("path/to/catalog.json") - -print(f"Catalog ID: {catalog.id}") -print(f"Description: {catalog.description}") - -# Iterate through items -for item in catalog.get_items(): - print(f"Item: {item.id}") - -# Recursively walk through the catalog -for child in catalog.walk(): - if isinstance(child, pystac.Item): - print(f"Found item: {child.id}") - elif isinstance(child, pystac.Collection): - print(f"Found collection: {child.id}") - else: - print(f"Found catalog: {child.id}") -``` - -### Reading a Collection - -```python -# Read a collection -collection = pystac.Collection.from_file("path/to/collection.json") - -print(f"Collection ID: {collection.id}") -print(f"License: {collection.license}") -print(f"Extent: {collection.extent}") - -# Access items in the collection -for item in collection.get_items(): - print(f"Item: {item.id}") -``` - -## Creating STAC Objects - -### Creating an Item - -```python -import pystac -from datetime import datetime, timezone - -# Create a new item -item = pystac.Item( - id="my-item", - geometry={ - "type": "Point", - "coordinates": [-105.0, 40.0] - }, - bbox=[-105.0, 40.0, -105.0, 40.0], - datetime=datetime.now(timezone.utc), - properties={"key": "value"} -) - -# Add an asset -item.assets["thumbnail"] = pystac.Asset( - href="https://example.com/thumbnail.png", - type="image/png", - roles=["thumbnail"] -) -``` - -### Creating a Catalog - -```python -# Create a catalog -catalog = pystac.Catalog( - id="my-catalog", - description="My STAC catalog" -) - -# Add items -catalog.add_item(item) - -# Add a child catalog -child_catalog = pystac.Catalog( - id="child-catalog", - description="A child catalog" -) -catalog.add_child(child_catalog) -``` - -### Creating a Collection - -```python -# Create a collection -collection = pystac.Collection( - id="my-collection", - description="My STAC collection", - license="CC-BY-4.0", - extent=pystac.Extent( - spatial=pystac.SpatialExtent([[-180, -90, 180, 90]]), - temporal=pystac.TemporalExtent([[datetime.now(timezone.utc), None]]) - ) -) - -# Add items -collection.add_item(item) -``` - -## Working with Links - -```python -# Access links -for link in item.links: - print(f"Link rel: {link.rel}, href: {link.href}") - -# Add a link -item.add_link(pystac.Link( - rel="related", - target="https://example.com/related.json" -)) - -# Get specific links -parent_link = item.get_single_link("parent") -if parent_link: - print(f"Parent: {parent_link.href}") -``` - -## Working with Assets - -```python -# Access assets -for key, asset in item.assets.items(): - print(f"Asset {key}:") - print(f" href: {asset.href}") - print(f" type: {asset.type}") - print(f" roles: {asset.roles}") - -# Add an asset -item.assets["data"] = pystac.Asset( - href="https://example.com/data.tif", - type="image/tiff; application=geotiff", - roles=["data"] -) - -# Remove an asset -del item.assets["thumbnail"] -``` - -## Saving STAC Objects - -```python -# Render sets hrefs for the catalog and all children -catalog.render(root="/path/to/output") - -# Save writes all objects to disk -catalog.save() - -# Or save a single object -item.save("/path/to/item.json") -``` - -## Validation - -If you've installed the validation dependencies, you can validate STAC objects: - -```python -from pystac.validate import JsonschemaValidator - -validator = JsonschemaValidator() - -# Validate an item -errors = validator.validate(item.to_dict()) -if errors: - for error in errors: - print(f"Validation error: {error}") -else: - print("Item is valid!") -``` - -## Next Steps - -- Learn more about core concepts in the [User Guide](../user-guide/concepts.md) -- Explore the [API Reference](../api/stac-object.md) for detailed documentation -- Check out specific guides for [Items](../user-guide/items.md), [Catalogs](../user-guide/catalogs.md), and [Collections](../user-guide/collections.md) diff --git a/docs/img b/docs/img new file mode 120000 index 000000000..6ffc6ca9f --- /dev/null +++ b/docs/img @@ -0,0 +1 @@ +../img \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 553e31b51..5d10fb5c3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,81 +1,69 @@ # PySTAC -[![Build Status](https://github.com/stac-utils/pystac/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/stac-utils/pystac/actions/workflows/continuous-integration.yml) -[![PyPI version](https://badge.fury.io/py/pystac.svg)](https://badge.fury.io/py/pystac) -[![Documentation](https://readthedocs.org/projects/pystac/badge/?version=latest)](https://pystac.readthedocs.io/en/latest/) +!!! warning -PySTAC is a Python library for working with the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org) specification. + These docs are for the work-in-progress v2 of PySTAC. + For the current PySTAC v1 docs, see . -## What is STAC? + Our work plan for v2 goes like this: -The SpatioTemporal Asset Catalog (STAC) specification provides a common language to describe geospatial information, so it can more easily be worked with, indexed, and discovered. STAC is a specification for describing geospatial data using JSON and GeoJSON. + 1. Rebuild the core data structures (`Item`, `Catalog`, `Collection`, etc) from scratch, with new tests + 2. Slowly re-add the old tests to the `tests/v1` one at a time, to make sure that we're breaking as little as possible + 3. If we intentionally break a test (e.g. by relaxing a check on inputs) we'll mark it `xfail` and copy it to test the new expected behavior -## What is PySTAC? + This will take a while. + Watch to track our progress. + We'll sometimes use pull requests, but sometimes not. -PySTAC is a library that provides a Python API for working with STAC catalogs, collections, and items. It allows you to: +**PySTAC** is a Python library for reading and writing [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org) metadata. +To install: -- **Read** STAC catalogs, collections, and items from files or URLs -- **Create** new STAC objects programmatically -- **Modify** existing STAC objects -- **Write** STAC objects to disk -- **Validate** STAC objects against the specification -- **Traverse** STAC hierarchies - -## PySTAC v2.0 - -This is the v2.0 rewrite of PySTAC with improved design principles: + +```shell +python -m pip install pystac +``` -- **Stable core APIs**: Keep data structure APIs (Item, Catalog, Collection) consistent -- **Flexible validation**: Accept almost anything with warnings for corrections -- **Simplified implementation**: Do fewer things at once, single responsibility methods -- **Low dependencies**: Minimal dependencies by default, optional extras for additional features +## Creating -## Quick Example +STAC has three data structure: `Item`, `Catalog`, and `Collection`. +Each can be created with sensible defaults: ```python -import pystac +from pystac import Item, Catalog, Collection -# Read a STAC item -item = pystac.Item.from_file("path/to/item.json") +item = Item("an-item-id") +catalog = Item("a-catalog-id", "A catalog description") +collection = Item("a-collection-id", "A collection description") +``` -# Access item properties -print(f"Item ID: {item.id}") -print(f"Datetime: {item.datetime}") +## Reading -# Access assets -for key, asset in item.assets.items(): - print(f"Asset {key}: {asset.href}") +Reading STAC from the local filesystem is supported out-of-the-box: -# Create a new catalog -catalog = pystac.Catalog( - id="my-catalog", - description="A catalog of imagery" -) +```python +item = pystac.read_file("item.json") +``` -# Add the item to the catalog -catalog.add_item(item) +To read from remote locations, including HTTP(S) and blob storage, we use [obstore](https://developmentseed.org/obstore/). +Install with that optional dependency: -# Save the catalog -catalog.render(root="/path/to/output") -catalog.save() +```shell +python -m pip install 'pystac[obstore]' ``` -## Features +Then: -- **Core STAC Types**: Full support for Item, Catalog, and Collection -- **Links and Assets**: Complete link and asset management -- **I/O**: Flexible I/O system with pluggable readers and writers -- **Validation**: Optional validation against STAC JSON schemas -- **Extensions**: Preserve extension data through extra_fields -- **Type Safety**: Full type hints with strict type checking +```python +from pystac.obstore import ObstoreReader +reader = ObstoreReader() # provide any configuration values here, e.g. ObstoreReader(aws_region="us-east-1") +item = reader.read_file("s3://bucket/item.json") +``` -## Get Started +!!! todo -Check out the [Installation Guide](getting-started/installation.md) to install PySTAC, then follow the [Quickstart](getting-started/quickstart.md) to learn the basics. + Add more examples, and maybe put them in a notebook so we can execute them. -## Links +## Supported versions -- [GitHub Repository](https://github.com/stac-utils/pystac) -- [PyPI Package](https://pypi.org/project/pystac/) -- [STAC Specification](https://stacspec.org) -- [Issue Tracker](https://github.com/stac-utils/pystac/issues) +PySTAC v2.0 supports STAC v1.0 and STAC v1.1. +For pre-STAC v1.0 versions, use `pystac<2`. diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 000000000..2058d21a6 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block announce %} +

+ 👷 These are work-in-progress docs for PySTAC v2.0, which has not been released. + For the current PySTAC v1 docs, see https://pystac.readthedocs.io. +

+{% endblock %} \ No newline at end of file diff --git a/docs/overrides/stylesheets/extra.css b/docs/overrides/stylesheets/extra.css new file mode 100644 index 000000000..eb430eb28 --- /dev/null +++ b/docs/overrides/stylesheets/extra.css @@ -0,0 +1,4 @@ +:root, +[data-md-color-scheme="default"] { + --md-primary-fg-color: #4cb1ac; +} \ No newline at end of file diff --git a/docs/pystac-v2.0.md b/docs/pystac-v2.0.md new file mode 100644 index 000000000..ed7f92514 --- /dev/null +++ b/docs/pystac-v2.0.md @@ -0,0 +1,22 @@ +# PySTAC v2.0 + +**PySTAC v2.0** is a ground-up re-write of **PySTAC**. +Our high-level design goals are a form of [Postel's law](https://en.wikipedia.org/wiki/Robustness_principle): + +- Help people make the best STAC possible +- Help people interact with existing (even poorly constructed or invalid) STAC as easily as possible + +To do so, we have some specific implementation strategies. + +- **Keep the core data structure APIs basically the same**: People are used to the basic methods on `Item`, `Catalog`, `Collection`, etc. + We shouldn't change the external APIs unless there's a good reason to. +- **Relax core data structure initializers to accept almost anything**, with warnings if something's being changed to make it valid +- **Stay low-dependency by default**: This keeps our maintenance burden lower, at the cost of having to hand-roll more stuff ourselves. +- **Replace "implementation" APIs**: `Item`, `Catalog`, etc are the "what" of **pystac**. + Things like `StacIO` are the "how". + We should create replacement structures for any of these "how" interfaces that we want to dramatically change, rather than try to "fix" the existing ones. + Backwards compatibility will be _much_ more difficult for these "how" interfaces, so we shouldn't even try. + _If possible_ we should re-write the existing "how" structure (e.g. `StacIO`) to use the new API, but this should be a lower-priority objective. +- **Do fewer things at once**: One of the biggest design problems of PySTAC v1.0 (in this author's opinion) was that many functions tried to be "helpful" by doing a lot of things at once. + When possible, we should simplify methods to do just one thing, and provide intuitive patterns for doing complex operations using multiple method calls. + Top-level functions can be used to "synthesize" complex operations, e.g. `pystac.read_file`. diff --git a/docs/user-guide/catalogs.md b/docs/user-guide/catalogs.md deleted file mode 100644 index 921f26cf7..000000000 --- a/docs/user-guide/catalogs.md +++ /dev/null @@ -1,175 +0,0 @@ -# Working with Catalogs - -A STAC Catalog provides organizational structure for Items and other Catalogs. This guide covers creating, traversing, and managing STAC Catalogs. - -## Creating Catalogs - -```python -import pystac - -catalog = pystac.Catalog( - id="my-catalog", - description="A catalog of satellite imagery", - title="My Satellite Catalog" # Optional -) -``` - -## Adding Items and Children - -```python -# Add an item -item = pystac.Item(...) -catalog.add_item(item) - -# Add a child catalog -child = pystac.Catalog( - id="child-catalog", - description="A subcatalog" -) -catalog.add_child(child) - -# Add a collection -collection = pystac.Collection(...) -catalog.add_child(collection) -``` - -## Traversing Catalogs - -### Get Direct Children - -```python -# Get all direct children (catalogs and collections) -for child in catalog.get_children(): - print(f"Child: {child.id}") - -# Get all direct items -for item in catalog.get_items(): - print(f"Item: {item.id}") -``` - -### Recursive Traversal - -```python -# Walk the entire catalog tree -for obj in catalog.walk(): - if isinstance(obj, pystac.Item): - print(f"Item: {obj.id}") - elif isinstance(obj, pystac.Collection): - print(f"Collection: {obj.id}") - elif isinstance(obj, pystac.Catalog): - print(f"Catalog: {obj.id}") -``` - -## Reading Catalogs - -```python -# Read from file -catalog = pystac.Catalog.from_file("path/to/catalog.json") - -# Read from URL -catalog = pystac.Catalog.from_file("https://example.com/catalog.json") - -# Traverse after reading -for item in catalog.get_items(recursive=True): - print(f"Found item: {item.id}") -``` - -## Catalog Structure - -### Links - -Catalogs use links to connect to other objects: - -- `root`: Link to the root catalog -- `parent`: Link to parent catalog (if not root) -- `child`: Links to child catalogs/collections -- `item`: Links to items -- `self`: Canonical location - -```python -# Get root -root_link = catalog.get_root_link() -if root_link: - root = root_link.get_stac_object() - -# Get parent -parent_link = catalog.get_single_link("parent") -if parent_link: - parent = parent_link.get_stac_object() -``` - -## Saving Catalogs - -```python -# Render sets hrefs for catalog and all children -catalog.render(root="/path/to/output") - -# Save writes all objects to disk -catalog.save() -``` - -### Render Strategies - -The default renderer creates a directory structure: - -``` -output/ -├── catalog.json -├── child-catalog/ -│ ├── catalog.json -│ └── item-1/ -│ └── item-1.json -└── collection-1/ - ├── collection.json - └── item-2/ - └── item-2.json -``` - -## Catalog Organization Patterns - -### Geographic Organization - -```python -root = pystac.Catalog(id="root", description="Root catalog") - -# Organize by region -north_america = pystac.Catalog(id="north-america", description="North America") -europe = pystac.Catalog(id="europe", description="Europe") - -root.add_child(north_america) -root.add_child(europe) - -# Add items to appropriate regions -north_america.add_item(usa_item) -europe.add_item(france_item) -``` - -### Temporal Organization - -```python -root = pystac.Catalog(id="root", description="Root catalog") - -# Organize by year -year_2023 = pystac.Catalog(id="2023", description="2023 imagery") -year_2024 = pystac.Catalog(id="2024", description="2024 imagery") - -root.add_child(year_2023) -root.add_child(year_2024) -``` - -## Batch Operations - -```python -# Set STAC version for all objects -catalog.set_stac_version("1.1.0") - -# Clear all links of a specific type -for child in catalog.walk(): - child.clear_links("derived_from") -``` - -## Next Steps - -- Learn about [Collections](collections.md) -- Learn about [Items](items.md) -- Explore the [Catalog API Reference](../api/catalog.md) diff --git a/docs/user-guide/collections.md b/docs/user-guide/collections.md deleted file mode 100644 index 7572b2157..000000000 --- a/docs/user-guide/collections.md +++ /dev/null @@ -1,205 +0,0 @@ -# Working with Collections - -A STAC Collection extends Catalog with additional metadata about a group of related Items. This guide covers creating and managing Collections. - -## Creating Collections - -```python -import pystac -from datetime import datetime, timezone - -collection = pystac.Collection( - id="my-collection", - description="Satellite imagery collection", - title="My Satellite Collection", # Optional - license="CC-BY-4.0", - extent=pystac.Extent( - spatial=pystac.SpatialExtent([[-180, -90, 180, 90]]), - temporal=pystac.TemporalExtent([ - [datetime(2020, 1, 1, tzinfo=timezone.utc), None] - ]) - ) -) -``` - -## Collection Properties - -### Required Properties - -- **id**: Unique identifier -- **description**: Detailed description -- **license**: License identifier or "proprietary" -- **extent**: Spatial and temporal extent of all items - -### Optional Properties - -```python -collection.title = "Human-readable title" -collection.keywords = ["satellite", "imagery", "earth observation"] -collection.providers = [ - pystac.Provider( - name="Example Org", - roles=["producer", "licensor"], - url="https://example.com" - ) -] -``` - -## Extent - -The extent describes the spatial and temporal coverage: - -### Spatial Extent - -```python -# Single bounding box -spatial_extent = pystac.SpatialExtent( - bboxes=[[-180, -90, 180, 90]] -) - -# Multiple bounding boxes -spatial_extent = pystac.SpatialExtent( - bboxes=[ - [-180, -90, -90, 0], # Western hemisphere, southern half - [-90, 0, 0, 90] # Northwestern quadrant - ] -) -``` - -### Temporal Extent - -```python -# Closed time range -temporal_extent = pystac.TemporalExtent( - intervals=[ - [ - datetime(2020, 1, 1, tzinfo=timezone.utc), - datetime(2024, 12, 31, tzinfo=timezone.utc) - ] - ] -) - -# Open-ended (still collecting) -temporal_extent = pystac.TemporalExtent( - intervals=[ - [datetime(2020, 1, 1, tzinfo=timezone.utc), None] - ] -) -``` - -## Summaries - -Summaries provide aggregate information about item properties: - -```python -collection.summaries = pystac.Summaries({ - "platform": ["satellite-1", "satellite-2"], - "instruments": ["camera-1", "camera-2"], - "gsd": {"minimum": 10, "maximum": 30}, - "eo:cloud_cover": {"minimum": 0, "maximum": 50} -}) -``` - -## Item Assets - -Define common assets that appear in items: - -```python -collection.item_assets = { - "data": pystac.extensions.item_assets.AssetDefinition({ - "type": "image/tiff; application=geotiff", - "roles": ["data"], - "title": "Data" - }), - "thumbnail": pystac.extensions.item_assets.AssetDefinition({ - "type": "image/png", - "roles": ["thumbnail"], - "title": "Thumbnail" - }) -} -``` - -## Adding Items - -```python -# Create and add an item -item = pystac.Item( - id="item-1", - geometry=geometry, - bbox=bbox, - datetime=datetime.now(timezone.utc), - properties={} -) - -# Add to collection (sets item.collection_id automatically) -collection.add_item(item) - -# Update extent to include new items -collection.update_extent_from_items() -``` - -## Reading Collections - -```python -# From file -collection = pystac.Collection.from_file("path/to/collection.json") - -# From URL -collection = pystac.Collection.from_file("https://example.com/collection.json") - -# Access items -for item in collection.get_items(): - print(f"Item: {item.id}") - print(f"Collection: {item.collection_id}") -``` - -## Collection vs Catalog - -When to use Collection vs Catalog: - -**Use Collection when:** -- Items are related and share characteristics -- You want to provide aggregate metadata (extent, summaries) -- You need license information -- Items should be discoverable as a cohesive dataset - -**Use Catalog when:** -- You only need organizational structure -- Items are heterogeneous -- You don't need collection-specific metadata - -## Updating Extent - -```python -# Manually update after adding items -collection.update_extent_from_items() - -# Or calculate from specific items -items = [item1, item2, item3] -spatial_extent = pystac.SpatialExtent.from_items(items) -temporal_extent = pystac.TemporalExtent.from_items(items) - -collection.extent = pystac.Extent( - spatial=spatial_extent, - temporal=temporal_extent -) -``` - -## Saving Collections - -```python -# Render and save -collection.render(root="/path/to/output") -collection.save() - -# Or add to catalog and save together -catalog.add_child(collection) -catalog.render(root="/path/to/output") -catalog.save() -``` - -## Next Steps - -- Learn about [Items](items.md) -- Learn about [Catalogs](catalogs.md) -- Explore the [Collection API Reference](../api/collection.md) diff --git a/docs/user-guide/concepts.md b/docs/user-guide/concepts.md deleted file mode 100644 index d5db8176d..000000000 --- a/docs/user-guide/concepts.md +++ /dev/null @@ -1,221 +0,0 @@ -# Core Concepts - -This guide introduces the core concepts and architecture of PySTAC. - -## STAC Objects - -PySTAC provides Python classes for the main STAC specification types: - -### Item - -A **STAC Item** is a GeoJSON Feature with additional STAC-specific properties. It represents a single spatiotemporal asset or scene. - -Key properties: -- `id`: Unique identifier -- `geometry`: GeoJSON geometry -- `bbox`: Bounding box -- `datetime` or `start_datetime`/`end_datetime`: Temporal information -- `properties`: Additional metadata -- `assets`: Dictionary of associated files -- `links`: Relationships to other STAC objects - -### Catalog - -A **STAC Catalog** is a collection of STAC Items and/or other Catalogs. It provides organizational structure. - -Key properties: -- `id`: Unique identifier -- `description`: Human-readable description -- `links`: Relationships to children, items, and parent - -### Collection - -A **STAC Collection** extends Catalog with additional metadata about a group of related Items. - -Key properties: -- All Catalog properties, plus: -- `extent`: Spatial and temporal extent of all items -- `license`: License information -- `providers`: Organizations involved -- `summaries`: Aggregate information about item properties -- `item_assets`: Common asset definitions - -## Object Hierarchy - -``` -STACObject (Abstract Base) -├── Container (Abstract Base) -│ ├── Catalog -│ └── Collection -└── Item -``` - -All STAC objects inherit from `STACObject`, which provides: -- Serialization/deserialization (`from_dict()`, `to_dict()`, `from_file()`) -- Link management -- File I/O operations - -`Container` is an abstract base for Catalog and Collection, providing: -- Hierarchy traversal (`walk()`, `get_children()`, `get_items()`) -- Child-parent relationship management -- Batch operations - -## Links - -Links connect STAC objects together, forming a graph structure. PySTAC uses the `Link` class to represent relationships. - -Common link relation types: -- `root`: Link to the root catalog -- `parent`: Link to the parent catalog/collection -- `child`: Link to a child catalog/collection -- `item`: Link to an item -- `self`: Canonical location of the object -- `collection`: Link from an item to its collection - -### Lazy Loading - -Links support lazy loading - the target object is only loaded when accessed via `get_stac_object()`: - -```python -parent_link = item.get_single_link("parent") -if parent_link: - # Object is loaded only when needed - parent = parent_link.get_stac_object() -``` - -## Assets - -Assets represent the actual data files associated with an Item. Each asset has: - -- `href`: URL or path to the file -- `type`: Media type (MIME type) -- `title`: Human-readable title -- `description`: Detailed description -- `roles`: Semantic roles (e.g., "data", "thumbnail", "metadata") - -```python -asset = pystac.Asset( - href="https://example.com/image.tif", - type="image/tiff; application=geotiff", - roles=["data"] -) -``` - -## I/O System - -PySTAC v2.0 uses a protocol-based I/O system for flexibility: - -### Read Protocol - -- `read_json_from_path(path)`: Read from filesystem -- `read_json_from_url(url)`: Read from HTTP/HTTPS - -### Write Protocol - -- `write_json_to_path(data, path)`: Write to filesystem -- `write_json_to_url(data, url)`: Write to URL (custom implementations) - -The default implementation uses Python's standard library for filesystem operations and `urllib` for HTTP. - -### Custom I/O - -You can provide custom readers/writers for cloud storage or other backends: - -```python -# Custom implementation using duck typing -class S3Reader: - def read_json_from_path(self, path): - # S3 implementation - pass - - def read_json_from_url(self, url): - # S3 implementation - pass - -# Use with PySTAC -item = pystac.Item.from_file("s3://bucket/item.json", reader=S3Reader()) -``` - -## Rendering - -Before saving, STAC objects need their `href` properties set. The `render()` method handles this using a rendering strategy. - -```python -# Set hrefs using best practices layout -catalog.render(root="/path/to/output") - -# Then save all objects -catalog.save() -``` - -The default `BestPracticesRenderer` creates a standard layout: -- Items: `{id}/{id}.json` -- Catalogs: `{id}/catalog.json` -- Collections: `{id}/collection.json` - -## Extensions - -STAC Extensions add additional fields and functionality. PySTAC v2.0 uses a simplified approach: - -- `stac_extensions`: List of extension URLs -- `extra_fields`: Dictionary for extension-specific fields - -```python -# Extension data is preserved automatically -item = pystac.Item.from_dict({ - "type": "Feature", - "stac_extensions": ["https://stac-extensions.github.io/eo/v1.0.0/schema.json"], - "properties": { - "eo:cloud_cover": 10.5 - }, - # ... other fields -}) - -# Access extension data -cloud_cover = item.properties.get("eo:cloud_cover") - -# Or via extra_fields for root-level extension properties -# item.extra_fields["custom:field"] -``` - -## Validation - -PySTAC provides optional validation against JSON schemas: - -```python -from pystac.validate import JsonschemaValidator - -validator = JsonschemaValidator() -errors = validator.validate(item.to_dict()) -``` - -Validation requires the `validation` optional dependencies: - -```bash -pip install 'pystac[validation]' -``` - -## PySTAC v2.0 Design Principles - -### Stable Core APIs - -Core data structure APIs (Item, Catalog, Collection) remain stable across versions. - -### Relaxed Validation - -Initializers accept almost anything, with warnings for corrections. This makes it easier to work with imperfect real-world data. - -### Single Responsibility - -Methods do one thing well, rather than combining multiple operations. - -### Low Dependencies - -Minimal required dependencies. Additional features available via optional dependency groups. - -## Next Steps - -- [Working with Items](items.md) -- [Working with Catalogs](catalogs.md) -- [Working with Collections](collections.md) -- [Reading and Writing](io.md) diff --git a/docs/user-guide/io.md b/docs/user-guide/io.md deleted file mode 100644 index 9756a16c1..000000000 --- a/docs/user-guide/io.md +++ /dev/null @@ -1,244 +0,0 @@ -# Reading and Writing - -This guide covers PySTAC's I/O system for reading and writing STAC objects. - -## Reading STAC Objects - -### From Files - -```python -import pystac - -# Read any STAC object (auto-detects type) -obj = pystac.STACObject.from_file("path/to/stac.json") - -# Read specific types -item = pystac.Item.from_file("path/to/item.json") -catalog = pystac.Catalog.from_file("path/to/catalog.json") -collection = pystac.Collection.from_file("path/to/collection.json") -``` - -### From URLs - -```python -# Read from HTTP/HTTPS -item = pystac.Item.from_file("https://example.com/item.json") - -# Works with any accessible URL -catalog = pystac.Catalog.from_file("https://example.com/catalog.json") -``` - -### From Dictionaries - -```python -# From Python dict -item_dict = { - "type": "Feature", - "stac_version": "1.1.0", - "id": "example", - # ... other fields -} - -item = pystac.Item.from_dict(item_dict) -``` - -## Writing STAC Objects - -### Save Individual Objects - -```python -# Save to specific path -item.save("/path/to/output/item.json") - -# Save with pretty printing (default) -item.save("/path/to/output/item.json") -``` - -### Render and Save Hierarchies - -When working with catalogs, use `render()` to set hrefs before saving: - -```python -# Create catalog structure -catalog = pystac.Catalog(id="root", description="Root catalog") -catalog.add_item(item1) -catalog.add_item(item2) - -# Render sets hrefs for all objects -catalog.render(root="/path/to/output") - -# Save writes all objects -catalog.save() -``` - -## Custom I/O - -PySTAC v2.0 uses protocol-based I/O for flexibility. - -### Read Protocol - -Implement these methods for custom reading: - -```python -class CustomReader: - def read_json_from_path(self, path: str) -> dict: - """Read JSON from a path.""" - # Custom implementation - pass - - def read_json_from_url(self, url: str) -> dict: - """Read JSON from a URL.""" - # Custom implementation - pass -``` - -### Write Protocol - -Implement these methods for custom writing: - -```python -class CustomWriter: - def write_json_to_path(self, data: dict, path: str) -> None: - """Write JSON to a path.""" - # Custom implementation - pass - - def write_json_to_url(self, data: dict, url: str) -> None: - """Write JSON to a URL.""" - # Custom implementation - pass -``` - -### Using Custom I/O - -```python -# Use custom reader -reader = CustomReader() -item = pystac.Item.from_file("custom://path", reader=reader) - -# Use custom writer -writer = CustomWriter() -item.save("custom://path", writer=writer) -``` - -## Cloud Storage - -### Using obstore - -For S3, GCS, and Azure Blob Storage: - -```bash -pip install 'pystac[obstore]' -``` - -```python -from pystac.io import ObstoreReader, ObstoreWriter - -# Configure for S3 -reader = ObstoreReader("s3://bucket-name") -writer = ObstoreWriter("s3://bucket-name") - -# Read from S3 -item = pystac.Item.from_file("s3://bucket-name/item.json", reader=reader) - -# Write to S3 -item.save("s3://bucket-name/output/item.json", writer=writer) -``` - -## Rendering Strategies - -The renderer controls how hrefs are set when saving. - -### Best Practices Renderer (Default) - -```python -from pystac.render import BestPracticesRenderer - -renderer = BestPracticesRenderer() -catalog.render(root="/path/to/output", renderer=renderer) -``` - -Creates this structure: -``` -output/ -├── catalog.json -├── collection-1/ -│ ├── collection.json -│ └── item-1/ -│ └── item-1.json -└── catalog-2/ - └── catalog.json -``` - -### Custom Renderer - -```python -class CustomRenderer: - def render(self, obj, root): - """Set href for object and render children.""" - # Custom layout logic - pass - -# Use custom renderer -catalog.render(root="/path/to/output", renderer=CustomRenderer()) -``` - -## Lazy Loading - -Links support lazy loading to avoid loading entire hierarchies: - -```python -# Read catalog -catalog = pystac.Catalog.from_file("path/to/catalog.json") - -# Links are not resolved until accessed -for link in catalog.links: - if link.rel == "child": - # Object loaded only when needed - child = link.get_stac_object() -``` - -## Href Resolution - -PySTAC handles absolute and relative hrefs: - -```python -from pystac.utils import make_absolute_href, make_relative_href - -# Make absolute -absolute = make_absolute_href("./item.json", "/base/path/catalog.json") -# Result: "/base/path/item.json" - -# Make relative -relative = make_relative_href("/path/to/item.json", "/path/catalog.json") -# Result: "./to/item.json" -``` - -## Error Handling - -```python -try: - item = pystac.Item.from_file("path/to/item.json") -except FileNotFoundError: - print("File not found") -except pystac.STACError as e: - print(f"STAC error: {e}") -``` - -## Performance Tips - -1. **Use lazy loading** for large catalogs -2. **Limit recursion depth** when traversing -3. **Use specific type methods** instead of auto-detection -4. **Consider caching** for remote catalogs - -```python -# Specify recursion limit -for item in catalog.get_items(recursive=True, max_depth=2): - process(item) -``` - -## Next Steps - -- Learn about [Core Concepts](concepts.md) -- Explore the [I/O API Reference](../api/io.md) diff --git a/docs/user-guide/items.md b/docs/user-guide/items.md deleted file mode 100644 index e2d018a1a..000000000 --- a/docs/user-guide/items.md +++ /dev/null @@ -1,188 +0,0 @@ -# Working with Items - -A STAC Item is a GeoJSON Feature that represents a single spatiotemporal asset or scene. This guide covers creating, reading, and manipulating STAC Items. - -## Creating Items - -```python -import pystac -from datetime import datetime, timezone - -item = pystac.Item( - id="example-item", - geometry={ - "type": "Polygon", - "coordinates": [[ - [-105.0, 40.0], - [-104.0, 40.0], - [-104.0, 39.0], - [-105.0, 39.0], - [-105.0, 40.0] - ]] - }, - bbox=[-105.0, 39.0, -104.0, 40.0], - datetime=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc), - properties={ - "platform": "satellite-1", - "instruments": ["camera"] - } -) -``` - -## Item Properties - -### Required Properties - -- **id**: Unique identifier for the item -- **geometry**: GeoJSON geometry object (or `None` for non-spatial items) -- **bbox**: Bounding box `[min_lon, min_lat, max_lon, max_lat]` -- **datetime**: Single datetime, or `None` if using datetime range -- **properties**: Dictionary of additional metadata - -### Datetime Handling - -Items can have either a single datetime or a datetime range: - -```python -# Single datetime -item = pystac.Item( - id="single-time", - geometry=geometry, - bbox=bbox, - datetime=datetime.now(timezone.utc), - properties={} -) - -# Datetime range -item = pystac.Item( - id="time-range", - geometry=geometry, - bbox=bbox, - datetime=None, # Must be None for ranges - properties={ - "start_datetime": "2024-01-01T00:00:00Z", - "end_datetime": "2024-01-02T00:00:00Z" - } -) -``` - -## Adding Assets - -Assets represent the actual data files: - -```python -# Add a data asset -item.assets["data"] = pystac.Asset( - href="https://example.com/data.tif", - type="image/tiff; application=geotiff", - title="COG Data", - roles=["data"] -) - -# Add a thumbnail -item.assets["thumbnail"] = pystac.Asset( - href="https://example.com/thumbnail.png", - type="image/png", - roles=["thumbnail"] -) - -# Add metadata -item.assets["metadata"] = pystac.Asset( - href="https://example.com/metadata.xml", - type="application/xml", - roles=["metadata"] -) -``` - -## Links - -Items typically have links to: -- Parent catalog or collection -- Self (canonical location) -- Collection (if part of a collection) - -```python -# Link to collection -item.add_link(pystac.Link( - rel="collection", - target="https://example.com/collection.json" -)) - -# Access links -collection_link = item.get_single_link("collection") -if collection_link: - collection = collection_link.get_stac_object() -``` - -## Collection Membership - -Items can reference their collection: - -```python -# Set collection reference -item.collection_id = "my-collection" - -# Or when adding to a collection -collection.add_item(item) # Automatically sets collection_id -``` - -## Reading Items - -```python -# From file -item = pystac.Item.from_file("path/to/item.json") - -# From dict -item = pystac.Item.from_dict(item_dict) - -# From URL -item = pystac.Item.from_file("https://example.com/item.json") -``` - -## Saving Items - -```python -# Save to specific path -item.save("/path/to/output/item.json") - -# Or render with parent and save -catalog.add_item(item) -catalog.render(root="/path/to/output") -catalog.save() # Saves catalog and all items -``` - -## Item Validation - -```python -from pystac.validate import JsonschemaValidator - -validator = JsonschemaValidator() -errors = validator.validate(item.to_dict()) - -if errors: - for error in errors: - print(f"Error: {error}") -else: - print("Item is valid!") -``` - -## Common Metadata - -Items often include common metadata fields: - -```python -item.properties.update({ - "title": "Example Scene", - "description": "An example satellite scene", - "platform": "satellite-1", - "instruments": ["camera-1"], - "constellation": "sat-constellation", - "gsd": 10.0 # Ground sample distance in meters -}) -``` - -## Next Steps - -- Learn about [Catalogs](catalogs.md) -- Learn about [Collections](collections.md) -- Explore the [Item API Reference](../api/item.md) diff --git a/img/stac-python.png b/img/stac-python.png new file mode 100644 index 0000000000000000000000000000000000000000..798c91ea0f0c9e4e015ee459dcc26be5618081ce GIT binary patch literal 63684 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 H<%9nN;kY(1 literal 0 HcmV?d00001 diff --git a/img/stac-python.xcf b/img/stac-python.xcf new file mode 100644 index 0000000000000000000000000000000000000000..f2b67a870fe32ace82ed6a016791fd5b2450f7fc GIT binary patch literal 259935 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 Date: Thu, 8 Jan 2026 06:47:09 -0700 Subject: [PATCH 10/60] chore: pre-release config --- release-please-config.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index 4ef108150..81f9864a8 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,10 +3,8 @@ ".": { "changelog-path": "CHANGELOG.md", "release-type": "python", - "bump-minor-pre-major": false, - "bump-patch-for-minor-pre-major": false, - "draft": false, - "prerelease": false + "prerelease": true, + "prerelease-type": "dev" } }, "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" From 2d18b73a6cd202a1583f69fb22d5c1cf84537872 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 06:48:37 -0700 Subject: [PATCH 11/60] docs: tweak the warning message --- docs/index.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/index.md b/docs/index.md index 5d10fb5c3..40bb2bd1d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,16 +5,6 @@ These docs are for the work-in-progress v2 of PySTAC. For the current PySTAC v1 docs, see . - Our work plan for v2 goes like this: - - 1. Rebuild the core data structures (`Item`, `Catalog`, `Collection`, etc) from scratch, with new tests - 2. Slowly re-add the old tests to the `tests/v1` one at a time, to make sure that we're breaking as little as possible - 3. If we intentionally break a test (e.g. by relaxing a check on inputs) we'll mark it `xfail` and copy it to test the new expected behavior - - This will take a while. - Watch to track our progress. - We'll sometimes use pull requests, but sometimes not. - **PySTAC** is a Python library for reading and writing [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org) metadata. To install: From 6dcefb8d9d17484db6d8073b1ac34b3c228c660c Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 07:04:45 -0700 Subject: [PATCH 12/60] refactor: split things out to files --- src/pystac/__init__.py | 3 +- src/pystac/catalog.py | 10 +-- src/pystac/collection.py | 50 +------------ src/pystac/container.py | 82 +++++++++++++++++++++ src/pystac/href_generator.py | 49 +++++++++++++ src/pystac/item.py | 3 +- src/pystac/provider.py | 50 +++++++++++++ src/pystac/stac_object.py | 133 ++++------------------------------- 8 files changed, 207 insertions(+), 173 deletions(-) create mode 100644 src/pystac/container.py create mode 100644 src/pystac/href_generator.py create mode 100644 src/pystac/provider.py diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py index f3f9f8c71..2cfaf0e8a 100644 --- a/src/pystac/__init__.py +++ b/src/pystac/__init__.py @@ -7,8 +7,6 @@ from .collection import ( Collection, Extent, - Provider, - ProviderRole, SpatialExtent, TemporalExtent, ) @@ -28,6 +26,7 @@ from .item_collection import ItemCollection from .link import HIERARCHICAL_LINKS, Link from .media_type import MediaType +from .provider import Provider, ProviderRole from .rel_type import RelType from .stac_object import STACObject from .version import __version__ diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py index 63d18b9f1..2fe3e308d 100644 --- a/src/pystac/catalog.py +++ b/src/pystac/catalog.py @@ -5,9 +5,11 @@ from typing_extensions import deprecated +from . import utils from .constants import DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE +from .container import Container from .link import Link -from .stac_object import Container +from .rel_type import RelType class Catalog(Container): @@ -82,7 +84,7 @@ class CatalogType(StrEnum): """ @classmethod - def determine_type(cls, stac_json: dict[str, Any]) -> CatalogType | None: + def determine_type(cls, stac_json: dict[str, Any]) -> CatalogType | None: # pyright: ignore[reportDeprecated] """Determines the catalog type based on a STAC JSON dict. Only applies to Catalogs or Collections @@ -97,10 +99,10 @@ def determine_type(cls, stac_json: dict[str, Any]) -> CatalogType | None: self_link = None relative = False for link in stac_json["links"]: - if link["rel"] == pystac.RelType.SELF: + if link["rel"] == RelType.SELF: self_link = link else: - relative |= not is_absolute_href(link["href"]) + relative |= not utils.is_absolute_href(link["href"]) if self_link: if relative: diff --git a/src/pystac/collection.py b/src/pystac/collection.py index 89b535550..cd224dd71 100644 --- a/src/pystac/collection.py +++ b/src/pystac/collection.py @@ -1,16 +1,15 @@ from __future__ import annotations -import copy import datetime as dt -from enum import StrEnum from typing import Any, ClassVar, TypedDict, cast, override from typing_extensions import deprecated from .asset import Asset, Assets, ItemAsset from .constants import DEFAULT_LICENSE, DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE +from .container import Container from .link import Link -from .stac_object import Container +from .provider import Provider from .utils import datetime_to_str SpatialExtentBboxType = list[float | int] | list[list[float | int]] @@ -217,48 +216,3 @@ def to_datetime_str(datetime: dt.datetime | str | None) -> str | None: return datetime else: return datetime_to_str(datetime) - - -class Provider: - def __init__( - self, - name: str, - description: str | None = None, - roles: list[ProviderRole | str] | None = None, - url: str | None = None, - **kwargs: Any, - ) -> None: - self.name: str = name - self.description: str | None = description - self.roles: list[ProviderRole] | None = roles - self.url: str | None = url - self.extra_fields: dict[str, Any] = kwargs - - @classmethod - def try_from(cls, data: Provider | dict[str, Any]) -> Provider: - if isinstance(data, Provider): - return data - else: - return Provider.from_dict(data) - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> Provider: - return cls(**data) - - def to_dict(self) -> dict[str, Any]: - data = copy.deepcopy(self.extra_fields) - data["name"] = self.name - if self.description: - data["description"] = self.description - if self.roles: - data["roles"] = self.roles - if self.url: - data["url"] = self.url - return data - - -class ProviderRole(StrEnum): - LICENSOR = "licensor" - PRODUCER = "producer" - PROCESSOR = "processor" - HOST = "host" diff --git a/src/pystac/container.py b/src/pystac/container.py new file mode 100644 index 000000000..fe19d13f8 --- /dev/null +++ b/src/pystac/container.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from abc import ABC +from collections.abc import Iterator +from typing import TYPE_CHECKING + +from typing_extensions import deprecated + +from .link import Link +from .rel_type import RelType +from .stac_object import STACObject + +if TYPE_CHECKING: + from .item import Item + + +class Container(STACObject, ABC): + def get_items(self, recursive: bool = False) -> Iterator[Item]: + for link in self.links: + if link.is_item(): + from .item import Item + + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Item): + yield stac_object + elif recursive and link.is_child(): + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Container): + yield from stac_object.get_items(recursive=True) + + def add_item(self, item: Item) -> None: + self.links.append(Link(target=item, rel=RelType.ITEM)) + + def get_child(self, id: str) -> Container | None: + for link in self.get_child_links(): + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Container) and stac_object.id == id: + return stac_object + + def add_child(self, child: Container) -> None: + link = Link(target=child, rel=RelType.CHILD) + self.links.append(link) + + def get_child_links(self) -> list[Link]: + return [link for link in self.links if link.is_child()] + + def get_item_links(self) -> list[Link]: + return [link for link in self.links if link.is_item()] + + @deprecated("Use render instead") + def normalize_hrefs(self, root_href: str) -> None: + from .href_generator import DEFAULT_HREF_GENERATOR + + self.set_self_href(DEFAULT_HREF_GENERATOR.get_root(root_href, self)) + for _ in self.render(): + pass + + def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: + from .item import Item + + self_href = self.get_self_href() + children: list[Container] = [] + items: list[Item] = [] + for link in self.links: + if link.is_child() or link.is_item(): + stac_object = link.get_target(self_href, self.reader) + if isinstance(stac_object, Container): + children.append(stac_object) + elif isinstance(stac_object, Item): + items.append(stac_object) + + yield (self, children, items) + + for child in children: + yield from child.walk() + + def target_in_hierarchy(self, target: STACObject) -> bool: + for root, _, items in self.walk(): + if root == target or any(item == target for item in items): + return True + + return False diff --git a/src/pystac/href_generator.py b/src/pystac/href_generator.py new file mode 100644 index 000000000..9f387a536 --- /dev/null +++ b/src/pystac/href_generator.py @@ -0,0 +1,49 @@ +from typing import TYPE_CHECKING, Protocol + +from .utils import make_absolute_href + +if TYPE_CHECKING: + from .container import Container + from .item import Item + + +class HrefGenerator(Protocol): + def get_root(self, prefix: str, container: Container) -> str: ... + def get_child(self, parent_href: str, container: Container) -> str: ... + def get_item(self, parent_href: str, item: Item) -> str: ... + + +class BestPracticesHrefGenerator: + def get_root(self, prefix: str, container: Container) -> str: + from .catalog import Catalog + from .collection import Collection + + if isinstance(container, Catalog): + return make_absolute_href(prefix, "./catalog.json", start_is_dir=True) + elif isinstance(container, Collection): + return make_absolute_href(prefix, "./collection.json", start_is_dir=True) + else: + raise ValueError(f"Unsupported root type: {type(container)}") + + def get_child(self, parent_href: str, container: Container) -> str: + from .catalog import Catalog + from .collection import Collection + + if isinstance(container, Catalog): + file_name = "catalog.json" + elif isinstance(container, Collection): + file_name = "collection.json" + else: + raise ValueError(f"Unsupported child type: {type(container)}") + + return make_absolute_href( + parent_href, "/".join((container.id, file_name)), start_is_dir=False + ) + + def get_item(self, parent_href: str, item: Item) -> str: + return make_absolute_href( + parent_href, "/".join((item.id, f"{item.id}.json")), start_is_dir=False + ) + + +DEFAULT_HREF_GENERATOR = BestPracticesHrefGenerator() diff --git a/src/pystac/item.py b/src/pystac/item.py index d3716707c..310370db5 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -11,9 +11,10 @@ from .asset import Asset, Assets from .constants import DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE +from .container import Container from .geo_interface import GeoInterface from .link import Link -from .stac_object import Container, STACObject +from .stac_object import STACObject from .utils import datetime_to_str, str_to_datetime if TYPE_CHECKING: diff --git a/src/pystac/provider.py b/src/pystac/provider.py new file mode 100644 index 000000000..b1e679192 --- /dev/null +++ b/src/pystac/provider.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import copy +from enum import StrEnum +from typing import Any + + +class Provider: + def __init__( + self, + name: str, + description: str | None = None, + roles: list[ProviderRole | str] | None = None, + url: str | None = None, + **kwargs: Any, + ) -> None: + self.name: str = name + self.description: str | None = description + self.roles: list[ProviderRole | str] | None = roles + self.url: str | None = url + self.extra_fields: dict[str, Any] = kwargs + + @classmethod + def try_from(cls, data: Provider | dict[str, Any]) -> Provider: + if isinstance(data, Provider): + return data + else: + return Provider.from_dict(data) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Provider: + return cls(**data) + + def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) + data["name"] = self.name + if self.description: + data["description"] = self.description + if self.roles: + data["roles"] = self.roles + if self.url: + data["url"] = self.url + return data + + +class ProviderRole(StrEnum): + LICENSOR = "licensor" + PRODUCER = "producer" + PROCESSOR = "processor" + HOST = "host" diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 35654523b..b1e2b49e5 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -6,7 +6,7 @@ from collections.abc import Iterator from enum import StrEnum from pathlib import Path -from typing import TYPE_CHECKING, Any, ClassVar, Protocol, override +from typing import TYPE_CHECKING, Any, ClassVar, override from typing_extensions import deprecated @@ -23,7 +23,8 @@ if TYPE_CHECKING: from .collection import Collection - from .item import Item + from .container import Container + from .href_generator import HrefGenerator def __getattr__(name: str) -> Any: @@ -31,61 +32,12 @@ def __getattr__(name: str) -> Any: if name == "S": warnings.warn( - "pystac.stac_object.S is deprecated, just use `type[STACObject]` instead" + "pystac.stac_object.S is deprecated, use `type[STACObject]` instead" ) return TypeVar("S", bound="STACObject") raise AttributeError(f"module {__name__} has no attribute {name}") -class HrefGenerator(Protocol): - def get_root(self, prefix: str, container: Container) -> str: ... - def get_child(self, parent_href: str, container: Container) -> str: ... - def get_item(self, parent_href: str, item: Item) -> str: ... - - -class BestPracticesHrefGenerator: - def get_root(self, prefix: str, container: Container) -> str: - from .catalog import Catalog - from .collection import Collection - - if isinstance(container, Catalog): - return make_absolute_href(prefix, "./catalog.json", start_is_dir=True) - elif isinstance(container, Collection): - return make_absolute_href(prefix, "./collection.json", start_is_dir=True) - else: - raise ValueError(f"Unsupported root type: {type(container)}") - - def get_child(self, parent_href: str, container: Container) -> str: - from .catalog import Catalog - from .collection import Collection - - if isinstance(container, Catalog): - file_name = "catalog.json" - elif isinstance(container, Collection): - file_name = "collection.json" - else: - raise ValueError(f"Unsupported child type: {type(container)}") - - return make_absolute_href( - parent_href, "/".join((container.id, file_name)), start_is_dir=False - ) - - def get_item(self, parent_href: str, item: Item) -> str: - return make_absolute_href( - parent_href, "/".join((item.id, f"{item.id}.json")), start_is_dir=False - ) - - -DEFAULT_HREF_GENERATOR = BestPracticesHrefGenerator() - - -@deprecated("STACObjectType is deprecated") -class STACObjectType(StrEnum): - CATALOG = "Catalog" - COLLECTION = "Collection" - ITEM = "Feature" - - class STACObject(ABC): type: ClassVar[STAC_OBJECT_TYPE] @@ -282,10 +234,15 @@ def render( parent: Container | None = None, collection: Collection | None = None, use_absolute_links: bool = False, - href_generator: HrefGenerator = DEFAULT_HREF_GENERATOR, + href_generator: HrefGenerator | None = None, ) -> Iterator[STACObject]: from .item import Item + if href_generator is None: + from .href_generator import DEFAULT_HREF_GENERATOR + + href_generator = DEFAULT_HREF_GENERATOR + self_href = self.get_self_href() if self_href is None: raise ValueError("Cannot render a self href") @@ -403,68 +360,8 @@ def _maybe_get_link_target(self, rel: str) -> STACObject | None: return None -class Container(STACObject, ABC): - def get_items(self, recursive: bool = False) -> Iterator[Item]: - for link in self.links: - if link.is_item(): - from .item import Item - - stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Item): - yield stac_object - elif recursive and link.is_child(): - stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Container): - yield from stac_object.get_items(recursive=True) - - def add_item(self, item: Item) -> None: - self.links.append(Link(target=item, rel=RelType.ITEM)) - - def get_child(self, id: str) -> Container | None: - for link in self.get_child_links(): - stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Container) and stac_object.id == id: - return stac_object - - def add_child(self, child: Container) -> None: - link = Link(target=child, rel=RelType.CHILD) - self.links.append(link) - - def get_child_links(self) -> list[Link]: - return [link for link in self.links if link.is_child()] - - def get_item_links(self) -> list[Link]: - return [link for link in self.links if link.is_item()] - - @deprecated("Use render instead") - def normalize_hrefs(self, root_href: str) -> None: - href_generator = DEFAULT_HREF_GENERATOR - self.set_self_href(href_generator.get_root(root_href, self)) - for _ in self.render(): - pass - - def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: - from .item import Item - - self_href = self.get_self_href() - children: list[Container] = [] - items: list[Item] = [] - for link in self.links: - if link.is_child() or link.is_item(): - stac_object = link.get_target(self_href, self.reader) - if isinstance(stac_object, Container): - children.append(stac_object) - elif isinstance(stac_object, Item): - items.append(stac_object) - - yield (self, children, items) - - for child in children: - yield from child.walk() - - def target_in_hierarchy(self, target: STACObject) -> bool: - for root, _, items in self.walk(): - if root == target or any(item == target for item in items): - return True - - return False +@deprecated("STACObjectType is deprecated") +class STACObjectType(StrEnum): + CATALOG = "Catalog" + COLLECTION = "Collection" + ITEM = "Feature" From 48bcf38b5b3a27a3fc1c6feed3db0cbff420505d Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 07:14:28 -0700 Subject: [PATCH 13/60] fix: remove src/pystac/v1 --- src/pystac/v1/__init__.py | 256 -------------------- src/pystac/v1/client.py | 23 -- src/pystac/v1/provider.py | 122 ---------- src/pystac/v1/static/__init__.py | 0 src/pystac/v1/static/fields-normalized.json | 1 - 5 files changed, 402 deletions(-) delete mode 100644 src/pystac/v1/__init__.py delete mode 100644 src/pystac/v1/client.py delete mode 100644 src/pystac/v1/provider.py delete mode 100644 src/pystac/v1/static/__init__.py delete mode 100644 src/pystac/v1/static/fields-normalized.json diff --git a/src/pystac/v1/__init__.py b/src/pystac/v1/__init__.py deleted file mode 100644 index edf89eb13..000000000 --- a/src/pystac/v1/__init__.py +++ /dev/null @@ -1,256 +0,0 @@ -# isort: skip_file -""" -PySTAC is a library for working with SpatioTemporal Asset Catalogs (STACs) -""" - -__all__ = [ - "__version__", - "TemplateError", - "STACError", - "STACTypeError", - "DuplicateObjectKeyError", - "ExtensionAlreadyExistsError", - "ExtensionNotImplemented", - "ExtensionTypeError", - "RequiredPropertyMissing", - "STACValidationError", - "DeprecatedWarning", - "MediaType", - "RelType", - "StacIO", - "STACObject", - "STACObjectType", - "Link", - "HIERARCHICAL_LINKS", - "Catalog", - "CatalogType", - "Collection", - "Extent", - "SpatialExtent", - "TemporalExtent", - "Summaries", - "CommonMetadata", - "RangeSummary", - "Item", - "Asset", - "ItemAssetDefinition", - "ItemCollection", - "Provider", - "ProviderRole", - "read_file", - "read_dict", - "write_file", - "get_stac_version", - "set_stac_version", -] - -import warnings -from typing import Any - -from pystac.errors import ( - TemplateError, - STACError, - STACTypeError, - DuplicateObjectKeyError, - ExtensionAlreadyExistsError, - ExtensionNotImplemented, - ExtensionTypeError, - RequiredPropertyMissing, - STACValidationError, - DeprecatedWarning, -) - -from pystac.version import ( - __version__, - get_stac_version, - set_stac_version, -) -from pystac.media_type import MediaType -from pystac.rel_type import RelType -from pystac.stac_io import StacIO -from pystac.stac_object import STACObject, STACObjectType -from pystac.link import Link, HIERARCHICAL_LINKS -from pystac.catalog import Catalog, CatalogType -from pystac.collection import ( - Collection, - Extent, - SpatialExtent, - TemporalExtent, -) -from pystac.common_metadata import CommonMetadata -from pystac.summaries import RangeSummary, Summaries -from pystac.asset import Asset -from pystac.item import Item -from pystac.item_assets import ItemAssetDefinition -from pystac.item_collection import ItemCollection -from pystac.provider import ProviderRole, Provider -from pystac.utils import HREF - -import pystac.extensions.hooks -import pystac.extensions.classification -import pystac.extensions.datacube -import pystac.extensions.eo -import pystac.extensions.file -import pystac.extensions.grid -import pystac.extensions.item_assets - -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - import pystac.extensions.label -import pystac.extensions.mgrs -import pystac.extensions.mlm -import pystac.extensions.pointcloud -import pystac.extensions.projection -import pystac.extensions.raster -import pystac.extensions.sar -import pystac.extensions.sat -import pystac.extensions.scientific -import pystac.extensions.storage -import pystac.extensions.table -import pystac.extensions.timestamps -import pystac.extensions.version -import pystac.extensions.view -import pystac.extensions.xarray_assets - -EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks( - [ - pystac.extensions.classification.CLASSIFICATION_EXTENSION_HOOKS, - pystac.extensions.datacube.DATACUBE_EXTENSION_HOOKS, - pystac.extensions.eo.EO_EXTENSION_HOOKS, - pystac.extensions.file.FILE_EXTENSION_HOOKS, - pystac.extensions.grid.GRID_EXTENSION_HOOKS, - pystac.extensions.item_assets.ITEM_ASSETS_EXTENSION_HOOKS, - pystac.extensions.label.LABEL_EXTENSION_HOOKS, - pystac.extensions.mgrs.MGRS_EXTENSION_HOOKS, - pystac.extensions.mlm.MLM_EXTENSION_HOOKS, - pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS, - pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS, - pystac.extensions.raster.RASTER_EXTENSION_HOOKS, - pystac.extensions.sar.SAR_EXTENSION_HOOKS, - pystac.extensions.sat.SAT_EXTENSION_HOOKS, - pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS, - pystac.extensions.storage.STORAGE_EXTENSION_HOOKS, - pystac.extensions.table.TABLE_EXTENSION_HOOKS, - pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, - pystac.extensions.version.VERSION_EXTENSION_HOOKS, - pystac.extensions.view.VIEW_EXTENSION_HOOKS, - pystac.extensions.xarray_assets.XARRAY_ASSETS_EXTENSION_HOOKS, - ] -) - - -def read_file(href: HREF, stac_io: StacIO | None = None) -> STACObject: - """Reads a STAC object from a file. - - This method will return either a Catalog, a Collection, or an Item based on what - the file contains. - - This is a convenience method for :meth:`StacIO.read_stac_object - ` - - Args: - href : The HREF to read the object from. - stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not - provided, will use :meth:`StacIO.default` to create an instance. - - Returns: - The specific STACObject implementation class that is represented - by the JSON read from the file located at HREF. - - Raises: - STACTypeError : If the file at ``href`` does not represent a valid - :class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` - is not a :class:`~pystac.STACObject` and must be read using - :meth:`ItemCollection.from_file ` - """ - if stac_io is None: - stac_io = StacIO.default() - return stac_io.read_stac_object(href) - - -def write_file( - obj: STACObject, - include_self_link: bool = True, - dest_href: HREF | None = None, - stac_io: StacIO | None = None, -) -> None: - """Writes a STACObject to a file. - - This will write only the Catalog, Collection or Item ``obj``. It will not attempt - to write any other objects that are linked to ``obj``; if you'd like functionality - to save off catalogs recursively see :meth:`Catalog.save `. - - This method will write the JSON of the object to the object's assigned "self" link - or to the dest_href if provided. To set the self link, see - :meth:`STACObject.set_self_href `. - - Convenience method for :meth:`STACObject.from_file ` - - Args: - obj : The STACObject to save. - include_self_link : If ``True``, include the ``"self"`` link with this object. - Otherwise, leave out the self link. - dest_href : Optional HREF to save the file to. If ``None``, the object will be - saved to the object's ``"self"`` href. - stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not - provided, will use :meth:`StacIO.default` to create an instance. - """ - if stac_io is None: - stac_io = StacIO.default() - import os - - dest_href = None if dest_href is None else str(os.fspath(dest_href)) - obj.save_object( - include_self_link=include_self_link, dest_href=dest_href, stac_io=stac_io - ) - - -def read_dict( - d: dict[str, Any], - href: str | None = None, - root: Catalog | None = None, - stac_io: StacIO | None = None, -) -> STACObject: - """Reads a :class:`~STACObject` or :class:`~ItemCollection` from a JSON-like dict - representing a serialized STAC object. - - This method will return either a :class:`~Catalog`, :class:`~Collection`, - or :class`~Item` based on the contents of the dict. - - This is a convenience method for either - :meth:`StacIO.stac_object_from_dict `. - - Args: - d : The dict to parse. - href : Optional href that is the file location of the object being - parsed. - root : Optional root of the catalog for this object. - If provided, the root's resolved object cache can be used to search for - previously resolved instances of the STAC object. - stac_io: Optional :class:`~StacIO` instance to use for reading. If ``None``, - the default instance will be used. - - Raises: - STACTypeError : If the ``d`` dictionary does not represent a valid - :class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` - is not a :class:`~pystac.STACObject` and must be read using - :meth:`ItemCollection.from_dict ` - """ - if stac_io is None: - stac_io = StacIO.default() - return stac_io.stac_object_from_dict(d, href, root) - - -def __getattr__(name: str) -> Any: - if name == "validation": - import warnings - import pystac.validation - - warnings.warn( - "pystac.validation will not be automatically imported to the package in " - "pystac v2.0. Instead, import it directly: `import pystac.validation`", - DeprecationWarning, - stacklevel=2, - ) - return pystac.validation - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/src/pystac/v1/client.py b/src/pystac/v1/client.py deleted file mode 100644 index f429bf10b..000000000 --- a/src/pystac/v1/client.py +++ /dev/null @@ -1,23 +0,0 @@ -# mypy: ignore-errors - -_import_error_message = ( - "pystac-client is not installed.\n\n" - "Please install pystac-client:\n\n" - " pip install pystac-client" -) - -try: - from pystac_client import * # noqa: F403 -except ImportError as e: - if e.msg == "No module named 'pystac_client'": - raise ImportError(_import_error_message) from e - else: - raise - - -def __getattr__(value: str): - try: - import pystac_client - except ImportError as e: - raise ImportError(_import_error_message) from e - return getattr(pystac_client, value) diff --git a/src/pystac/v1/provider.py b/src/pystac/v1/provider.py deleted file mode 100644 index 673a41d85..000000000 --- a/src/pystac/v1/provider.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any - -from pystac.utils import StringEnum - - -class ProviderRole(StringEnum): - """Enumerates the allows values of the Provider "role" field.""" - - LICENSOR = "licensor" - PRODUCER = "producer" - PROCESSOR = "processor" - HOST = "host" - - -class Provider: - """Provides information about a provider of STAC data. A provider is any of the - organizations that captured or processed the content of the collection and therefore - influenced the data offered by this collection. May also include information about - the final storage provider hosting the data. - - Args: - name : The name of the organization or the individual. - description : Optional multi-line description to add further provider - information such as processing details for processors and producers, - hosting details for hosts or basic contact information. - roles : Optional roles of the provider. Any of - licensor, producer, processor or host. - url : Optional homepage on which the provider describes the dataset - and publishes contact information. - extra_fields : Optional dictionary containing additional top-level fields - defined on the Provider object. - """ - - name: str - """The name of the organization or the individual.""" - - description: str | None - """Optional multi-line description to add further provider - information such as processing details for processors and producers, - hosting details for hosts or basic contact information.""" - - roles: list[ProviderRole] | None - """Optional roles of the provider. Any of - licensor, producer, processor or host.""" - - url: str | None - """Optional homepage on which the provider describes the dataset - and publishes contact information.""" - - extra_fields: dict[str, Any] - """Dictionary containing additional top-level fields defined on the Provider - object.""" - - def __init__( - self, - name: str, - description: str | None = None, - roles: list[ProviderRole] | None = None, - url: str | None = None, - extra_fields: dict[str, Any] | None = None, - ): - self.name = name - self.description = description - self.roles = roles - self.url = url - self.extra_fields = extra_fields or {} - - def __eq__(self, o: object) -> bool: - if not isinstance(o, Provider): - return NotImplemented - return self.to_dict() == o.to_dict() - - def _repr_html_(self) -> str: - from html import escape - - from pystac.html.jinja_env import get_jinja_env - - jinja_env = get_jinja_env() - if jinja_env: - template = jinja_env.get_template("JSON.jinja2") - return str(template.render(dict=self.to_dict(), plain=escape(repr(self)))) - else: - return escape(repr(self)) - - def to_dict(self) -> dict[str, Any]: - """Returns this provider as a dictionary. - - Returns: - dict: A serialization of the Provider. - """ - d: dict[str, Any] = {"name": self.name} - if self.description is not None: - d["description"] = self.description - if self.roles is not None: - d["roles"] = self.roles - if self.url is not None: - d["url"] = self.url - - d.update(self.extra_fields) - - return d - - @staticmethod - def from_dict(d: dict[str, Any]) -> "Provider": - """Constructs a Provider from a dict. - - Returns: - Provider: The Provider deserialized from the JSON dict. - """ - return Provider( - name=d["name"], - description=d.get("description"), - roles=d.get( - "roles", - ), - url=d.get("url"), - extra_fields={ - k: v - for k, v in d.items() - if k not in {"name", "description", "roles", "url"} - }, - ) diff --git a/src/pystac/v1/static/__init__.py b/src/pystac/v1/static/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/pystac/v1/static/fields-normalized.json b/src/pystac/v1/static/fields-normalized.json deleted file mode 100644 index 9d5e1dff5..000000000 --- a/src/pystac/v1/static/fields-normalized.json +++ /dev/null @@ -1 +0,0 @@ -{"metadata":{"id":{"label":"Identifier"},"keywords":{"label":"Keywords"},"datetime":{"label":"Time of Data","format":"Timestamp","summary":false},"title":{"label":"Title","summary":false},"description":{"label":"Description","format":"CommonMark","summary":false},"roles":{"label":"Purpose"},"start_datetime":{"label":"Time of Data begins","format":"Timestamp","summary":false},"end_datetime":{"label":"Time of Data ends","format":"Timestamp","summary":false},"created":{"label":"Created","format":"Timestamp","summary":"r"},"updated":{"label":"Updated","format":"Timestamp","summary":"r"},"published":{"label":"Published","format":"Timestamp","summary":"r"},"expires":{"label":"Expires","format":"Timestamp","summary":"r"},"unpublished":{"label":"Unpublished","format":"Timestamp","summary":"r"},"license":{"label":"License","format":"License","summary":false},"providers":{"label":"Providers","format":"Providers","summary":false},"platform":{"label":"Platform"},"instruments":{"label":"Instruments","format":"CSV"},"constellation":{"label":"Constellation"},"mission":{"label":"Mission"},"gsd":{"label":"GSD","explain":"Ground Sample Distance","unit":"m"},"bands":{"label":"Bands","items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"description":{"alias":"description","order":2,"label":"Description","format":"CommonMark","summary":false},"gsd":{"alias":"gsd","sortable":true,"label":"GSD","explain":"Ground Sample Distance","unit":"m"},"nodata":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"data_type":{"alias":"data_type","sortable":true,"label":"Data Type of Values","format":"FileDataType"},"statistics":{"alias":"statistics","label":"Statistics","listWithKeys":true,"items":{"mean":{"label":"Average"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stdev":{"label":"Std. Dev.","explain":"Standard Deviation"},"count":{"label":"Count","explain":"Total number of values"},"valid_percent":{"label":"Valid","explain":"Percentage of valid values","unit":"%"}},"itemOrder":["mean","count","maximum","minimum","stdev","valid_percent"]},"eo:common_name":{"alias":"eo:common_name","sortable":true,"order":1,"label":"Common Name"},"eo:center_wavelength":{"alias":"eo:center_wavelength","sortable":true,"label":"Wavelength","explain":"The center wavelength of the band","unit":"μm"},"eo:full_width_half_max":{"alias":"eo:full_width_half_max","sortable":true,"label":"FWHM","explain":"Full Width Half Max","unit":"μm"},"eo:solar_illumination":{"alias":"eo:solar_illumination","sortable":true,"label":"Solar Illumination","unit":"W/m²/μm"},"raster:sampling":{"alias":"raster:sampling","sortable":true,"label":"Sampling","mapping":{"area":"Area","point":"Point (at pixel center)"}},"unit":{"alias":"unit","sortable":true,"label":"Unit of Values"},"raster:bits_per_sample":{"alias":"raster:bits_per_sample","sortable":true,"label":"Bits per Sample"},"raster:spatial_resolution":{"alias":"raster:spatial_resolution","sortable":true,"label":"Resolution","explain":"Average spatial resolution","unit":"m"},"raster:scale":{"alias":"raster:scale","sortable":true,"label":"Scale"},"raster:offset":{"alias":"raster:offset","sortable":true,"label":"Offset"},"raster:histogram":{"alias":"raster:histogram","label":"Histogram","custom":true},"classification:classes":{"alias":"classification:classes","summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"classification:bitfields":{"alias":"classification:bitfields","summary":false,"label":"Bit Mask","items":{"name":{"label":"Name","sortable":true,"order":0},"offset":{"label":"Offset","explain":"Offset to the first bit","order":1},"length":{"label":"Number of bits","order":2},"description":{"label":"Description","order":3,"format":"CommonMark"},"classes":{"alias":"classification:classes","summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"roles":{"label":"Purpose"}},"itemOrder":["classes","name","offset","length","description","roles"]}},"itemOrder":["name","classification:bitfields","raster:bits_per_sample","classification:classes","eo:common_name","data_type","description","eo:full_width_half_max","gsd","raster:histogram","nodata","raster:offset","raster:spatial_resolution","raster:sampling","raster:scale","eo:solar_illumination","statistics","unit","eo:center_wavelength"]},"nodata":{"label":"No-Data Values","format":"CSV","summary":false},"data_type":{"label":"Data Type of Values","format":"FileDataType"},"unit":{"label":"Unit of Values"},"statistics":{"label":"Statistics","listWithKeys":true,"items":{"mean":{"label":"Average"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stdev":{"label":"Std. Dev.","explain":"Standard Deviation"},"count":{"label":"Count","explain":"Total number of values"},"valid_percent":{"label":"Valid","explain":"Percentage of valid values","unit":"%"}},"itemOrder":["mean","count","maximum","minimum","stdev","valid_percent"]},"version":{"label":"Data Version","summary":false},"deprecated":{"label":"Deprecated","summary":false},"experimental":{"label":"Experimental","summary":false},"language":{"label":"Current Language","ext":"language","summary":"v","properties":{"name":{"label":"Name"},"alternate":{"label":"Alternate Name"},"code":{"label":"Code"},"dir":{"label":"Direction","explain":"Reading and writing direction","mapping":{"ltr":"left-to-right","rtl":"right-to-left"},"default":"ltr"}}},"languages":{"label":"Available Languages","ext":"language","summary":false,"items":{"name":{"label":"Name","sortable":true,"order":0},"alternate":{"label":"Alternate Name","sortable":true,"order":1},"code":{"label":"Code","sortable":true,"order":2},"dir":{"label":"Direction","explain":"Reading and writing direction","sortable":true,"order":3,"mapping":{"ltr":"left-to-right","rtl":"right-to-left"},"default":"ltr"}},"itemOrder":["name","alternate","code","dir"]},"contacts":{"label":"Contacts","ext":"contacts","summary":"v","items":{"name":{"label":"Name"},"identifier":{"label":"Identifier"},"position":{"label":"Position"},"organization":{"label":"Organization"},"logo":{"label":"Logo","format":"Image"},"phones":{"label":"Phone","items":{"value":{"label":"Number","format":"Phone","order":0},"roles":{"label":"Used For","order":1,"mapping":{"work":"Work","home":"Personal","fax":"Fax"}}},"itemOrder":["value","roles"]},"emails":{"label":"Email","items":{"value":{"label":"Address","format":"Email","order":0},"roles":{"label":"Used For","order":1,"mapping":{"work":"Work","home":"Personal"}}},"itemOrder":["value","roles"]},"addresses":{"label":"Postal Addresses","format":"Address","items":{"deliveryPoint":{"label":"Street / House","order":0},"city":{"label":"City","order":1},"administrativeArea":{"label":"State / Province","order":2},"postalCode":{"label":"Postal Code","order":3},"country":{"label":"Country","order":4}},"itemOrder":["deliveryPoint","city","administrativeArea","postalCode","country"]},"links":{"label":"Additional Resources","format":"Link"},"contactInstructions":{"label":"Further Instructions"},"roles":{"label":"Types","format":"CSV"}},"itemOrder":["links","emails","contactInstructions","identifier","logo","name","organization","phones","position","addresses","roles"]},"themes":{"label":"Themes","ext":"themes","summary":false,"items":{"scheme":{"label":"Vocabulary","order":0,"format":"Url"},"concepts":{"label":"Terms","order":1,"format":"Concepts","items":{"id":{"label":"Identifier","order":0},"title":{"label":"Title","order":1},"description":{"label":"Description","order":2},"url":{"label":"URL","order":3,"format":"Url"}},"itemOrder":["id","title","description","url"]}},"itemOrder":["scheme","concepts"]},"crs":{"label":"CRS","format":"CRS","explain":"Coordinate Reference System"},"anon:size":{"label":"Uncertainty","unit":"°","explain":"The size of one side of the anonymized bounding box"},"anon:warning":{"label":"Warning","summary":false},"classification:classes":{"summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"classification:bitfields":{"summary":false,"label":"Bit Mask","items":{"name":{"label":"Name","sortable":true,"order":0},"offset":{"label":"Offset","explain":"Offset to the first bit","order":1},"length":{"label":"Number of bits","order":2},"description":{"label":"Description","order":3,"format":"CommonMark"},"classes":{"alias":"classification:classes","summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"roles":{"label":"Purpose"}},"itemOrder":["classes","name","offset","length","description","roles"]},"cube:dimensions":{"label":"Dimensions","summary":false,"listWithKeys":true,"items":{"type":{"label":"Type","order":0},"axis":{"label":"Axis","order":1},"description":{"label":"Description","format":"CommonMark","order":2},"extent":{"label":"Extent","format":"Extent","order":3},"bbox":{"alias":"proj:bbox","order":3,"label":"Bounding Box","custom":true,"summary":false},"values":{"label":"Values","order":4},"step":{"label":"Step","order":5},"unit":{"alias":"unit","order":5,"label":"Unit of Values"},"geometry_types":{"label":"Geometry Types","order":5},"reference_system":{"label":"Reference System","explain":"Coordinate / Temporal / Other Reference System","order":6}},"itemOrder":["type","axis","description","extent","bbox","values","step","unit","geometry_types","reference_system"]},"cube:variables":{"label":"Variables","summary":false,"listWithKeys":true,"items":{"dimensions":{"label":"Dimensions","order":0},"type":{"label":"Type","order":1,"mapping":{"data":"Measured values","auxiliary":"Coordinate data"}},"description":{"label":"Description","format":"CommonMark","order":2},"extent":{"label":"Extent","format":"Extent","order":3},"values":{"label":"Values","order":4},"step":{"label":"Step","order":5},"unit":{"alias":"unit","order":6,"label":"Unit of Values"}},"itemOrder":["dimensions","type","description","extent","values","step","unit"]},"eo:bands":{"label":"Spectral Bands","items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"common_name":{"alias":"eo:common_name","sortable":true,"order":1,"label":"Common Name"},"center_wavelength":{"alias":"eo:center_wavelength","sortable":true,"order":2,"label":"Wavelength","explain":"The center wavelength of the band","unit":"μm"},"full_width_half_max":{"alias":"eo:full_width_half_max","sortable":true,"order":3,"label":"FWHM","explain":"Full Width Half Max","unit":"μm"},"solar_illumination":{"alias":"eo:solar_illumination","sortable":true,"order":7,"label":"Solar Illumination","unit":"W/m²/μm"},"description":{"alias":"description","label":"Description","format":"CommonMark","summary":false},"gsd":{"alias":"gsd","sortable":true,"label":"GSD","explain":"Ground Sample Distance","unit":"m"},"cloud_cover":{"alias":"eo:cloud_cover","sortable":true,"label":"Cloud Cover","unit":"%"}},"itemOrder":["name","cloud_cover","common_name","description","gsd","center_wavelength","full_width_half_max","solar_illumination"]},"eo:cloud_cover":{"label":"Cloud Cover","unit":"%"},"eo:snow_cover":{"label":"Snow/Ice Cover","unit":"%"},"eo:common_name":{"label":"Common Name"},"eo:center_wavelength":{"label":"Wavelength","explain":"The center wavelength of the band","unit":"μm"},"eo:full_width_half_max":{"label":"FWHM","explain":"Full Width Half Max","unit":"μm"},"eo:solar_illumination":{"label":"Solar Illumination","unit":"W/m²/μm"},"forecast:reference_datetime":{"label":"Reference Time","format":"Timestamp","summary":"r"},"forecast:horizon":{"label":"Forecast Horizon","explain":"The time between the reference time and the forecast time","format":"Duration"},"forecast:duration":{"label":"Forecast Length","format":"Duration"},"file:bits_per_sample":{"alias":"raster:bits_per_sample","label":"Bits per Sample"},"file:byte_order":{"label":"Byte Order"},"file:checksum":{"label":"Checksum","format":"Checksum","summary":false},"file:data_type":{"alias":"data_type","label":"Data Type of Values","format":"FileDataType"},"file:header_size":{"label":"Header Size","format":"FileSize","summary":false},"file:nodata":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"file:size":{"label":"Size","format":"FileSize","summary":false},"file:unit":{"alias":"unit","label":"Unit of Values"},"file:values":{"label":"Map of Values","summary":false,"items":{"values":{"label":"Values","format":"CSV","order":1},"summary":{"label":"Summary","order":0}},"itemOrder":["summary","values"]},"file:local_path":{"label":"Local Path","summary":false},"nodata:values":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"goes:orbital_slot":{"label":"Orbital Slot"},"goes:system_environment":{"label":"System Environment","mapping":{"OR":"Operational system, real-time data","OT":"Operational system, test data","IR":"Test system, real-time data","IT":"Test system, test data","IP":"Test system, playback data","IS":"Test system, simulated data"}},"goes:image_type":{"label":"Area","mapping":{"FULL DISK":"The Americas (full disk)","CONUS":"North America (continental US)","MESOSCALE":"Central/South America (mesoscale)"}},"goes:mesoscale_image_number":{"label":"Area in Central/South America","mapping":{"1":"Region 1","2":"Region 2"}},"goes:mode":{"label":"Capture Mode","mapping":{"3":"3: 1x full disk, 3x continental US, 30x mesoscale region 1, 30x mesoscale region 2 (every 15 minutes)","4":"4: 1x full disk (every 5 minutes)","6":"6: 1x full disk, 2x continental US, 20x mesoscale region 1, 20x mesoscale region 2 (every 10 minutes)"}},"goes:group_time_threshold":{"label":"Time Threshold in a Group","explain":"Lightning group maximum time difference among lightning events in a group","unit":"s"},"goes:flash_time_threshold":{"label":"Time Threshold in a Flash","explain":"Lightning flash maximum time difference among lightning events in a flash","unit":"s"},"goes:lightning_wavelength":{"label":"Central Wavelength","unit":"nm"},"goes:yaw_flip_flag":{"label":"Yaw Flip Configuration","explain":"Flag indicating that the spacecraft is operating in yaw flip configuration.","mapping":{"0":"Upright","1":"Neither","2":"Inverted"}},"goes:event_count":{"label":"Lightning Events"},"goes:group_count":{"label":"Lightning Groups"},"goes:flash_count":{"label":"Lightning Flashes"},"goes:nominal_satellite_subpoint_lat":{"label":"Satellite Subpoint Latitude","unit":"°N"},"goes:nominal_satellite_subpoint_lon":{"label":"Satellite Subpoint Longitude","unit":"°E"},"goes:nominal_satellite_height":{"label":"Satellite Height","explain":"Nominal satellite height above GRS 80 ellipsoid","unit":"km"},"goes:percent_navigated_L1b_events":{"label":"Events navigated by Instrument","format":"Percent0to1","unit":"%"},"goes:percent_uncorrectable_L0_errors":{"label":"Data Lost","format":"Percent0to1","unit":"%"},"grid:code":{"label":"Grid","format":"GridCode"},"raster:bands":{"label":"Layers","items":{"nodata":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"sampling":{"alias":"raster:sampling","sortable":true,"label":"Sampling","mapping":{"area":"Area","point":"Point (at pixel center)"}},"data_type":{"alias":"data_type","sortable":true,"label":"Data Type of Values","format":"FileDataType"},"bits_per_sample":{"alias":"raster:bits_per_sample","sortable":true,"label":"Bits per Sample"},"spatial_resolution":{"alias":"raster:spatial_resolution","sortable":true,"label":"Resolution","explain":"Average spatial resolution","unit":"m"},"statistics":{"alias":"statistics","label":"Statistics","listWithKeys":true,"items":{"mean":{"label":"Average"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stdev":{"label":"Std. Dev.","explain":"Standard Deviation"},"count":{"label":"Count","explain":"Total number of values"},"valid_percent":{"label":"Valid","explain":"Percentage of valid values","unit":"%"}},"itemOrder":["mean","count","maximum","minimum","stdev","valid_percent"]},"unit":{"alias":"unit","sortable":true,"label":"Unit of Values"},"scale":{"alias":"raster:scale","sortable":true,"label":"Scale"},"offset":{"alias":"raster:offset","sortable":true,"label":"Offset"},"histogram":{"alias":"raster:histogram","label":"Histogram","custom":true}},"itemOrder":["bits_per_sample","data_type","histogram","nodata","offset","spatial_resolution","sampling","scale","statistics","unit"]},"raster:sampling":{"label":"Sampling","mapping":{"area":"Area","point":"Point (at pixel center)"}},"raster:bits_per_sample":{"label":"Bits per Sample"},"raster:spatial_resolution":{"label":"Resolution","explain":"Average spatial resolution","unit":"m"},"raster:scale":{"label":"Scale"},"raster:offset":{"label":"Offset"},"raster:histogram":{"label":"Histogram","custom":true},"label:properties":{"label":"Properties","null":"raster data"},"label:classes":{"label":"Classes","items":{"name":{"label":"Name","null":"raster-formatted","sortable":true,"id":true},"classes":{"label":"Classes"}},"itemOrder":["name","classes"]},"label:description":{"label":"Description","format":"CommonMark","summary":false},"label:type":{"label":"Type"},"label:tasks":{"label":"Tasks"},"label:methods":{"label":"Methods"},"label:overviews":{"label":"Overviews","summary":false,"items":{"property_key":{"label":"Property Key","id":true},"counts":{"label":"Counts","custom":true},"statistics":{"label":"Statistics","custom":true}},"itemOrder":["property_key","counts","statistics"]},"mgrs:latitude_band":{"label":"Latitude Band"},"mgrs:grid_square":{"label":"Grid Square"},"mgrs:utm_zone":{"label":"UTM Zone"},"noaa_mrms_qpe:pass":{"label":"Pass Number","mapping":{"1":"1 (less latency / less gauges)","2":"2 (more latency / more gauges)"}},"noaa_mrms_qpe:period":{"label":"Accumulation Period","unit":"h"},"noaa_mrms_qpe:region":{"label":"Region","mapping":{"CONUS":"Continental US","HAWAII":"Hawaii","GUAM":"Guam","ALASKA":"Alaska","CARIB":"Caribbean Islands"}},"openeo:status":{"label":"Processing Status"},"api_version":{"label":"API Version","ext":"openeo"},"backend_version":{"label":"Back-End Version","ext":"openeo"},"production":{"label":"Production-Ready","ext":"openeo"},"endpoints":{"label":"Supported Endpoints","ext":"openeo","summary":false,"items":{"path":{"label":"Path Template","order":0},"methods":{"label":"HTTP Methods","order":1,"format":"CSV"}},"itemOrder":["path","methods"]},"billing":{"label":"Billing","ext":"openeo","custom":true,"summary":false},"order:status":{"label":"Status","mapping":{"orderable":"Orderable (data can be ordered)","ordered":"Ordered (preparing to deliver data)","pending":"Pending (waiting for activation)","shipping":"Shipping (data is getting processed)","succeeded":"Delivered (data is available)","failed":"Failed (unable to deliver)","canceled":"Canceled (delivery stopped)"}},"order:id":{"label":"Identifier"},"order:date":{"label":"Submitted","format":"Timestamp","summary":"r"},"order:expiration_date":{"alias":"expires","label":"Expires","format":"Timestamp","summary":"r"},"pc:count":{"label":"Points","explain":"Number of Points"},"pc:type":{"label":"Type"},"pc:encoding":{"label":"Format"},"pc:schemas":{"label":"Schemas","summary":false,"items":{"name":{"label":"Name","sortable":true,"id":true},"size":{"label":"Size","unit":"bytes","sortable":true},"type":{"label":"Type","sortable":true}},"itemOrder":["name","size","type"]},"pc:density":{"label":"Density"},"pc:statistics":{"label":"Statistics","summary":"s","items":{"name":{"label":"Name","id":true},"position":{"label":"Position"},"average":{"label":"Average"},"count":{"label":"Count"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stddev":{"label":"Std. Dev.","explain":"Standard Deviation"},"variance":{"label":"Variance"}},"itemOrder":["name","average","count","maximum","minimum","position","stddev","variance"]},"processing:expression":{"label":"Processing Instructions","summary":false},"processing:lineage":{"label":"Lineage","format":"CommonMark","summary":false},"processing:level":{"label":"Level"},"processing:facility":{"label":"Facility"},"processing:software":{"label":"Software","format":"Software","summary":false},"processing:version":{"label":"Processor Version"},"processing:datetime":{"label":"Processing Time","format":"Timestamp","summary":"r"},"product:type":{"label":"Product Type"},"product:timeliness":{"label":"Timeliness","format":"Duration"},"product:timeliness_category":{"label":"Timeliness Category"},"product:acquisition_type":{"label":"Acquisition Type","mapping":{"nominal":"Nominal","calibration":"Calibration","other":"Other"}},"proj:epsg":{"label":"EPSG Code","format":"EPSG","summary":"v"},"proj:code":{"label":"Code","format":"CrsCode","summary":"v"},"proj:wkt2":{"label":"WKT2","explain":"Well-Known Text, version 2","format":"WKT2","summary":false},"proj:projjson":{"label":"PROJJSON","explain":"JSON encoding of WKT2","format":"PROJJSON","summary":false},"proj:geometry":{"label":"Footprint","custom":true,"summary":false},"proj:bbox":{"label":"Bounding Box","custom":true,"summary":false},"proj:centroid":{"label":"Centroid","custom":true,"summary":false},"proj:shape":{"label":"Image Dimensions","format":"Shape","summary":false},"proj:transform":{"label":"Transformation Matrix","format":"Transform","summary":false},"sar:instrument_mode":{"label":"Instrument Mode"},"sar:frequency_band":{"label":"Frequency Band"},"sar:center_frequency":{"label":"Center Frequency","unit":"GHz"},"sar:polarizations":{"label":"Polarizations","format":"CSV"},"sar:product_type":{"label":"Product Type"},"sar:resolution_range":{"label":"Range Resolution","unit":"m"},"sar:resolution_azimuth":{"label":"Azimuth Resolution","unit":"m"},"sar:pixel_spacing_range":{"label":"Range Pixel Spacing","unit":"m"},"sar:pixel_spacing_azimuth":{"label":"Azimuth Pixel Spacing","unit":"m"},"sar:looks_range":{"label":"Range Looks"},"sar:looks_azimuth":{"label":"Azimuth Looks"},"sar:looks_equivalent_number":{"label":"ENL","explain":"Equivalent Number of Looks"},"sar:observation_direction":{"label":"Observation Direction"},"sat:platform_international_designator":{"label":"Int. Designator","explain":"International designator for the platform, also known as COSPAR ID and NSSDCA ID."},"sat:orbit_state":{"label":"Orbit State"},"sat:absolute_orbit":{"label":"Abs. Orbit Number","explain":"Absolute Orbit Number"},"sat:relative_orbit":{"label":"Rel. Orbit Number","explain":"Relative Orbit Number"},"sat:anx_datetime":{"label":"ANX Time","format":"Timestamp","explain":"Ascending Node Crossing time","summary":"r"},"sci:doi":{"label":"DOI","format":"DOI"},"sci:citation":{"label":"Citation"},"sci:publications":{"label":"Publications","summary":false,"items":{"citation":{"label":"Publication","sortable":true,"order":0},"doi":{"label":"DOI","format":"DOI","sortable":true,"order":1}},"itemOrder":["citation","doi"]},"ssys:targets":{"label":"Target Body"},"storage:platform":{"label":"Provider","mapping":{"ALIBABA":"Alibaba Cloud","AWS":"Amazon AWS","AZURE":"Microsoft Azure","GCP":"Google Cloud Platform","IBM":"IBM Cloud","ORACLE":"Oracle Cloud"}},"storage:region":{"label":"Region"},"storage:requester_pays":{"label":"Requester Pays"},"storage:tier":{"label":"Tier Type"},"table:columns":{"label":"Columns","items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"type":{"label":"Data Type","sortable":true,"order":1},"description":{"label":"Description","format":"CommonMark","order":2}},"itemOrder":["name","type","description"]},"table:primary_geometry":{"label":"Primary Geometry Column"},"table:row_count":{"label":"Rows"},"table:tables":{"label":"Tables","summary":false,"listWithKeys":true,"items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"description":{"label":"Description","format":"CommonMark","order":1}},"itemOrder":["name","description"]},"tiles:tile_matrix_sets":{"label":"Tile Matrix Sets","custom":true,"summary":false},"tiles:tile_matrix_set_links":{"label":"Tile Matrix Set Links","custom":true,"summary":false},"view:off_nadir":{"label":"Off-Nadir Angle","unit":"°"},"view:incidence_angle":{"label":"Incidence Angle","unit":"°"},"view:azimuth":{"label":"Viewing Azimuth","unit":"°"},"view:sun_azimuth":{"label":"Sun Azimuth","unit":"°"},"view:sun_elevation":{"label":"Sun Elevation","unit":"°"},"pl:black_fill":{"label":"Unfilled Image Parts","unit":"%"},"pl:clear_percent":{"label":"Clear Sky","unit":"%"},"pl:grid_cell":{"label":"Grid Cell"},"pl:ground_control":{"label":"Positional Accuracy"},"pl:ground_control_ratio":{"label":"Successful Rectification Ratio"},"pl:item_type":{"label":"Type"},"pl:pixel_resolution":{"label":"Spatial Resolution","unit":"m"},"pl:publishing_stage":{"label":"Publishing Stage","mapping":{"preview":"Preview","standard":"Standard","finalized":"Finalized"}},"pl:quality_category":{"label":"Quality Category","mapping":{"standard":"Standard","test":"Test"}},"pl:strip_id":{"label":"Image Strip ID"},"gee:type":{"label":"Type","mapping":{"image":"Single image","image_collection":"Image collection","table":"Table"}},"gee:cadence":{"label":"Cadence"},"gee:schema":{"label":"Variables","items":{"name":{"label":"Name"},"description":{"label":"Description"},"type":{"label":"Data Type"}},"summary":false,"itemOrder":["type","description","name"]},"gee:revisit_interval":{"label":"Revisit Interval"},"gee:terms_of_use":{"label":"Terms of Use","format":"CommonMark","summary":false},"gee:visualizations":{"label":"Visualizations","custom":true,"summary":false},"landsat:scene_id":{"label":"Scene ID"},"landsat:collection_category":{"label":"Collection Category"},"landsat:collection_number":{"label":"Collection Number"},"landsat:wrs_type":{"label":"WRS Type","explain":"Worldwide Reference System Type"},"landsat:wrs_path":{"label":"WRS Path","explain":"Worldwide Reference System Path"},"landsat:wrs_row":{"label":"WRS Row","explain":"Worldwide Reference System Row"},"landsat:cloud_cover_land":{"label":"Land Cloud Cover","unit":"%"},"msft:container":{"label":"Container"},"msft:storage_account":{"label":"Storage Account"},"msft:short_description":{"label":"Summary","summary":false},"sentinel:utm_zone":{"label":"UTM Zone"},"sentinel:latitude_band":{"label":"Latitude Band"},"sentinel:grid_square":{"label":"Grid Square"},"sentinel:sequence":{"label":"Sequence"},"sentinel:product_id":{"label":"Product ID","summary":"s"},"sentinel:data_coverage":{"label":"Data Coverage","unit":"%"},"sentinel:valid_cloud_cover":{"label":"Valid Cloud Cover"},"cbers:data_type":{"label":"Processing Level","explain":"Geolocation precision level","mapping":{"L2":"Geolocation using only satellite telemetry","L3":"Control points used to geolocate image, no terrain correction","L4":"Control points used to geolocate image, orthorectified"},"summary":"v"},"cbers:path":{"label":"Reference Grid Path"},"cbers:row":{"label":"Reference Grid Row"},"card4l:specification":{"label":"Specification","mapping":{"SR":"Surface Reflectance (Optical)","ST":"Surface Temperature (Optical)","NRB":"Normalized Radar Backscatter (SAR)","POL":"Polarimetric Radar (SAR)"}},"card4l:specification_version":{"label":"Specification Version"},"card4l:orbit_mean_altitude":{"label":"Platform Altitude","unit":"m"},"card4l:incidence_angle_near_range":{"label":"Incidence Angle (near)","unit":"°"},"card4l:incidence_angle_far_range":{"label":"Incidence Angle (far)","unit":"°"},"card4l:noise_equivalent_intensity":{"label":"Noise Equivalent Intensity","unit":"dB"},"card4l:mean_faraday_rotation_angle":{"label":"Mean Faraday Rotation","unit":"°"},"card4l:speckle_filtering":{"label":"Speckle Filtering","custom":true,"summary":false,"null":"not applied"},"card4l:relative_rtc_accuracy":{"label":"Rel. RTC Accuracy","explain":"Relative accuracy of the Radiometric Terrain Correction","unit":"dB"},"card4l:absolute_rtc_accuracy":{"label":"Abs. RTC Accuracy","explain":"Absolute accuracy of the Radiometric Terrain Correction","unit":"dB"},"card4l:northern_geometric_accuracy":{"label":"Northern Geometric Accuracy","unit":"m"},"card4l:eastern_geometric_accuracy":{"label":"Eastern Geometric Accuracy","unit":"m"},"card4l:ellipsoidal_height":{"label":"Ellipsoidal Height","unit":"m"},"geoadmin:variant":{"label":"Product Variant","mapping":{"krel":"RGB color with relief","komb":"RGB color without relief","kgrel":"Grayscale with relief","kgrs":"Grayscale without relief"}},"href:servers":{"label":"Servers","ext":"web-map-links"},"pmtiles:layers":{"label":"Layers","ext":"web-map-links"},"wms:layers":{"label":"Layers","ext":"web-map-links"},"wms:styles":{"label":"Styles","ext":"web-map-links"},"wms:dimensions":{"label":"Dimensions","ext":"web-map-links"},"wms:transparent":{"label":"Transparency","ext":"web-map-links"},"wmts:layer":{"label":"Layers","ext":"web-map-links"},"wmts:dimensions":{"label":"Dimensions","ext":"web-map-links"}}} From f768b6eb26eecab250f5a61ecf307dfe9b6656d6 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 07:42:00 -0700 Subject: [PATCH 14/60] refactor: rendering --- src/pystac/container.py | 20 ++++++---- src/pystac/href_generator.py | 11 ++---- src/pystac/stac_object.py | 72 ++++++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index fe19d13f8..2f543de66 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -4,13 +4,12 @@ from collections.abc import Iterator from typing import TYPE_CHECKING -from typing_extensions import deprecated - from .link import Link from .rel_type import RelType from .stac_object import STACObject if TYPE_CHECKING: + from .href_generator import HrefGenerator from .item import Item @@ -47,12 +46,19 @@ def get_child_links(self) -> list[Link]: def get_item_links(self) -> list[Link]: return [link for link in self.links if link.is_item()] - @deprecated("Use render instead") def normalize_hrefs(self, root_href: str) -> None: - from .href_generator import DEFAULT_HREF_GENERATOR - - self.set_self_href(DEFAULT_HREF_GENERATOR.get_root(root_href, self)) - for _ in self.render(): + from .href_generator import BestPracticesHrefGenerator + + href_generator = BestPracticesHrefGenerator() + self.set_self_href(href_generator.get_root(root_href, self)) + self.render_all(href_generator=href_generator) + + def render_all( + self, + use_absolute_links: bool = False, + href_generator: HrefGenerator | None = None, + ) -> None: + for _ in self.render(use_absolute_links, href_generator): pass def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: diff --git a/src/pystac/href_generator.py b/src/pystac/href_generator.py index 9f387a536..a39a029b2 100644 --- a/src/pystac/href_generator.py +++ b/src/pystac/href_generator.py @@ -1,11 +1,9 @@ -from typing import TYPE_CHECKING, Protocol +from typing import Protocol +from .container import Container +from .item import Item from .utils import make_absolute_href -if TYPE_CHECKING: - from .container import Container - from .item import Item - class HrefGenerator(Protocol): def get_root(self, prefix: str, container: Container) -> str: ... @@ -44,6 +42,3 @@ def get_item(self, parent_href: str, item: Item) -> str: return make_absolute_href( parent_href, "/".join((item.id, f"{item.id}.json")), start_is_dir=False ) - - -DEFAULT_HREF_GENERATOR = BestPracticesHrefGenerator() diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index b1e2b49e5..f0cac65ca 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -27,17 +27,6 @@ from .href_generator import HrefGenerator -def __getattr__(name: str) -> Any: - from typing import TypeVar - - if name == "S": - warnings.warn( - "pystac.stac_object.S is deprecated, use `type[STACObject]` instead" - ) - return TypeVar("S", bound="STACObject") - raise AttributeError(f"module {__name__} has no attribute {name}") - - class STACObject(ABC): type: ClassVar[STAC_OBJECT_TYPE] @@ -107,7 +96,7 @@ def to_dict( ) -> dict[str, Any]: if transform_hrefs is not None: warnings.warn( - "transform_hrefs is deprecated as an argument in `to_dict`", + "transform_hrefs is deprecated as an argument", DeprecationWarning, ) if transform_hrefs: @@ -140,7 +129,7 @@ def get_link(self, rel: str) -> Link | None: def get_links(self, rel: str) -> list[Link]: return list(link for link in self.links if link.rel == rel) - @deprecated("Use get_link instead") + @deprecated("Use .get_link()") def get_single_link(self, rel: str) -> Link | None: return self.get_link(rel) @@ -151,16 +140,13 @@ def get_derived_from(self) -> list[STACObject]: derived_from.append(link.get_target(self._href, self.reader)) return derived_from - @deprecated("Just append to the links array") + @deprecated("Append to .links") def add_link(self, link: Link) -> None: self.links.append(link) def add_derived_from(self, *values: STACObject | str) -> None: for value in values: - if isinstance(value, STACObject): - self.links.append(Link(target=value, rel=RelType.DERIVED_FROM)) - else: - self.links.append(Link(target=value, rel=RelType.DERIVED_FROM)) + self.links.append(Link(target=value, rel=RelType.DERIVED_FROM)) def remove_derived_from(self, id: str) -> None: links: list[Link] = [] @@ -229,6 +215,21 @@ def validate( validator.validate_extension(extension, data) def render( + self, + use_absolute_links: bool = False, + href_generator: HrefGenerator | None = None, + ) -> Iterator[STACObject]: + """Remove and re-create all hierarchical links on this STAC object and + all children and items, yielding each.""" + yield from self._render( + use_absolute_links=use_absolute_links, + href_generator=href_generator, + root=None, + parent=None, + collection=None, + ) + + def _render( self, root: Container | None = None, parent: Container | None = None, @@ -236,12 +237,15 @@ def render( use_absolute_links: bool = False, href_generator: HrefGenerator | None = None, ) -> Iterator[STACObject]: + from .catalog import Catalog + from .collection import Collection + from .container import Container from .item import Item if href_generator is None: - from .href_generator import DEFAULT_HREF_GENERATOR + from .href_generator import BestPracticesHrefGenerator - href_generator = DEFAULT_HREF_GENERATOR + href_generator = BestPracticesHrefGenerator() self_href = self.get_self_href() if self_href is None: @@ -306,15 +310,23 @@ def render( ) ) - if isinstance(self, Container): - yield from stac_object.render( + if isinstance(self, Collection): + yield from stac_object._render( + root=root, + parent=self, + collection=self, + use_absolute_links=use_absolute_links, + href_generator=href_generator, + ) + elif isinstance(self, Catalog): + yield from stac_object._render( root=root, parent=self, use_absolute_links=use_absolute_links, href_generator=href_generator, ) else: - yield from stac_object.render( + yield from stac_object._render( root=root, use_absolute_links=use_absolute_links, href_generator=href_generator, @@ -360,8 +372,20 @@ def _maybe_get_link_target(self, rel: str) -> STACObject | None: return None -@deprecated("STACObjectType is deprecated") +@deprecated("STACObjectType is deprecated, use pystac.constants.STAC_OBJECT_TYPE") class STACObjectType(StrEnum): CATALOG = "Catalog" COLLECTION = "Collection" ITEM = "Feature" + + +def __getattr__(name: str) -> Any: + from typing import TypeVar + + if name == "S": + warnings.warn( + "pystac.stac_object.S is deprecated, use `type[STACObject]`", + DeprecationWarning, + ) + return TypeVar("S", bound="STACObject") + raise AttributeError(f"module {__name__} has no attribute {name}") From 9447c8cc735d42a2201c3f2c7f01b8125894b54b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 08:44:35 -0700 Subject: [PATCH 15/60] feat: add normalize_and_save --- src/pystac/container.py | 41 +++++++++++++++++++++++++++++++++--- src/pystac/href_generator.py | 8 +++---- src/pystac/stac_object.py | 22 +++++++++++-------- src/pystac/writer.py | 1 + 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index 2f543de66..524cbba68 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -4,11 +4,14 @@ from collections.abc import Iterator from typing import TYPE_CHECKING +from typing_extensions import deprecated + from .link import Link from .rel_type import RelType from .stac_object import STACObject if TYPE_CHECKING: + from .catalog import CatalogType # pyright: ignore[reportDeprecated] from .href_generator import HrefGenerator from .item import Item @@ -46,21 +49,53 @@ def get_child_links(self) -> list[Link]: def get_item_links(self) -> list[Link]: return [link for link in self.links if link.is_item()] - def normalize_hrefs(self, root_href: str) -> None: + @deprecated("Call .normalize_hrefs() and then .save_all()") + def normalize_and_save( + self, + root_href: str, + catalog_type: CatalogType | None = None, # pyright: ignore[reportDeprecated] + ) -> None: + from .catalog import CatalogType # pyright: ignore[reportDeprecated] + + self.normalize_hrefs( + root_href, + use_absolute_links=catalog_type == CatalogType.ABSOLUTE_PUBLISHED, # pyright: ignore[reportDeprecated] + ) + self.save_all() + + def normalize_hrefs(self, root_href: str, use_absolute_links: bool = False) -> None: from .href_generator import BestPracticesHrefGenerator href_generator = BestPracticesHrefGenerator() + previous_self_href = self.get_self_href() self.set_self_href(href_generator.get_root(root_href, self)) - self.render_all(href_generator=href_generator) + self.render_all( + use_absolute_links=use_absolute_links, + href_generator=href_generator, + previous_self_href=previous_self_href, + ) def render_all( self, use_absolute_links: bool = False, href_generator: HrefGenerator | None = None, + previous_self_href: str | None = None, ) -> None: - for _ in self.render(use_absolute_links, href_generator): + for _ in self.render(use_absolute_links, href_generator, previous_self_href): pass + def save_all(self) -> None: + for _ in self.save_iter(): + pass + + def save_iter(self) -> Iterator[STACObject]: + for root, _, items in self.walk(): + root.save_object() + yield root + for item in items: + item.save_object() + yield item + def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: from .item import Item diff --git a/src/pystac/href_generator.py b/src/pystac/href_generator.py index a39a029b2..c673bfdef 100644 --- a/src/pystac/href_generator.py +++ b/src/pystac/href_generator.py @@ -17,9 +17,9 @@ def get_root(self, prefix: str, container: Container) -> str: from .collection import Collection if isinstance(container, Catalog): - return make_absolute_href(prefix, "./catalog.json", start_is_dir=True) + return make_absolute_href("./catalog.json", prefix, start_is_dir=True) elif isinstance(container, Collection): - return make_absolute_href(prefix, "./collection.json", start_is_dir=True) + return make_absolute_href("./collection.json", prefix, start_is_dir=True) else: raise ValueError(f"Unsupported root type: {type(container)}") @@ -35,10 +35,10 @@ def get_child(self, parent_href: str, container: Container) -> str: raise ValueError(f"Unsupported child type: {type(container)}") return make_absolute_href( - parent_href, "/".join((container.id, file_name)), start_is_dir=False + "/".join((container.id, file_name)), parent_href, start_is_dir=False ) def get_item(self, parent_href: str, item: Item) -> str: return make_absolute_href( - parent_href, "/".join((item.id, f"{item.id}.json")), start_is_dir=False + "/".join((item.id, f"{item.id}.json")), parent_href, start_is_dir=False ) diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index f0cac65ca..ce15519c1 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -218,12 +218,14 @@ def render( self, use_absolute_links: bool = False, href_generator: HrefGenerator | None = None, + previous_self_href: str | None = None, ) -> Iterator[STACObject]: """Remove and re-create all hierarchical links on this STAC object and all children and items, yielding each.""" yield from self._render( use_absolute_links=use_absolute_links, href_generator=href_generator, + previous_self_href=previous_self_href, root=None, parent=None, collection=None, @@ -236,6 +238,7 @@ def _render( collection: Collection | None = None, use_absolute_links: bool = False, href_generator: HrefGenerator | None = None, + previous_self_href: str | None = None, ) -> Iterator[STACObject]: from .catalog import Catalog from .collection import Collection @@ -249,7 +252,7 @@ def _render( self_href = self.get_self_href() if self_href is None: - raise ValueError("Cannot render a self href") + raise ValueError("Cannot render without a self href") if root is None and isinstance(self, Container): root = self links: list[Link] = [] @@ -283,12 +286,11 @@ def _render( ) for link in self.links: if link.is_child() or link.is_item(): - stac_object = link.get_target(self.get_self_href(), self.reader) - href = stac_object.get_self_href() + stac_object = link.get_target(previous_self_href, self.reader) + stac_object_previous_self_href = stac_object.get_self_href() if isinstance(stac_object, Container): - if not href: - href = href_generator.get_child(self_href, stac_object) - stac_object.set_self_href(href) + href = href_generator.get_child(self_href, stac_object) + stac_object.set_self_href(href) links.append( self._make_link( rel=RelType.CHILD, @@ -298,9 +300,8 @@ def _render( ) ) elif isinstance(stac_object, Item): - if not href: - href = href_generator.get_item(self_href, stac_object) - stac_object.set_self_href(href) + href = href_generator.get_item(self_href, stac_object) + stac_object.set_self_href(href) links.append( self._make_link( rel=RelType.ITEM, @@ -317,6 +318,7 @@ def _render( collection=self, use_absolute_links=use_absolute_links, href_generator=href_generator, + previous_self_href=stac_object_previous_self_href, ) elif isinstance(self, Catalog): yield from stac_object._render( @@ -324,12 +326,14 @@ def _render( parent=self, use_absolute_links=use_absolute_links, href_generator=href_generator, + previous_self_href=stac_object_previous_self_href, ) else: yield from stac_object._render( root=root, use_absolute_links=use_absolute_links, href_generator=href_generator, + previous_self_href=stac_object_previous_self_href, ) elif not link.is_hierarchical(): non_hierarchical_links.append(link) diff --git a/src/pystac/writer.py b/src/pystac/writer.py index 88d630367..b094e5d36 100644 --- a/src/pystac/writer.py +++ b/src/pystac/writer.py @@ -12,6 +12,7 @@ def delete(self, href: str | Path) -> None: ... class StandardLibraryWriter: def put_json(self, data: dict[str, Any], href: str | Path) -> None: if isinstance(href, Path) or not urllib.parse.urlparse(href).scheme: + Path(href).parent.mkdir(parents=True, exist_ok=True) with open(href, "w") as f: json.dump( data, From 275ab8249cb3b59c6329245bbf69756bde642123 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 09:03:03 -0700 Subject: [PATCH 16/60] feat: couple more test fixes --- src/pystac/catalog.py | 11 ++++++++++- src/pystac/collection.py | 11 ++++++++++- src/pystac/container.py | 9 +++++++-- src/pystac/item.py | 7 +++---- src/pystac/stac_object.py | 10 +++++++++- tests/v1/test_catalog.py | 1 + 6 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py index 2fe3e308d..f859d9a55 100644 --- a/src/pystac/catalog.py +++ b/src/pystac/catalog.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy from enum import StrEnum from typing import Any, ClassVar, override @@ -37,7 +38,15 @@ def __init__( @override @classmethod - def from_dict(cls, data: dict[str, Any]) -> Catalog: + def from_dict( + cls, + data: dict[str, Any], + preserve_dict: bool = True, + migrate: bool | None = None, + root: Container | None = None, + ) -> Catalog: + if preserve_dict: + data = copy.deepcopy(data) return cls(**data) @override diff --git a/src/pystac/collection.py b/src/pystac/collection.py index cd224dd71..c06b68882 100644 --- a/src/pystac/collection.py +++ b/src/pystac/collection.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import datetime as dt from typing import Any, ClassVar, TypedDict, cast, override @@ -72,7 +73,15 @@ def __init__( @override @classmethod - def from_dict(cls, data: dict[str, Any]) -> Collection: + def from_dict( + cls, + data: dict[str, Any], + preserve_dict: bool = True, + migrate: bool | None = None, + root: Container | None = None, + ) -> Collection: + if preserve_dict: + data = copy.deepcopy(data) return Collection(**data) @override diff --git a/src/pystac/container.py b/src/pystac/container.py index 524cbba68..11bc86d25 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -34,10 +34,15 @@ def add_item(self, item: Item) -> None: self.links.append(Link(target=item, rel=RelType.ITEM)) def get_child(self, id: str) -> Container | None: + for child in self.get_children(): + if child.id == id: + return child + + def get_children(self) -> Iterator[Container]: for link in self.get_child_links(): stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Container) and stac_object.id == id: - return stac_object + if isinstance(stac_object, Container): + yield stac_object def add_child(self, child: Container) -> None: link = Link(target=child, rel=RelType.CHILD) diff --git a/src/pystac/item.py b/src/pystac/item.py index 310370db5..cb643f7d3 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -87,18 +87,17 @@ def __init__( def from_dict( cls, data: dict[str, Any], + preserve_dict: bool = True, migrate: bool | None = None, - preserve_dict: bool | None = None, root: Container | None = None, ) -> Item: + if preserve_dict: + data = copy.deepcopy(data) item = cls(**data) # TODO deprecate migrate if migrate: raise NotImplementedError - # TODO deprecate preserve_dict - if preserve_dict: - raise NotImplementedError if root: warnings.warn( "The root parameter is deprecated, call `set_root` directly after " diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index ce15519c1..355e2b826 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -85,9 +85,17 @@ def from_file[T: STACObject]( @classmethod @abstractmethod - def from_dict[T: STACObject](cls: type[T], data: dict[str, Any]) -> T: + def from_dict[T: STACObject]( + cls: type[T], + data: dict[str, Any], + preserve_dict: bool = True, + migrate: bool | None = None, + root: Container | None = None, + ) -> T: from .deserialize import from_dict + if preserve_dict: + data = copy.deepcopy(data) return from_dict(data) # pyright: ignore[reportReturnType] @abstractmethod diff --git a/tests/v1/test_catalog.py b/tests/v1/test_catalog.py index ddfcced12..d90cb6f5c 100644 --- a/tests/v1/test_catalog.py +++ b/tests/v1/test_catalog.py @@ -77,6 +77,7 @@ def test_determine_type_for_self_contained(self) -> None: catalog_type = CatalogType.determine_type(cat_json) assert catalog_type == CatalogType.SELF_CONTAINED + @pytest.mark.xfail(reason="CatalogType is deprecated in v2, so it's not worth fixing this test") def test_determine_type_for_unknown(self) -> None: catalog = Catalog(id="test", description="test desc") subcat = Catalog(id="subcat", description="subcat desc") From 99b8795cba7ebfd1c8f1fa8bcff3f96e46d4b507 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 8 Jan 2026 09:28:26 -0700 Subject: [PATCH 17/60] fix: remove item --- src/pystac/catalog.py | 16 +++++++++++++++- src/pystac/container.py | 23 +++++++++++++++++++++++ src/pystac/item.py | 3 +++ tests/v1/test_catalog.py | 2 ++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py index f859d9a55..d4049a29e 100644 --- a/src/pystac/catalog.py +++ b/src/pystac/catalog.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +import warnings from enum import StrEnum from typing import Any, ClassVar, override @@ -47,7 +48,20 @@ def from_dict( ) -> Catalog: if preserve_dict: data = copy.deepcopy(data) - return cls(**data) + catalog = cls(**data) + + # TODO deprecate migrate + if migrate: + raise NotImplementedError + if root: + warnings.warn( + "The root parameter is deprecated, call `set_root` directly after " + "creating the item", + DeprecationWarning, + ) + catalog.set_root(root) + + return catalog @override def to_dict( diff --git a/src/pystac/container.py b/src/pystac/container.py index 11bc86d25..9a6d83c5d 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -33,6 +33,29 @@ def get_items(self, recursive: bool = False) -> Iterator[Item]: def add_item(self, item: Item) -> None: self.links.append(Link(target=item, rel=RelType.ITEM)) + def remove_item(self, id: str) -> Item | None: + from .item import Item + + removed_item = None + index = None + for i, link in enumerate(self.links): + if link.is_item(): + stac_object = link.get_target( + start_href=self.get_self_href(), reader=self.reader + ) + if isinstance(stac_object, Item) and stac_object.id == id: + removed_item = stac_object + index = i + break + + if index is not None: + _ = self.links.pop(index) + + return removed_item + + def clear_items(self) -> None: + self.links: list[Link] = [link for link in self.links if not link.is_item()] + def get_child(self, id: str) -> Container | None: for child in self.get_children(): if child.id == id: diff --git a/src/pystac/item.py b/src/pystac/item.py index cb643f7d3..3c9711db8 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -229,6 +229,9 @@ def __init__(self, datetime: dt.datetime | str | None = None, **kwargs: Any): self.extra_fields: dict[str, Any] = kwargs + def __getitem__(self, key: str) -> Any: + return self.extra_fields[key] + @classmethod def try_from( cls, diff --git a/tests/v1/test_catalog.py b/tests/v1/test_catalog.py index d90cb6f5c..f6eed62b5 100644 --- a/tests/v1/test_catalog.py +++ b/tests/v1/test_catalog.py @@ -107,6 +107,7 @@ def test_create_and_read(self) -> None: assert len(list(items)) == 8 + @pytest.mark.xfail(reason="We no longer migrate by default in v2, so this test should fail") def test_from_dict_preserves_dict(self) -> None: catalog_dict = TestCases.case_1().to_dict() param_dict = deepcopy(catalog_dict) @@ -120,6 +121,7 @@ def test_from_dict_preserves_dict(self) -> None: _ = Catalog.from_dict(param_dict, preserve_dict=False, migrate=False) assert param_dict != catalog_dict + @pytest.mark.xfail(reason="We deserialize more permissively in v2") def test_from_file_bad_catalog(self) -> None: with pytest.raises(pystac.errors.STACTypeError) as ctx: _ = Catalog.from_file(TestCases.get_path(TestCases.bad_catalog_case)) From 8ab2f97b321eb43c6dc64f31932cbc4dc4ed4eab Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 07:08:26 -0700 Subject: [PATCH 18/60] feat: dest_href --- src/pystac/catalog.py | 5 + src/pystac/container.py | 206 ++++++++++++++++++++++++++++++----- src/pystac/href_generator.py | 4 +- src/pystac/link.py | 21 ++-- src/pystac/stac_object.py | 46 +++++++- src/pystac/writer.py | 16 +++ tests/v1/test_catalog.py | 5 + 7 files changed, 261 insertions(+), 42 deletions(-) diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py index d4049a29e..75744a58f 100644 --- a/src/pystac/catalog.py +++ b/src/pystac/catalog.py @@ -37,6 +37,11 @@ def __init__( ) self.description: str = description + @property + @deprecated("catalog_type is deprecated") + def catalog_type(self) -> CatalogType | None: # pyright: ignore[reportDeprecated] + return CatalogType.determine_type(self.to_dict()) # pyright: ignore[reportDeprecated] + @override @classmethod def from_dict( diff --git a/src/pystac/container.py b/src/pystac/container.py index 9a6d83c5d..0581e21ed 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -6,6 +6,10 @@ from typing_extensions import deprecated +from pystac.errors import STACError +from pystac.stac_io import StacIO +from pystac.utils import make_absolute_href, make_relative_href + from .link import Link from .rel_type import RelType from .stac_object import STACObject @@ -14,24 +18,47 @@ from .catalog import CatalogType # pyright: ignore[reportDeprecated] from .href_generator import HrefGenerator from .item import Item + from .writer import Writer class Container(STACObject, ABC): - def get_items(self, recursive: bool = False) -> Iterator[Item]: + def get_items(self, *ids: str, recursive: bool = False) -> Iterator[Item]: for link in self.links: if link.is_item(): from .item import Item stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Item): + if isinstance(stac_object, Item) and (not ids or stac_object.id in ids): yield stac_object elif recursive and link.is_child(): stac_object = link.get_target(start_href=self._href, reader=self.reader) if isinstance(stac_object, Container): - yield from stac_object.get_items(recursive=True) + yield from stac_object.get_items(*ids, recursive=True) + + def get_item_links(self) -> list[Link]: + return [link for link in self.links if link.is_item()] + + def add_item(self, item: Item, set_parent: bool = True) -> Link: + from .item import Item + + if not isinstance(item, Item): # pyright: ignore[reportUnnecessaryIsInstance] + raise STACError("Cannot add a non-item as an item") # pyright: ignore[reportUnreachable] - def add_item(self, item: Item) -> None: - self.links.append(Link(target=item, rel=RelType.ITEM)) + item_link = Link(target=item, rel=RelType.ITEM) + self.links.append(item_link) + + if set_parent: + parent_link = Link(target=self, rel=RelType.PARENT) + item.remove_links(RelType.PARENT) + item.links.append(parent_link) + + return item_link + + def add_items(self, items: list[Item]) -> list[Link]: + links: list[Link] = [] + for item in items: + links.append(self.add_item(item)) + return links def remove_item(self, id: str) -> Item | None: from .item import Item @@ -56,26 +83,79 @@ def remove_item(self, id: str) -> Item | None: def clear_items(self) -> None: self.links: list[Link] = [link for link in self.links if not link.is_item()] - def get_child(self, id: str) -> Container | None: - for child in self.get_children(): + def get_child(self, id: str, recursive: bool = False) -> Container | None: + for child in self.get_children(recursive=recursive): if child.id == id: return child - def get_children(self) -> Iterator[Container]: - for link in self.get_child_links(): - stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Container): - yield stac_object - - def add_child(self, child: Container) -> None: - link = Link(target=child, rel=RelType.CHILD) - self.links.append(link) + def get_children(self, recursive: bool = False) -> Iterator[Container]: + for link in self.links: + if link.is_child(): + stac_object = link.get_target(start_href=self._href, reader=self.reader) + if isinstance(stac_object, Container): + yield stac_object + if recursive: + yield from stac_object.get_children(recursive=recursive) def get_child_links(self) -> list[Link]: return [link for link in self.links if link.is_child()] - def get_item_links(self) -> list[Link]: - return [link for link in self.links if link.is_item()] + def add_child(self, child: Container, set_parent: bool = True) -> Link: + if not isinstance(child, Container): # pyright: ignore[reportUnnecessaryIsInstance] + raise STACError("Only catalogs or collections can be children") # pyright: ignore[reportUnreachable] + + child_link = Link(target=child, rel=RelType.CHILD) + self.links.append(child_link) + + if set_parent: + parent_link = Link(target=self, rel=RelType.PARENT) + child.remove_links(RelType.PARENT) + child.links.append(parent_link) + + if root := self.get_root(): + child.set_root(root) + else: + child.set_root(self) + + return child_link + + def add_children(self, children: list[Container]) -> list[Link]: + links: list[Link] = [] + for child in children: + links.append(self.add_child(child)) + return links + + def remove_child(self, id: str) -> Container | None: + removed_child = None + index = None + for i, link in enumerate(self.links): + if link.is_child(): + stac_object = link.get_target( + start_href=self.get_self_href(), reader=self.reader + ) + if isinstance(stac_object, Container) and stac_object.id == id: + removed_child = stac_object + index = i + break + + if index is not None: + _ = self.links.pop(index) + assert removed_child + removed_child.remove_links(RelType.PARENT) + _ = removed_child.remove_root() + + return removed_child + + def clear_children(self) -> None: + links: list[Link] = [] + for link in self.links: + if link.is_child(): + child = link.get_target(self.get_self_href(), self.reader) + child.remove_links(RelType.PARENT) + _ = child.remove_root() + else: + links.append(link) + self.links = links @deprecated("Call .normalize_hrefs() and then .save_all()") def normalize_and_save( @@ -91,6 +171,30 @@ def normalize_and_save( ) self.save_all() + @deprecated("Use .save_all()") + def save( + self, + catalog_type: CatalogType | None = None, # pyright: ignore[reportDeprecated] + dest_href: str | None = None, + stac_io: StacIO | None = None, + ) -> None: + from .catalog import CatalogType # pyright: ignore[reportDeprecated] + + if catalog_type: + # In PySTAC v1, Link.to_dict() implicitly converted hrefs. In PySTAC + # v2, we have to explicitly convert the hrefs before saving. + self.render_all( + use_absolute_links=catalog_type == CatalogType.ABSOLUTE_PUBLISHED # pyright: ignore[reportDeprecated] + ) + + if stac_io: + from .writer import StacIOWriter # pyright: ignore[reportDeprecated] + + writer = StacIOWriter(stac_io) # pyright: ignore[reportDeprecated] + else: + writer = None + self.save_all(dest_href=dest_href, writer=writer) + def normalize_hrefs(self, root_href: str, use_absolute_links: bool = False) -> None: from .href_generator import BestPracticesHrefGenerator @@ -112,17 +216,65 @@ def render_all( for _ in self.render(use_absolute_links, href_generator, previous_self_href): pass - def save_all(self) -> None: - for _ in self.save_iter(): + def save_all( + self, dest_href: str | None = None, writer: Writer | None = None + ) -> None: + for _ in self.save_iter(dest_href=dest_href, writer=writer): pass - def save_iter(self) -> Iterator[STACObject]: - for root, _, items in self.walk(): - root.save_object() - yield root - for item in items: - item.save_object() - yield item + def save_iter( + self, dest_href: str | None = None, writer: Writer | None = None + ) -> Iterator[STACObject]: + self_href = self.get_self_href() + if dest_href is not None: + if self_href: + self_dest_href = make_absolute_href( + make_relative_href(self_href, self_href), + dest_href, + start_is_dir=True, + ) + else: + raise ValueError("Cannot use dest_href without a self href") + + self.save_object(dest_href=self_dest_href, writer=writer) + else: + self.save_object(dest_href=None, writer=writer) + + yield self + + for child in self.get_children(): + if dest_href is not None: + if (child_self_href := child.get_self_href()) and self_href: + child_dest_href = make_absolute_href( + make_relative_href(child_self_href, self_href), + dest_href, + start_is_dir=True, + ) + else: + raise ValueError( + "Cannot use dest_href unless both self and child have self " + "hrefs" + ) + yield from child.save_iter(dest_href=child_dest_href, writer=writer) + else: + yield from child.save_iter(dest_href=None, writer=writer) + + for item in self.get_items(): + if dest_href is not None: + if (item_self_href := item.get_self_href()) and self_href: + item_dest_href = make_absolute_href( + make_relative_href(item_self_href, self_href), + dest_href, + start_is_dir=True, + ) + else: + raise ValueError( + "Cannot use dest_href unless both self and item have self hrefs" + ) + item.save_object(dest_href=item_dest_href, writer=writer) + else: + item.save_object(dest_href=None, writer=writer) + yield item def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: from .item import Item diff --git a/src/pystac/href_generator.py b/src/pystac/href_generator.py index c673bfdef..f9d573751 100644 --- a/src/pystac/href_generator.py +++ b/src/pystac/href_generator.py @@ -17,9 +17,9 @@ def get_root(self, prefix: str, container: Container) -> str: from .collection import Collection if isinstance(container, Catalog): - return make_absolute_href("./catalog.json", prefix, start_is_dir=True) + return make_absolute_href("catalog.json", prefix, start_is_dir=True) elif isinstance(container, Collection): - return make_absolute_href("./collection.json", prefix, start_is_dir=True) + return make_absolute_href("collection.json", prefix, start_is_dir=True) else: raise ValueError(f"Unsupported root type: {type(container)}") diff --git a/src/pystac/link.py b/src/pystac/link.py index e5a70040c..8c8bec65f 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -4,6 +4,8 @@ import warnings from typing import TYPE_CHECKING, Any, override +from typing_extensions import deprecated + from pystac.errors import STACError from pystac.media_type import MediaType from pystac.rel_type import RelType @@ -48,14 +50,14 @@ def __init__( if isinstance(target, STACObject): self._href: str | None = href or target.get_self_href() - self.target: STACObject | None = target + self._target: STACObject | None = target elif href and target: raise ValueError("Both target and href were provided as strings") elif not href and not target: raise ValueError("Neither href nor target were provided") else: self._href = href or target - self.target = None + self._target = None @override def __getattribute__(self, name: str, /) -> Any: @@ -98,22 +100,27 @@ def is_json(self) -> bool: return self.media_type in (MediaType.JSON, MediaType.GEOJSON) def get_href(self) -> str | None: - return self._href or self.target and self.target.get_self_href() + return self._href or self._target and self._target.get_self_href() + + @property + @deprecated("target is deprecated, either use .get_href() or .get_target()") + def target(self) -> str | STACObject | None: + return self._target or self._href def get_target(self, start_href: str | None, reader: Reader) -> STACObject: from .stac_object import STACObject - if not self.target: + if not self._target: if not self._href: raise ValueError("No target and no href on the link") href = make_absolute_href(self._href, start_href, start_is_dir=False) try: - self.target = STACObject.from_file(href, reader=reader) + self._target = STACObject.from_file(href, reader=reader) except Exception as e: raise STACError(f"Error while resolving link: {e}") - return self.target + return self._target def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: if transform_href is not None: @@ -127,7 +134,7 @@ def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: data = copy.deepcopy(self.extra_fields) if self._href: data["href"] = self._href - elif self.target and (href := self.target.get_self_href()): + elif self._target and (href := self._target.get_self_href()): data["href"] = href else: raise ValueError("No href or target self href on the link") diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 355e2b826..46cee7dc3 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -25,6 +25,7 @@ from .collection import Collection from .container import Container from .href_generator import HrefGenerator + from .writer import Writer class STACObject(ABC): @@ -74,6 +75,16 @@ def from_file[T: STACObject]( ) -> T: href = make_absolute_href(str(path)) data = reader.get_json(href) + if "type" not in data: + if "properties" in data: + type_value = "Feature" + elif "extents" in data or "license" in data: + type_value = "Collection" + else: + type_value = "Catalog" + warnings.warn(f"Data does not have a 'type' field, guessed as {type_value}") + data["type"] = type_value + stac_object = cls.from_dict(data) if not isinstance(stac_object, cls): raise ValueError( @@ -134,8 +145,8 @@ def to_dict( def get_link(self, rel: str) -> Link | None: return next((link for link in self.links if link.rel == rel), None) - def get_links(self, rel: str) -> list[Link]: - return list(link for link in self.links if link.rel == rel) + def get_links(self, rel: str | None = None) -> list[Link]: + return list(link for link in self.links if rel is None or link.rel == rel) @deprecated("Use .get_link()") def get_single_link(self, rel: str) -> Link | None: @@ -189,8 +200,10 @@ def set_self_href(self, href: str | None) -> None: self.links.append(Link(rel="self", target=href)) def get_root(self) -> Container | None: + from .container import Container + if self._root is None: - root = self._maybe_get_link_target("root") + root = self._maybe_get_link_target(RelType.ROOT) if isinstance(root, Container): self._root = root else: @@ -199,6 +212,22 @@ def get_root(self) -> Container | None: ) return self._root + def remove_root(self) -> Container | None: + root = self._root + self._root = None + self.remove_links(RelType.ROOT) + return root + + def get_parent(self) -> Container | None: + from .container import Container + + stac_object = self._maybe_get_link_target(RelType.PARENT) + if isinstance(stac_object, Container): + return stac_object + else: + warnings.warn("The 'parent' link does not point to a collection or catalog") + return None + def get_root_link(self) -> Link | None: return next((link for link in self.links if link.is_root()), None) @@ -350,7 +379,10 @@ def _render( yield self def save_object( - self, include_self_link: bool = True, dest_href: str | None = None + self, + include_self_link: bool = True, + dest_href: str | None = None, + writer: Writer | None = None, ) -> None: href = dest_href or self._href if not href: @@ -358,10 +390,12 @@ def save_object( "dest_href was not provided, and object does not have a self href" ) href = make_absolute_href(href) - self.writer.put_json(self.to_dict(include_self_link), href) + if writer is None: + writer = self.writer + writer.put_json(self.to_dict(include_self_link), href) def clone[T: STACObject](self: T) -> T: - return self.from_dict(self.to_dict()) + return copy.deepcopy(self) @override def __repr__(self) -> str: diff --git a/src/pystac/writer.py b/src/pystac/writer.py index b094e5d36..639609f5f 100644 --- a/src/pystac/writer.py +++ b/src/pystac/writer.py @@ -3,6 +3,10 @@ from pathlib import Path from typing import Any, Protocol +from typing_extensions import deprecated + +from pystac.stac_io import StacIO + class Writer(Protocol): def put_json(self, data: dict[str, Any], href: str | Path) -> None: ... @@ -28,6 +32,18 @@ def delete(self, href: str | Path) -> None: raise ValueError("StandardLibraryWriter cannot delete urls") +@deprecated("StacIO is deprecated in PySTAC v2") +class StacIOWriter: + def __init__(self, stac_io: StacIO) -> None: + self.stac_io: StacIO = stac_io + + def put_json(self, data: dict[str, Any], href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] + raise NotImplementedError + + def delete(self, href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] + raise NotImplementedError + + def set_default_writer(writer: Writer) -> None: global DEFAULT_WRITER DEFAULT_WRITER = writer # pyright: ignore[reportConstantRedefinition] diff --git a/tests/v1/test_catalog.py b/tests/v1/test_catalog.py index f6eed62b5..cb9e165ee 100644 --- a/tests/v1/test_catalog.py +++ b/tests/v1/test_catalog.py @@ -383,18 +383,21 @@ def test_get_child_returns_none_if_not_found(self) -> None: child = cat.get_child("thisshouldnotbeachildid", recursive=True) assert child is None + @pytest.mark.xfail(reason="We've advertised that get_item will be removed in v2") def test_get_item_is_deprecated_but_still_works(self) -> None: cat = TestCases.case_1() with pytest.warns(DeprecationWarning): item = cat.get_item("area-2-1-imagery", recursive=True) assert item is not None + @pytest.mark.xfail(reason="We've advertised that get_item will be removed in v2") def test_get_item_returns_none_if_not_found(self) -> None: cat = TestCases.case_1() with pytest.warns(DeprecationWarning): item = cat.get_item("thisshouldnotbeanitemid", recursive=True) assert item is None + @pytest.mark.xfail(reason="We've advertised that get_all_items will be removed in v2") def test_get_all_items_is_deprecated_but_still_works(self) -> None: cat = TestCases.case_1() with pytest.warns(DeprecationWarning): @@ -407,6 +410,7 @@ def test_get_items_returns_empty_generator_if_not_found(self) -> None: items = cat.get_items("thisshouldnotbeanitemid", recursive=True) assert next(items, None) is None + @pytest.mark.xfail(reason="CatalogType is deprecated, and we don't care about fixing this test") def test_sets_catalog_type(self) -> None: cat = TestCases.case_1() @@ -469,6 +473,7 @@ def test_clone_generates_correct_links(self, catalog: Catalog) -> None: f"Clone of {obj_id} has {actual_counts[rel]} {rel} links, original has {expected_counts[rel]}" ) + @pytest.mark.xfail(reason="v2 doesn't care about catalog type") def test_save_uses_previous_catalog_type(self) -> None: catalog = TestCases.case_1() assert catalog.catalog_type == CatalogType.SELF_CONTAINED From 00de5791aced4077441f4db4a3d7159659f010e3 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 07:25:32 -0700 Subject: [PATCH 19/60] feat: more save work --- src/pystac/container.py | 57 +++++++++++++++++++++++++++++++++-------- src/pystac/link.py | 11 ++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index 0581e21ed..150b53877 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os.path from abc import ABC from collections.abc import Iterator from typing import TYPE_CHECKING @@ -187,13 +188,23 @@ def save( use_absolute_links=catalog_type == CatalogType.ABSOLUTE_PUBLISHED # pyright: ignore[reportDeprecated] ) + include_self_links = catalog_type not in ( + CatalogType.RELATIVE_PUBLISHED, # pyright: ignore[reportDeprecated] + CatalogType.SELF_CONTAINED, # pyright: ignore[reportDeprecated] + ) + else: + include_self_links = True + if stac_io: from .writer import StacIOWriter # pyright: ignore[reportDeprecated] writer = StacIOWriter(stac_io) # pyright: ignore[reportDeprecated] else: writer = None - self.save_all(dest_href=dest_href, writer=writer) + + self.save_all( + dest_href=dest_href, writer=writer, include_self_links=include_self_links + ) def normalize_hrefs(self, root_href: str, use_absolute_links: bool = False) -> None: from .href_generator import BestPracticesHrefGenerator @@ -217,13 +228,21 @@ def render_all( pass def save_all( - self, dest_href: str | None = None, writer: Writer | None = None + self, + dest_href: str | None = None, + writer: Writer | None = None, + include_self_links: bool = True, ) -> None: - for _ in self.save_iter(dest_href=dest_href, writer=writer): + for _ in self.save_iter( + dest_href=dest_href, writer=writer, include_self_links=include_self_links + ): pass def save_iter( - self, dest_href: str | None = None, writer: Writer | None = None + self, + dest_href: str | None = None, + writer: Writer | None = None, + include_self_links: bool = True, ) -> Iterator[STACObject]: self_href = self.get_self_href() if dest_href is not None: @@ -236,9 +255,15 @@ def save_iter( else: raise ValueError("Cannot use dest_href without a self href") - self.save_object(dest_href=self_dest_href, writer=writer) + self.save_object( + dest_href=self_dest_href, + writer=writer, + include_self_link=include_self_links, + ) else: - self.save_object(dest_href=None, writer=writer) + self.save_object( + dest_href=None, writer=writer, include_self_link=include_self_links + ) yield self @@ -255,9 +280,15 @@ def save_iter( "Cannot use dest_href unless both self and child have self " "hrefs" ) - yield from child.save_iter(dest_href=child_dest_href, writer=writer) + yield from child.save_iter( + dest_href=os.path.dirname(child_dest_href), + writer=writer, + include_self_links=include_self_links, + ) else: - yield from child.save_iter(dest_href=None, writer=writer) + yield from child.save_iter( + dest_href=None, writer=writer, include_self_links=include_self_links + ) for item in self.get_items(): if dest_href is not None: @@ -271,9 +302,15 @@ def save_iter( raise ValueError( "Cannot use dest_href unless both self and item have self hrefs" ) - item.save_object(dest_href=item_dest_href, writer=writer) + item.save_object( + dest_href=item_dest_href, + writer=writer, + include_self_link=include_self_links, + ) else: - item.save_object(dest_href=None, writer=writer) + item.save_object( + dest_href=None, writer=writer, include_self_link=include_self_links + ) yield item def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]: diff --git a/src/pystac/link.py b/src/pystac/link.py index 8c8bec65f..7ba05acab 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -59,6 +59,17 @@ def __init__( self._href = href or target self._target = None + def __fspath__(self) -> str: + if self._target and (href := self._target.get_self_href()): + return href + elif self._href: + return self._href + else: + raise ValueError( + "Link cannot be used for an __fspath__, target does not have a self " + "link and _href is not set" + ) + @override def __getattribute__(self, name: str, /) -> Any: if name == "owner": From 5d5ae5bf0843dfa2f005c5a7ec060f9853f8235b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 07:29:22 -0700 Subject: [PATCH 20/60] refactor: move stacio writer to stacio --- src/pystac/container.py | 6 +++--- src/pystac/stac_io.py | 12 ++++++++++++ src/pystac/writer.py | 16 ---------------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index 150b53877..b93f32d09 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -8,7 +8,6 @@ from typing_extensions import deprecated from pystac.errors import STACError -from pystac.stac_io import StacIO from pystac.utils import make_absolute_href, make_relative_href from .link import Link @@ -19,6 +18,7 @@ from .catalog import CatalogType # pyright: ignore[reportDeprecated] from .href_generator import HrefGenerator from .item import Item + from .stac_io import StacIO from .writer import Writer @@ -196,9 +196,9 @@ def save( include_self_links = True if stac_io: - from .writer import StacIOWriter # pyright: ignore[reportDeprecated] + from .stac_io import StacIOWriter - writer = StacIOWriter(stac_io) # pyright: ignore[reportDeprecated] + writer = StacIOWriter(stac_io) else: writer = None diff --git a/src/pystac/stac_io.py b/src/pystac/stac_io.py index 69cc9d432..0828edc41 100644 --- a/src/pystac/stac_io.py +++ b/src/pystac/stac_io.py @@ -10,6 +10,7 @@ import os from abc import ABC, abstractmethod from collections.abc import Callable +from pathlib import Path from typing import TYPE_CHECKING, Any import pystac @@ -476,3 +477,14 @@ def read_text_from_href(self, href: str) -> str: raise Exception(f"Could not read uri {href}") from e else: return super().read_text_from_href(href) + + +class StacIOWriter: + def __init__(self, stac_io: StacIO) -> None: + self.stac_io: StacIO = stac_io + + def put_json(self, data: dict[str, Any], href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] + raise NotImplementedError + + def delete(self, href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] + raise NotImplementedError diff --git a/src/pystac/writer.py b/src/pystac/writer.py index 639609f5f..b094e5d36 100644 --- a/src/pystac/writer.py +++ b/src/pystac/writer.py @@ -3,10 +3,6 @@ from pathlib import Path from typing import Any, Protocol -from typing_extensions import deprecated - -from pystac.stac_io import StacIO - class Writer(Protocol): def put_json(self, data: dict[str, Any], href: str | Path) -> None: ... @@ -32,18 +28,6 @@ def delete(self, href: str | Path) -> None: raise ValueError("StandardLibraryWriter cannot delete urls") -@deprecated("StacIO is deprecated in PySTAC v2") -class StacIOWriter: - def __init__(self, stac_io: StacIO) -> None: - self.stac_io: StacIO = stac_io - - def put_json(self, data: dict[str, Any], href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] - raise NotImplementedError - - def delete(self, href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] - raise NotImplementedError - - def set_default_writer(writer: Writer) -> None: global DEFAULT_WRITER DEFAULT_WRITER = writer # pyright: ignore[reportConstantRedefinition] From 43fa637e27b0bff0cb53a0847d00d14d78811088 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 07:42:47 -0700 Subject: [PATCH 21/60] feat: stacio writer --- pyproject.toml | 4 ++-- src/pystac/stac_io.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 51d9ec63a..f635366c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md" Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software" [tool.basedpyright] -ignore = ["tests/v1"] +ignore = ["tests/v1", "src/pystac/stac_io.py"] reportExplicitAny = false reportAny = false reportImportCycles = false @@ -75,7 +75,7 @@ reportImplicitStringConcatenation = false [tool.ruff] lint.select = ["E", "F", "I", "UP"] -extend-exclude = ["tests/v1", "src/pystac/validation"] +extend-exclude = ["tests/v1", "src/pystac/validation", "src/pystac/stac_io.py"] [tool.pytest.ini_options] addopts = "--block-network --record-mode=none" diff --git a/src/pystac/stac_io.py b/src/pystac/stac_io.py index 0828edc41..e4d8ba64a 100644 --- a/src/pystac/stac_io.py +++ b/src/pystac/stac_io.py @@ -483,8 +483,8 @@ class StacIOWriter: def __init__(self, stac_io: StacIO) -> None: self.stac_io: StacIO = stac_io - def put_json(self, data: dict[str, Any], href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] - raise NotImplementedError + def put_json(self, data: dict[str, Any], href: str | Path) -> None: + self.stac_io.save_json(href, data) def delete(self, href: str | Path) -> None: # pyright: ignore[reportUnusedParameter] raise NotImplementedError From 564853ddf863110ce69f5db7f518477cdad5ffdb Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:01:11 -0700 Subject: [PATCH 22/60] feat: some more stuff --- src/pystac/container.py | 21 ++++++++++++++++----- src/pystac/link.py | 3 +++ src/pystac/stac_object.py | 15 ++++++++++++--- tests/v1/test_catalog.py | 1 + 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index b93f32d09..aabb890f2 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -39,13 +39,15 @@ def get_items(self, *ids: str, recursive: bool = False) -> Iterator[Item]: def get_item_links(self) -> list[Link]: return [link for link in self.links if link.is_item()] - def add_item(self, item: Item, set_parent: bool = True) -> Link: + def add_item( + self, item: Item, set_parent: bool = True, title: str | None = None + ) -> Link: from .item import Item if not isinstance(item, Item): # pyright: ignore[reportUnnecessaryIsInstance] raise STACError("Cannot add a non-item as an item") # pyright: ignore[reportUnreachable] - item_link = Link(target=item, rel=RelType.ITEM) + item_link = Link(target=item, rel=RelType.ITEM, title=title) self.links.append(item_link) if set_parent: @@ -206,16 +208,22 @@ def save( dest_href=dest_href, writer=writer, include_self_links=include_self_links ) - def normalize_hrefs(self, root_href: str, use_absolute_links: bool = False) -> None: + def normalize_hrefs( + self, + root_href: str, + use_absolute_links: bool = False, + skip_unresolved: bool = False, + ) -> None: from .href_generator import BestPracticesHrefGenerator href_generator = BestPracticesHrefGenerator() previous_self_href = self.get_self_href() - self.set_self_href(href_generator.get_root(root_href, self)) + self.set_self_href(href_generator.get_root(make_absolute_href(root_href), self)) self.render_all( use_absolute_links=use_absolute_links, href_generator=href_generator, previous_self_href=previous_self_href, + skip_unresolved=skip_unresolved, ) def render_all( @@ -223,8 +231,11 @@ def render_all( use_absolute_links: bool = False, href_generator: HrefGenerator | None = None, previous_self_href: str | None = None, + skip_unresolved: bool = False, ) -> None: - for _ in self.render(use_absolute_links, href_generator, previous_self_href): + for _ in self.render( + use_absolute_links, href_generator, previous_self_href, skip_unresolved + ): pass def save_all( diff --git a/src/pystac/link.py b/src/pystac/link.py index 7ba05acab..7b1882620 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -107,6 +107,9 @@ def is_child(self) -> bool: def is_derived_from(self) -> bool: return self.rel == RelType.DERIVED_FROM + def is_resolved(self) -> bool: + return self._target is not None + def is_json(self) -> bool: return self.media_type in (MediaType.JSON, MediaType.GEOJSON) diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 46cee7dc3..4767dcb48 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -253,9 +253,11 @@ def validate( def render( self, + /, use_absolute_links: bool = False, href_generator: HrefGenerator | None = None, previous_self_href: str | None = None, + skip_unresolved: bool = False, ) -> Iterator[STACObject]: """Remove and re-create all hierarchical links on this STAC object and all children and items, yielding each.""" @@ -263,6 +265,7 @@ def render( use_absolute_links=use_absolute_links, href_generator=href_generator, previous_self_href=previous_self_href, + skip_unresolved=skip_unresolved, root=None, parent=None, collection=None, @@ -270,10 +273,12 @@ def render( def _render( self, + /, root: Container | None = None, parent: Container | None = None, collection: Collection | None = None, use_absolute_links: bool = False, + skip_unresolved: bool = False, href_generator: HrefGenerator | None = None, previous_self_href: str | None = None, ) -> Iterator[STACObject]: @@ -292,8 +297,8 @@ def _render( raise ValueError("Cannot render without a self href") if root is None and isinstance(self, Container): root = self + links: list[Link] = [] - non_hierarchical_links: list[Link] = [] if root and (root_href := root.get_self_href()): links.append( self._make_link( @@ -322,6 +327,10 @@ def _render( ) ) for link in self.links: + if skip_unresolved and not link.is_resolved(): + links.append(link) + continue + if link.is_child() or link.is_item(): stac_object = link.get_target(previous_self_href, self.reader) stac_object_previous_self_href = stac_object.get_self_href() @@ -373,9 +382,9 @@ def _render( previous_self_href=stac_object_previous_self_href, ) elif not link.is_hierarchical(): - non_hierarchical_links.append(link) + links.append(link) - self.links = links + non_hierarchical_links + self.links = links yield self def save_object( diff --git a/tests/v1/test_catalog.py b/tests/v1/test_catalog.py index cb9e165ee..50bf9b93c 100644 --- a/tests/v1/test_catalog.py +++ b/tests/v1/test_catalog.py @@ -603,6 +603,7 @@ def test_subcatalogs_saved_to_correct_path(self) -> None: f"{expected_item_path} is not a file." ) + @pytest.mark.xfail(reason="CatalogType is deprecated in pystac v2, so we don't care about fixing this test") def test_clone_uses_previous_catalog_type(self) -> None: catalog = TestCases.case_1() assert catalog.catalog_type == CatalogType.SELF_CONTAINED From 945e384330d7d4107cb71e969b0325eb27011783 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:11:58 -0700 Subject: [PATCH 23/60] feat: skip unresolved --- src/pystac/link.py | 3 +++ src/pystac/stac_object.py | 32 +++++++++++++------------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/pystac/link.py b/src/pystac/link.py index 7b1882620..2d61e6417 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -116,6 +116,9 @@ def is_json(self) -> bool: def get_href(self) -> str | None: return self._href or self._target and self._target.get_self_href() + def set_href(self, href: str) -> None: + self._href = href + @property @deprecated("target is deprecated, either use .get_href() or .get_target()") def target(self) -> str | STACObject | None: diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 4767dcb48..da508d857 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -301,27 +301,24 @@ def _render( links: list[Link] = [] if root and (root_href := root.get_self_href()): links.append( - self._make_link( - rel=RelType.ROOT, - target=root, + self._update_link_href( + Link(rel=RelType.ROOT, target=root), href=root_href, make_absolute=use_absolute_links, ) ) if parent and (parent_href := parent.get_self_href()): links.append( - self._make_link( - rel=RelType.PARENT, - target=parent, + self._update_link_href( + Link(rel=RelType.PARENT, target=parent), href=parent_href, make_absolute=use_absolute_links, ) ) if collection and (collection_href := collection.get_self_href()): links.append( - self._make_link( - rel=RelType.COLLECTION, - target=collection, + self._update_link_href( + Link(rel=RelType.COLLECTION, target=collection), href=collection_href, make_absolute=use_absolute_links, ) @@ -338,9 +335,8 @@ def _render( href = href_generator.get_child(self_href, stac_object) stac_object.set_self_href(href) links.append( - self._make_link( - rel=RelType.CHILD, - target=stac_object, + self._update_link_href( + link, href=href, make_absolute=use_absolute_links, ) @@ -349,9 +345,8 @@ def _render( href = href_generator.get_item(self_href, stac_object) stac_object.set_self_href(href) links.append( - self._make_link( - rel=RelType.ITEM, - target=stac_object, + self._update_link_href( + link, href=href, make_absolute=use_absolute_links, ) @@ -410,14 +405,13 @@ def clone[T: STACObject](self: T) -> T: def __repr__(self) -> str: return f"<{self.__class__} id={self.id}>" - def _make_link( - self, rel: str, target: STACObject, href: str, make_absolute: bool - ) -> Link: + def _update_link_href(self, link: Link, href: str, make_absolute: bool) -> Link: self_href = self.get_self_href() href = make_absolute_href(href, self_href, start_is_dir=False) if not make_absolute and self_href: href = make_relative_href(href, self_href, start_is_dir=False) - return Link(rel=rel, target=target, href=href) + link.set_href(href) + return link def _maybe_get_link_target(self, rel: str) -> STACObject | None: link = self.get_link(rel) From 5d2cb084b916a5cf465eb2c8a257051cdb15a7b5 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:19:13 -0700 Subject: [PATCH 24/60] feat: skip_unresolved for normalize_and_save --- src/pystac/container.py | 14 ++++++++++++-- src/pystac/link.py | 3 +++ tests/v1/test_catalog.py | 4 +++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index aabb890f2..4ce643e39 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -165,12 +165,14 @@ def normalize_and_save( self, root_href: str, catalog_type: CatalogType | None = None, # pyright: ignore[reportDeprecated] + skip_unresolved: bool = False, ) -> None: from .catalog import CatalogType # pyright: ignore[reportDeprecated] self.normalize_hrefs( root_href, use_absolute_links=catalog_type == CatalogType.ABSOLUTE_PUBLISHED, # pyright: ignore[reportDeprecated] + skip_unresolved=skip_unresolved, ) self.save_all() @@ -278,7 +280,11 @@ def save_iter( yield self - for child in self.get_children(): + for child_link in self.get_child_links(): + child = child_link.maybe_get_target() + if not child or not isinstance(child, Container): + continue + if dest_href is not None: if (child_self_href := child.get_self_href()) and self_href: child_dest_href = make_absolute_href( @@ -301,7 +307,11 @@ def save_iter( dest_href=None, writer=writer, include_self_links=include_self_links ) - for item in self.get_items(): + for item_link in self.get_item_links(): + item = item_link.maybe_get_target() + if not item: + continue + if dest_href is not None: if (item_self_href := item.get_self_href()) and self_href: item_dest_href = make_absolute_href( diff --git a/src/pystac/link.py b/src/pystac/link.py index 2d61e6417..1bd107bea 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -139,6 +139,9 @@ def get_target(self, start_href: str | None, reader: Reader) -> STACObject: return self._target + def maybe_get_target(self) -> STACObject | None: + return self._target + def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: if transform_href is not None: warnings.warn( diff --git a/tests/v1/test_catalog.py b/tests/v1/test_catalog.py index 50bf9b93c..18a3daf70 100644 --- a/tests/v1/test_catalog.py +++ b/tests/v1/test_catalog.py @@ -684,7 +684,9 @@ def test_save_unresolved(self) -> None: assert len(os.listdir(temporary_directory)) == 2 with tempfile.TemporaryDirectory() as temporary_directory: - with pytest.raises(STACError, match="does not resolve to a STAC object"): + # We remove the error matching string for pystac v2 + # with pytest.raises(STACError, match="does not resolve to a STAC object"): + with pytest.raises(STACError): catalog.normalize_and_save(temporary_directory, skip_unresolved=False) def test_generate_subcatalogs_works_with_custom_properties(self) -> None: From b2db68f83405e83f093befa81676e78f7db293b8 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:23:08 -0700 Subject: [PATCH 25/60] feat: add some docs about key changes --- docs/pystac-v2.0.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/pystac-v2.0.md b/docs/pystac-v2.0.md index ed7f92514..91354c227 100644 --- a/docs/pystac-v2.0.md +++ b/docs/pystac-v2.0.md @@ -20,3 +20,16 @@ To do so, we have some specific implementation strategies. - **Do fewer things at once**: One of the biggest design problems of PySTAC v1.0 (in this author's opinion) was that many functions tried to be "helpful" by doing a lot of things at once. When possible, we should simplify methods to do just one thing, and provide intuitive patterns for doing complex operations using multiple method calls. Top-level functions can be used to "synthesize" complex operations, e.g. `pystac.read_file`. + +## Key changes + +- Rather than making `Collection` a subclass of `Catalog`, both `Catalog` and `Collection` are subclasses of an abstract `Container` class. +- `CatalogType` is deprecated and not used. + Instead, we provide arguments like `use_absolute_hrefs` and `include_self_href`. +- `StacIO` is deprecated in favor of `Reader` and `Writer`. + We provide simple wrappers to allow folks to continue using their existing custom `StacIO` classes. +- `Layout` is deprecated in favor of `HrefGenerator`. + We provide re-implementations of each existing `Layout` (TODO). +- Link hrefs are now more explicit. + In PySTAC v1, `Link.to_dict()` mutated the `href` based on the `CatalogType` and other factors. + In PySTAC v2, all href mutation is done _before_ serialization. From 47cb769d9b3688f35677da6030bf29b938919d41 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:25:11 -0700 Subject: [PATCH 26/60] feat: add julia to authors --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f635366c3..c5cf43983 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ authors = [ { name = "Rob Emanuele", email = "rdemanuele@gmail.com" }, { name = "Jon Duckworth", email = "duckontheweb@gmail.com" }, { name = "Pete Gadomski", email = "pete.gadomski@gmail.com" }, + { name = "Julia Signell", email = "jsignell@gmail.com" }, ] maintainers = [{ name = "Pete Gadomski", email = "pete.gadomski@gmail.com" }] keywords = ["pystac", "imagery", "raster", "catalog", "STAC"] From 04f60c1d156d390ca3c656880c2aa0f4cce0566d Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:36:27 -0700 Subject: [PATCH 27/60] fix: add docs workflow --- .github/workflows/docs.yml | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..01ea0c94f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,42 @@ +name: Docs + +on: + push: + branches: + - main + - v2 # FIXME: remove before mergin + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 + - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b + - name: Build docs + run: uv run mkdocs build --strict + - name: Upload artifact + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa + with: + path: ./site + + deploy: + name: Deploy to GitHub Pages + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2e6aed7e0febac5b3c0c03e From a930d9d970a1647b5fc65a172bed6c8cf6a78199 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:38:48 -0700 Subject: [PATCH 28/60] fix: deploy pages sha --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 01ea0c94f..bbf246c6c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -39,4 +39,4 @@ jobs: needs: build steps: - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2e6aed7e0febac5b3c0c03e + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e From da95f6567d44dc69f6531d467b92567ca1210bc9 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 08:43:01 -0700 Subject: [PATCH 29/60] fix: remove note about layouts --- docs/pystac-v2.0.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/pystac-v2.0.md b/docs/pystac-v2.0.md index 91354c227..4a162ec66 100644 --- a/docs/pystac-v2.0.md +++ b/docs/pystac-v2.0.md @@ -28,8 +28,6 @@ To do so, we have some specific implementation strategies. Instead, we provide arguments like `use_absolute_hrefs` and `include_self_href`. - `StacIO` is deprecated in favor of `Reader` and `Writer`. We provide simple wrappers to allow folks to continue using their existing custom `StacIO` classes. -- `Layout` is deprecated in favor of `HrefGenerator`. - We provide re-implementations of each existing `Layout` (TODO). - Link hrefs are now more explicit. In PySTAC v1, `Link.to_dict()` mutated the `href` based on the `CatalogType` and other factors. In PySTAC v2, all href mutation is done _before_ serialization. From 1118e0853eb0517ec400d1892c845172141568f6 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 09:35:32 -0700 Subject: [PATCH 30/60] feat: override _stac_io --- src/pystac/__init__.py | 2 ++ src/pystac/container.py | 62 ++++++++++++++++++++++++++++++++++++++- src/pystac/item.py | 6 ++++ src/pystac/stac_io.py | 8 +++++ src/pystac/stac_object.py | 11 +++++++ 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py index 2cfaf0e8a..0ef340657 100644 --- a/src/pystac/__init__.py +++ b/src/pystac/__init__.py @@ -19,6 +19,7 @@ STACError, STACTypeError, STACValidationError, + TemplateError, ) from .io import read_file from .item import Item @@ -79,6 +80,7 @@ def get_stac_version() -> str: "ProviderRole", "STACError", "STACObject", + "TemplateError", "STACTypeError", "STACValidationError", "SpatialExtent", diff --git a/src/pystac/container.py b/src/pystac/container.py index 4ce643e39..b6b1d0876 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -3,13 +3,14 @@ import os.path from abc import ABC from collections.abc import Iterator -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from typing_extensions import deprecated from pystac.errors import STACError from pystac.utils import make_absolute_href, make_relative_href +from .layout import LayoutTemplate from .link import Link from .rel_type import RelType from .stac_object import STACObject @@ -210,6 +211,65 @@ def save( dest_href=dest_href, writer=writer, include_self_links=include_self_links ) + def generate_subcatalogs( + self, + template: str, + defaults: dict[str, Any] | None = None, + parent_ids: list[str] | None = None, + ) -> list[Container]: + from .catalog import Catalog + + result: list[Container] = [] + parent_ids = parent_ids or list() + parent_ids.append(self.id) + for child in self.get_children(): + result.extend( + child.generate_subcatalogs( + template, defaults=defaults, parent_ids=parent_ids.copy() + ) + ) + + layout_template = LayoutTemplate(template, defaults=defaults) + keep_item_links: list[Link] = [] + for link in self.get_item_links(): + item = link.get_target(self.get_self_href(), self.reader) + subcatalog_ids = layout_template.substitute(item).split("/") + id_iter = reversed(parent_ids) + if all([f"{id}" == next(id_iter, None) for id in reversed(subcatalog_ids)]): + # Skip items for which the sub-catalog structure already + # matches the template. The list of parent IDs can include more + # elements on the root side, so compare the reversed sequences. + keep_item_links.append(link) + continue + current_parent = self + for subcatalog_id in subcatalog_ids: + subcatalog = current_parent.get_child(subcatalog_id) + if subcatalog is None: + subcatalog_description = ( + f"Catalog of items from {current_parent.id} with " + f"id {subcatalog_id}" + ) + subcatalog = Catalog( + id=subcatalog_id, description=subcatalog_description + ) + _ = current_parent.add_child(subcatalog) + result.append(subcatalog) + current_parent = subcatalog + + # resolve collection link so when added back points to correct location + collection_link = item.get_link(RelType.COLLECTION) + if collection_link is not None: + _ = collection_link.get_target(item.get_self_href(), self.reader) + + _ = current_parent.add_item(item) + + # keep only non-item links and item links that have not been moved elsewhere + self.links = [ + link for link in self.links if link.rel != RelType.ITEM + ] + keep_item_links + + return result + def normalize_hrefs( self, root_href: str, diff --git a/src/pystac/item.py b/src/pystac/item.py index 3c9711db8..f01dd2a56 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -232,6 +232,12 @@ def __init__(self, datetime: dt.datetime | str | None = None, **kwargs: Any): def __getitem__(self, key: str) -> Any: return self.extra_fields[key] + def __getattr__(self, key: str) -> Any: + return self.extra_fields[key] + + def __contains__(self, key: str) -> bool: + return key == "datetime" or key in self.extra_fields + @classmethod def try_from( cls, diff --git a/src/pystac/stac_io.py b/src/pystac/stac_io.py index e4d8ba64a..f2f1a07bd 100644 --- a/src/pystac/stac_io.py +++ b/src/pystac/stac_io.py @@ -479,6 +479,14 @@ def read_text_from_href(self, href: str) -> str: return super().read_text_from_href(href) +class StacIOReader: + def __init__(self, stac_io: StacIO) -> None: + self.stac_io = stac_io + + def get_json(self, href: str | Path) -> dict[str, Any]: + return self.stac_io.read_json(href) + + class StacIOWriter: def __init__(self, stac_io: StacIO) -> None: self.stac_io: StacIO = stac_io diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index da508d857..97fb240ee 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -69,6 +69,17 @@ def __init__( self._root: Container | None = None self._href: str | None = None + @override + def __setattr__(self, name: str, value: Any, /) -> None: + if name == "_stac_io": + warnings.warn("_stac_io is a deprecated attribute. Set .reader or .writer") + + from .stac_io import StacIOReader, StacIOWriter + + self.reader = StacIOReader(value) + self.writer = StacIOWriter(value) + return super().__setattr__(name, value) + @classmethod def from_file[T: STACObject]( cls: type[T], path: str | Path, reader: Reader = DEFAULT_READER From a7ece2704770dc874f2cc635df5f97523c958831 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 09:36:44 -0700 Subject: [PATCH 31/60] docs: add notes about stac-geoparquet, async, and pystac-client --- docs/pystac-v2.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pystac-v2.0.md b/docs/pystac-v2.0.md index 4a162ec66..e7195ef68 100644 --- a/docs/pystac-v2.0.md +++ b/docs/pystac-v2.0.md @@ -23,6 +23,8 @@ To do so, we have some specific implementation strategies. ## Key changes +- **stac-geoparquet** and **async** support is provided via **rustac** +- **pystac-client** has been merged in, and is can be used via an optional dependency - Rather than making `Collection` a subclass of `Catalog`, both `Catalog` and `Collection` are subclasses of an abstract `Container` class. - `CatalogType` is deprecated and not used. Instead, we provide arguments like `use_absolute_hrefs` and `include_self_href`. From f7fdefceed8fdef390d408e0be729c8b09c694d7 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 09:54:33 -0700 Subject: [PATCH 32/60] feat: map_items --- src/pystac/container.py | 38 +++++++++++++++++++++++++++++++++++++- src/pystac/item.py | 9 +++++++++ src/pystac/link.py | 3 +++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/pystac/container.py b/src/pystac/container.py index b6b1d0876..ceedd8e33 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -1,8 +1,10 @@ from __future__ import annotations +import copy import os.path +import warnings from abc import ABC -from collections.abc import Iterator +from collections.abc import Callable, Iterator from typing import TYPE_CHECKING, Any from typing_extensions import deprecated @@ -64,6 +66,40 @@ def add_items(self, items: list[Item]) -> list[Link]: links.append(self.add_item(item)) return links + def map_items(self, item_mapper: Callable[[Item], Item | list[Item]]) -> Container: + from .item import Item + + def apply(container: Container) -> None: + links: list[Link] = [] + for link in container.links: + if not link.is_item(): + links.append(link) + continue + + item = link.get_target(container.get_self_href(), container.reader) + if not isinstance(item, Item): + warnings.warn("Item link does not point to an item") + links.append(link) + continue + + item_or_items = item_mapper(item) + if isinstance(item_or_items, list): + for new_item in item_or_items: + new_link = copy.deepcopy(link) + new_link.set_target(new_item) + links.append(new_link) + else: + link.set_target(item_or_items) + links.append(link) + container.links = links + + container = self.clone() + apply(container) + for child in container.get_children(recursive=True): + apply(child) + + return container + def remove_item(self, id: str) -> Item | None: from .item import Item diff --git a/src/pystac/item.py b/src/pystac/item.py index f01dd2a56..463e91aea 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -232,12 +232,21 @@ def __init__(self, datetime: dt.datetime | str | None = None, **kwargs: Any): def __getitem__(self, key: str) -> Any: return self.extra_fields[key] + def __setitem__(self, name: str, value: Any, /) -> None: + self.extra_fields[name] = value + def __getattr__(self, key: str) -> Any: return self.extra_fields[key] def __contains__(self, key: str) -> bool: return key == "datetime" or key in self.extra_fields + def __deepcopy__(self, memo: dict[int, Any]) -> Properties: + return Properties( + datetime=self.datetime, + **copy.deepcopy(self.extra_fields, memo), + ) + @classmethod def try_from( cls, diff --git a/src/pystac/link.py b/src/pystac/link.py index 1bd107bea..594e2fd89 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -142,6 +142,9 @@ def get_target(self, start_href: str | None, reader: Reader) -> STACObject: def maybe_get_target(self) -> STACObject | None: return self._target + def set_target(self, target: STACObject) -> None: + self._target = target + def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: if transform_href is not None: warnings.warn( From f40a8df361f9fcb3973830a04c26dbc8e4feff66 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 10:55:44 -0700 Subject: [PATCH 33/60] docs: add small note --- docs/pystac-v2.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/pystac-v2.0.md b/docs/pystac-v2.0.md index e7195ef68..ff9e5c989 100644 --- a/docs/pystac-v2.0.md +++ b/docs/pystac-v2.0.md @@ -20,6 +20,7 @@ To do so, we have some specific implementation strategies. - **Do fewer things at once**: One of the biggest design problems of PySTAC v1.0 (in this author's opinion) was that many functions tried to be "helpful" by doing a lot of things at once. When possible, we should simplify methods to do just one thing, and provide intuitive patterns for doing complex operations using multiple method calls. Top-level functions can be used to "synthesize" complex operations, e.g. `pystac.read_file`. +- **Don't mutate on read** (aka auto-migration) ## Key changes From 402284916446700c5c95ac7840186350a0a9046e Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 11:03:13 -0700 Subject: [PATCH 34/60] fix: item tests --- src/pystac/catalog.py | 5 ++++ src/pystac/item.py | 3 --- src/pystac/layout.py | 52 ++++++++++++++++++--------------------- src/pystac/stac_object.py | 16 +++++++----- tests/v1/test_item.py | 5 ++-- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py index 75744a58f..2192c01ac 100644 --- a/src/pystac/catalog.py +++ b/src/pystac/catalog.py @@ -42,6 +42,11 @@ def __init__( def catalog_type(self) -> CatalogType | None: # pyright: ignore[reportDeprecated] return CatalogType.determine_type(self.to_dict()) # pyright: ignore[reportDeprecated] + @catalog_type.setter + @deprecated("catalog_type is deprecated, and setting it has no effect") + def catalog_type(self, _: CatalogType) -> None: # pyright: ignore[reportDeprecated] + pass + @override @classmethod def from_dict( diff --git a/src/pystac/item.py b/src/pystac/item.py index 463e91aea..3b38aed1c 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -235,9 +235,6 @@ def __getitem__(self, key: str) -> Any: def __setitem__(self, name: str, value: Any, /) -> None: self.extra_fields[name] = value - def __getattr__(self, key: str) -> Any: - return self.extra_fields[key] - def __contains__(self, key: str) -> bool: return key == "datetime" or key in self.extra_fields diff --git a/src/pystac/layout.py b/src/pystac/layout.py index ed790471a..eb81b7f53 100644 --- a/src/pystac/layout.py +++ b/src/pystac/layout.py @@ -1,26 +1,19 @@ from __future__ import annotations -import warnings - -warnings.warn( - message="pystac.layout is deprecated", - category=DeprecationWarning, -) - import posixpath from abc import ABC, abstractmethod from collections import OrderedDict from collections.abc import Callable from typing import TYPE_CHECKING, Any -import pystac -from pystac.utils import is_file_path +from .errors import STACError +from .utils import is_file_path if TYPE_CHECKING: - from pystac.catalog import Catalog - from pystac.collection import Collection - from pystac.item import Item - from pystac.stac_object import STACObject + from .catalog import Catalog + from .collection import Collection + from .item import Item + from .stac_object import STACObject class TemplateError(Exception): @@ -132,15 +125,16 @@ def __init__(self, template: str, defaults: dict[str, str] | None = None) -> Non def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any: if template_var in self.ITEM_TEMPLATE_VARS: - if isinstance(stac_object, pystac.Item): + if isinstance(stac_object, Item): # Datetime dt = stac_object.datetime if dt is None: dt = stac_object.common_metadata.start_datetime if dt is None: - raise pystac.TemplateError( + raise TemplateError( f"Item {stac_object} does not have a datetime or " - f"datetime range set; cannot template {template_var} in {self.template}" + f"datetime range set; cannot template {template_var} " + f"in {self.template}" ) if template_var == "year": @@ -156,20 +150,22 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any if template_var == "collection": if stac_object.collection_id is not None: return stac_object.collection_id - raise pystac.TemplateError( + raise TemplateError( f"Item {stac_object} does not have a collection ID set; " f"cannot template {template_var} in {self.template}" ) else: - raise pystac.TemplateError( - f'"{template_var}" cannot be used to template non-Item {stac_object} in {self.template}' + raise TemplateError( + f'"{template_var}" cannot be used to template non-Item "' + f"{stac_object} in {self.template}" ) # Allow dot-notation properties for arbitrary object values. props = template_var.split(".") - prop_source: pystac.STACObject | dict[str, Any] | None = None - error = pystac.TemplateError( - f"Cannot find property {template_var} on {stac_object} for template {self.template}" + prop_source: STACObject | dict[str, Any] | None = None + error = TemplateError( + f"Cannot find property {template_var} on {stac_object} for template " + f"{self.template}" ) try: @@ -199,7 +195,7 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any if not hasattr(v, prop): raise error v = getattr(v, prop) - except pystac.TemplateError as e: + except TemplateError as e: if template_var in self.defaults: return self.defaults[template_var] raise e @@ -222,7 +218,7 @@ def get_template_values(self, stac_object: STACObject) -> dict[str, Any]: stac object. Raises: - pystac.TemplateError: If a value for a template variable cannot be + TemplateError: If a value for a template variable cannot be derived from the stac object and there is no default, this error will be raised. """ @@ -268,14 +264,14 @@ def get_href( if is_file_path(parent_dir): parent_dir = os.path.dirname(parent_dir) - if isinstance(stac_object, pystac.Item): + if isinstance(stac_object, Item): return self.get_item_href(stac_object, parent_dir) - elif isinstance(stac_object, pystac.Collection): + elif isinstance(stac_object, Collection): return self.get_collection_href(stac_object, parent_dir, is_root) - elif isinstance(stac_object, pystac.Catalog): + elif isinstance(stac_object, Catalog): return self.get_catalog_href(stac_object, parent_dir, is_root) else: - raise pystac.STACError(f"Unknown STAC object type {stac_object}") + raise STACError(f"Unknown STAC object type {stac_object}") @abstractmethod def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 97fb240ee..1f40f2170 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -71,14 +71,18 @@ def __init__( @override def __setattr__(self, name: str, value: Any, /) -> None: - if name == "_stac_io": - warnings.warn("_stac_io is a deprecated attribute. Set .reader or .writer") + match name: + case "_stac_io": + warnings.warn( + "_stac_io is a deprecated attribute. Set .reader or .writer" + ) - from .stac_io import StacIOReader, StacIOWriter + from .stac_io import StacIOReader, StacIOWriter - self.reader = StacIOReader(value) - self.writer = StacIOWriter(value) - return super().__setattr__(name, value) + self.reader = StacIOReader(value) + self.writer = StacIOWriter(value) + case _: + return super().__setattr__(name, value) @classmethod def from_file[T: STACObject]( diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index bfe25d33b..16bc1540b 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -598,7 +598,7 @@ def test_resolve_collection_with_root( assert root.id == "root" -@pytest.mark.vcr() +@pytest.mark.xfail(reason="Setting the catalog type doesn't change behavior") def test_non_hierarchical_relative_link() -> None: root = pystac.Catalog("root", "root") a = pystac.Catalog("a", "a") @@ -616,8 +616,7 @@ def test_non_hierarchical_relative_link() -> None: related_href = [link for link in a.links if link.rel == "related"][0].get_href() assert related_href is not None and not is_absolute_href(related_href) - # @gadomski thinks this is a bad assertion, and has removed it for v2 - # assert a.target_in_hierarchy(b) + assert a.target_in_hierarchy(b) assert root.target_in_hierarchy(next(b.get_items())) assert root.target_in_hierarchy(root) From 04eac44800de5282920a36621d88a33383fb1613 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 13:09:30 -0700 Subject: [PATCH 35/60] fix: template checks --- pyproject.toml | 3 +++ src/pystac/layout.py | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c5cf43983..c3dc5ab8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,9 @@ lint.select = ["E", "F", "I", "UP"] extend-exclude = ["tests/v1", "src/pystac/validation", "src/pystac/stac_io.py"] [tool.pytest.ini_options] +filterwarnings = [ + "ignore", # FIXME: turn this into error +] addopts = "--block-network --record-mode=none" [tool.uv] diff --git a/src/pystac/layout.py b/src/pystac/layout.py index eb81b7f53..a5c7f59a0 100644 --- a/src/pystac/layout.py +++ b/src/pystac/layout.py @@ -124,6 +124,8 @@ def __init__(self, template: str, defaults: dict[str, str] | None = None) -> Non self.template_vars = template_vars def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any: + from .item import Item + if template_var in self.ITEM_TEMPLATE_VARS: if isinstance(stac_object, Item): # Datetime @@ -187,14 +189,12 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any v: Any = prop_source for prop in template_var.split("."): - if isinstance(v, dict): - if prop not in v: - raise error + if prop in v: v = v[prop] - else: - if not hasattr(v, prop): - raise error + elif hasattr(v, prop): v = getattr(v, prop) + else: + raise error except TemplateError as e: if template_var in self.defaults: return self.defaults[template_var] From 97a12e7f4902b61e1ec4ed58099c81ff8b97a2af Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Fri, 9 Jan 2026 13:24:35 -0700 Subject: [PATCH 36/60] feat: asset map_assets --- src/pystac/container.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/pystac/container.py b/src/pystac/container.py index ceedd8e33..8d1ac12e7 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -12,6 +12,7 @@ from pystac.errors import STACError from pystac.utils import make_absolute_href, make_relative_href +from .asset import Asset from .layout import LayoutTemplate from .link import Link from .rel_type import RelType @@ -100,6 +101,27 @@ def apply(container: Container) -> None: return container + def map_assets( + self, + asset_mapper: Callable[ + [str, Asset], Asset | tuple[str, Asset] | dict[str, Asset] + ], + ) -> Container: + def apply(item: Item) -> Item: + assets: dict[str, Asset] = dict() + for key, asset in item.assets.items(): + value = asset_mapper(key, asset) + if isinstance(value, Asset): + assets[key] = value + elif isinstance(value, tuple): + assets[value[0]] = value[1] + else: + assets.update(value) + item.assets = assets + return item + + return self.map_items(apply) + def remove_item(self, id: str) -> Item | None: from .item import Item From bd0b611c5cf56c5523bb69b2b8ba73d795b3d1e1 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 Jan 2026 08:04:01 -0700 Subject: [PATCH 37/60] tests: skip v1 tests by default --- tests/conftest.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 30bfe5324..a8d3a53de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,12 +2,29 @@ from typing import Any, cast import pytest -from pytest import FixtureRequest +from pytest import Config, FixtureRequest, Parser import pystac.jsonschema from pystac import Catalog, Item from pystac.jsonschema import JSONSchemaValidator +V1_DIR = Path(__file__).parent / "v1" + + +def pytest_addoption(parser: Parser) -> None: + parser.addoption( + "--v1", + action="store_true", + default=False, + help="Run v1 tests (skipped by default)", + ) + + +def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: + if collection_path.is_relative_to(V1_DIR) and not config.getoption("--v1"): + return True + return None + @pytest.fixture(scope="module") def vcr_config() -> dict[str, Any]: From d66043b74ffc5cd9100b73fd633c6215bdb4075f Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 Jan 2026 08:04:57 -0700 Subject: [PATCH 38/60] ci: disable lint checks for now --- .github/workflows/continuous-integration.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index df0c1ffbd..61ce0a618 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -30,9 +30,9 @@ jobs: - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 with: python-version: ${{ matrix.python-version }} - - name: Lint - if: runner.os != 'Windows' - run: uv run prek run --all-files + # - name: Lint + # if: runner.os != 'Windows' + # run: uv run prek run --all-files - name: Test on windows if: runner.os == 'Windows' shell: bash From be68715cc4e4c8b076873c9a6c3a7df8f5ce807c Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 Jan 2026 08:06:51 -0700 Subject: [PATCH 39/60] feat: add typing extensions --- pyproject.toml | 1 + uv.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c3dc5ab8d..8ac47c4cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ requires-python = ">=3.12" dependencies = [ "python-dateutil>=2.9.0.post0", + "typing-extensions>=4.15.0", ] dynamic = ["version"] diff --git a/uv.lock b/uv.lock index 1c522df47..14e89be80 100644 --- a/uv.lock +++ b/uv.lock @@ -712,6 +712,7 @@ name = "pystac" source = { editable = "." } dependencies = [ { name = "python-dateutil" }, + { name = "typing-extensions" }, ] [package.optional-dependencies] @@ -755,6 +756,7 @@ requires-dist = [ { name = "obstore", marker = "extra == 'obstore'", specifier = ">=0.8.2" }, { name = "python-dateutil", specifier = ">=2.9.0.post0" }, { name = "referencing", marker = "extra == 'validation'", specifier = ">=0.37.0" }, + { name = "typing-extensions", specifier = ">=4.15.0" }, ] provides-extras = ["jinja2", "obstore", "validation"] From 925dbbb4d6c2dce6c7b26a65a8877ff0afcbd9c0 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 10:43:13 -0500 Subject: [PATCH 40/60] Update v2 test cassettes --- .../test_collection_from_dict[v1.0.0].yaml | 6 - .../test_collection_from_dict[v1.1.0].yaml | 6 - .../test_example_from_dict[path10].yaml | 313 ++++++- ...ml => test_example_from_dict[path11].yaml} | 6 - .../test_example_from_dict[path12].yaml | 179 ++-- .../test_example_from_dict[path13].yaml | 233 +++-- .../test_example_from_dict[path15].yaml | 862 ++++++++++-------- .../test_example_from_dict[path16].yaml | 179 ++-- .../test_example_from_dict[path17].yaml | 233 ++--- .../test_example_from_dict[path19].yaml | 862 ++++++++---------- .../test_example_from_dict[path1].yaml | 64 ++ .../test_example_from_dict[path3].yaml | 385 -------- .../test_example_from_dict[path4].yaml | 392 ++++++-- ...aml => test_example_from_dict[path5].yaml} | 6 - .../test_example_from_dict[path7].yaml | 140 +++ .../test_example_from_dict[path9].yaml | 432 --------- .../test_example_read_file[path10].yaml | 313 ++++++- ...ml => test_example_read_file[path11].yaml} | 6 - .../test_example_read_file[path12].yaml | 179 ++-- .../test_example_read_file[path13].yaml | 233 +++-- .../test_example_read_file[path15].yaml | 231 +++-- .../test_example_read_file[path16].yaml | 179 ++-- .../test_example_read_file[path17].yaml | 233 ++--- .../test_example_read_file[path19].yaml | 231 ++--- .../test_example_read_file[path1].yaml | 64 ++ .../test_example_read_file[path3].yaml | 385 -------- .../test_example_read_file[path4].yaml | 392 ++++++-- ...aml => test_example_read_file[path5].yaml} | 6 - .../test_example_read_file[path7].yaml | 140 +++ .../test_example_read_file[path9].yaml | 432 --------- .../test_validate_extension[v1.0.0].yaml | 4 - .../test_validate_extension[v1.1.0].yaml | 4 - 32 files changed, 3581 insertions(+), 3749 deletions(-) rename tests/cassettes/test_examples/{test_example_from_dict[path2].yaml => test_example_from_dict[path11].yaml} (99%) create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path1].yaml delete mode 100644 tests/cassettes/test_examples/test_example_from_dict[path3].yaml rename tests/cassettes/test_examples/{test_example_read_file[path8].yaml => test_example_from_dict[path5].yaml} (99%) create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path7].yaml delete mode 100644 tests/cassettes/test_examples/test_example_from_dict[path9].yaml rename tests/cassettes/test_examples/{test_example_read_file[path2].yaml => test_example_read_file[path11].yaml} (99%) create mode 100644 tests/cassettes/test_examples/test_example_read_file[path1].yaml delete mode 100644 tests/cassettes/test_examples/test_example_read_file[path3].yaml rename tests/cassettes/test_examples/{test_example_from_dict[path8].yaml => test_example_read_file[path5].yaml} (99%) create mode 100644 tests/cassettes/test_examples/test_example_read_file[path7].yaml delete mode 100644 tests/cassettes/test_examples/test_example_read_file[path9].yaml diff --git a/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml b/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml index cb75f6e95..92b20ee3b 100644 --- a/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml +++ b/tests/cassettes/test_collection/test_collection_from_dict[v1.0.0].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: @@ -84,8 +82,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: @@ -162,8 +158,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml b/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml index 6f78bf269..b543e00c0 100644 --- a/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml +++ b/tests/cassettes/test_collection/test_collection_from_dict[v1.1.0].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: @@ -135,8 +133,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: @@ -209,8 +205,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path10].yaml b/tests/cassettes/test_examples/test_example_from_dict[path10].yaml index b60cc96ec..e799c741e 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path10].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path10].yaml @@ -6,8 +6,248 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: @@ -63,4 +303,73 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path2].yaml b/tests/cassettes/test_examples/test_example_from_dict[path11].yaml similarity index 99% rename from tests/cassettes/test_examples/test_example_from_dict[path2].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path11].yaml index cb75f6e95..92b20ee3b 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path2].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path11].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: @@ -84,8 +82,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: @@ -162,8 +158,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml index edd89ac14..5562e8a96 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml @@ -6,73 +6,122 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -84,8 +133,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json response: @@ -150,8 +197,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path13].yaml b/tests/cassettes/test_examples/test_example_from_dict[path13].yaml index cb75f6e95..b543e00c0 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path13].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path13].yaml @@ -6,73 +6,122 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -84,51 +133,45 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n @@ -162,8 +205,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path15].yaml b/tests/cassettes/test_examples/test_example_from_dict[path15].yaml index cf86e117f..41db0987c 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path15].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path15].yaml @@ -6,73 +6,122 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -84,51 +133,45 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n @@ -162,16 +205,14 @@ interactions: - close Host: - proj.org - User-Agent: - - pystac/1.14.2 method: GET - uri: https://proj.org/schemas/v0.2/projjson.schema.json + uri: https://proj.org/schemas/v0.7/projjson.schema.json response: body: string: '' headers: Location: - - https://proj.org/en/latest/schemas/v0.2/projjson.schema.json + - https://proj.org/en/latest/schemas/v0.7/projjson.schema.json status: code: 302 message: Found @@ -182,32 +223,33 @@ interactions: - close Host: - proj.org - User-Agent: - - pystac/1.14.2 method: GET - uri: https://proj.org/en/latest/schemas/v0.2/projjson.schema.json + uri: https://proj.org/en/latest/schemas/v0.7/projjson.schema.json response: body: - string: "{\n \"$id\": \"https://proj.org/schemas/v0.2/projjson.schema.json\",\n + string: "{\n \"$id\": \"https://proj.org/schemas/v0.7/projjson.schema.json\",\n \ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"description\": - \"Schema for PROJJSON (v0.2.1)\",\n \"$comment\": \"This document is copyright - Even Rouault and PROJ contributors, 2019-2020, and subject to the MIT license. + \"Schema for PROJJSON (v0.7)\",\n \"$comment\": \"This document is copyright + Even Rouault and PROJ contributors, 2019-2023, and subject to the MIT license. This file exists both in data/ and in schemas/vXXX/. Keep both in sync. And - if changing the value of $id, change PROJJSON_CURRENT_VERSION accordingly + if changing the value of $id, change PROJJSON_DEFAULT_VERSION accordingly in io.cpp\",\n\n \"oneOf\": [\n { \"$ref\": \"#/definitions/crs\" },\n \ { \"$ref\": \"#/definitions/datum\" },\n { \"$ref\": \"#/definitions/datum_ensemble\" },\n { \"$ref\": \"#/definitions/ellipsoid\" },\n { \"$ref\": \"#/definitions/prime_meridian\" },\n { \"$ref\": \"#/definitions/single_operation\" },\n { \"$ref\": - \"#/definitions/concatenated_operation\" }\n ],\n\n \"definitions\": {\n\n - \ \"abridged_transformation\": {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": - \"string\", \"enum\": [\"AbridgedTransformation\"] },\n \"name\": { - \"type\": \"string\" },\n \"method\": { \"$ref\": \"#/definitions/method\" - },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": - { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"name\", \"method\", \"parameters\" ],\n - \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + \"#/definitions/concatenated_operation\" },\n { \"$ref\": \"#/definitions/coordinate_metadata\" + }\n ],\n\n \"definitions\": {\n\n \"abridged_transformation\": {\n \"type\": + \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"AbridgedTransformation\"] + },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": {\n + \ \"$ref\": \"#/definitions/crs\",\n \"$comment\": \"Only + present when the source_crs of the bound_crs does not match the source_crs + of the AbridgedTransformation. No equivalent in WKT\"\n },\n \"method\": + { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", + \"method\", \"parameters\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": false\n },\n\n \"axis\": {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": @@ -233,7 +275,10 @@ interactions: \ \"counterClockwise\",\n \"towards\",\n \ \"awayFrom\",\n \"future\",\n \ \"past\",\n \"unspecified\" - ] },\n \"unit\": { \"$ref\": \"#/definitions/unit\" },\n \"id\": + ] },\n \"meridian\": { \"$ref\": \"#/definitions/meridian\" },\n \"unit\": + { \"$ref\": \"#/definitions/unit\" },\n \"minimum_value\": { \"type\": + \"number\" },\n \"maximum_value\": { \"type\": \"number\" },\n \"range_meaning\": + { \"type\": \"string\", \"enum\": [ \"exact\", \"wraparound\"] },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", \"abbreviation\", \"direction\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" @@ -244,86 +289,103 @@ interactions: { \"type\": \"number\" }\n },\n \"required\" : [ \"east_longitude\", \"west_longitude\",\n \"south_latitude\", \"north_latitude\" ],\n \"additionalProperties\": false\n },\n\n \"bound_crs\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": - [\"BoundCRS\"] },\n \"source_crs\": { \"$ref\": \"#/definitions/crs\" - },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"transformation\": - { \"$ref\": \"#/definitions/abridged_transformation\" }\n },\n \"required\" - : [ \"source_crs\", \"target_crs\", \"transformation\" ],\n \"additionalProperties\": - false\n },\n\n \"compound_crs\": {\n \"type\": \"object\",\n \"allOf\": - [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"CompoundCRS\"] },\n - \ \"name\": { \"type\": \"string\" },\n \"components\": {\n - \ \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/crs\" + \ \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"BoundCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"source_crs\": { \"$ref\": + \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"transformation\": { \"$ref\": \"#/definitions/abridged_transformation\" + },\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"source_crs\", \"target_crs\", \"transformation\" ],\n + \ \"additionalProperties\": false\n },\n\n \"compound_crs\": {\n + \ \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"CompoundCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"components\": + \ {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/crs\" }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"components\" ],\n \"additionalProperties\": false\n },\n\n \"concatenated_operation\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"components\" ],\n + \ \"additionalProperties\": false\n },\n\n \"concatenated_operation\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"ConcatenatedOperation\"] },\n \"name\": { \"type\": \"string\" },\n \ \"source_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"steps\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/single_operation\" - }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"source_crs\", \"target_crs\", \"steps\" ],\n \"additionalProperties\": - false\n },\n\n \"conversion\": {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": - \"string\", \"enum\": [\"Conversion\"] },\n \"name\": { \"type\": \"string\" - },\n \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": - {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"source_crs\", \"target_crs\", \"steps\" + ],\n \"additionalProperties\": false\n },\n\n \"conversion\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"Conversion\"] },\n \"name\": { \"type\": \"string\" },\n \"method\": + { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", \"method\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"coordinate_system\": + }\n ],\n \"additionalProperties\": false\n },\n\n \"coordinate_metadata\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"CoordinateMetadata\"] },\n \"crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"coordinateEpoch\": { \"type\": \"number\" }\n },\n \"required\" + : [ \"crs\" ],\n \"additionalProperties\": false\n },\n\n \"coordinate_system\": {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"CoordinateSystem\"] },\n \"name\": { \"type\": \"string\" },\n \"subtype\": { \"type\": \"string\",\n \"enum\": [\"Cartesian\",\n \ \"spherical\",\n \"ellipsoidal\",\n \ \"vertical\",\n \"ordinal\",\n - \ \"parametric\",\n \"TemporalDateTime\",\n - \ \"TemporalCount\",\n \"TemporalMeasure\"] - \ },\n \"axis\": {\n \"type\": \"array\",\n \"items\": - { \"$ref\": \"#/definitions/axis\" }\n },\n \"id\": { \"$ref\": - \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"subtype\", \"axis\" ],\n \"allOf\": - [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n - \ \"additionalProperties\": false\n },\n\n \"crs\": {\n \"oneOf\": - [\n { \"$ref\": \"#/definitions/bound_crs\" },\n { \"$ref\": - \"#/definitions/compound_crs\" },\n { \"$ref\": \"#/definitions/derived_engineering_crs\" - },\n { \"$ref\": \"#/definitions/derived_geodetic_crs\" },\n { - \"$ref\": \"#/definitions/derived_parametric_crs\" },\n { \"$ref\": - \"#/definitions/derived_projected_crs\" },\n { \"$ref\": \"#/definitions/derived_temporal_crs\" - },\n { \"$ref\": \"#/definitions/derived_vertical_crs\" },\n { - \"$ref\": \"#/definitions/engineering_crs\" },\n { \"$ref\": \"#/definitions/geodetic_crs\" - },\n { \"$ref\": \"#/definitions/parametric_crs\" },\n { \"$ref\": - \"#/definitions/projected_crs\" },\n { \"$ref\": \"#/definitions/temporal_crs\" - },\n { \"$ref\": \"#/definitions/vertical_crs\" }\n ]\n },\n\n - \ \"datum\": {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/geodetic_reference_frame\" - },\n { \"$ref\": \"#/definitions/vertical_reference_frame\" },\n { - \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" },\n { - \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" },\n { - \"$ref\": \"#/definitions/temporal_datum\" },\n { \"$ref\": \"#/definitions/parametric_datum\" - },\n { \"$ref\": \"#/definitions/engineering_datum\" }\n ]\n },\n\n - \ \"datum_ensemble\": {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": - \"string\", \"enum\": [\"DatumEnsemble\"] },\n \"name\": { \"type\": - \"string\" },\n \"members\": {\n \"type\": \"array\",\n - \ \"items\": {\n \"type\": \"object\",\n \"properties\": - {\n \"name\": { \"type\": \"string\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": - \"#/definitions/ids\" }\n },\n \"required\" - : [ \"name\" ],\n \"allOf\": [\n { \"$ref\": - \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": - false\n }\n },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" + \ \"parametric\",\n \"affine\",\n + \ \"TemporalDateTime\",\n \"TemporalCount\",\n + \ \"TemporalMeasure\"] },\n \"axis\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/axis\" + }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": + { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"subtype\", + \"axis\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"crs\": + {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/bound_crs\" },\n + \ { \"$ref\": \"#/definitions/compound_crs\" },\n { \"$ref\": + \"#/definitions/derived_engineering_crs\" },\n { \"$ref\": \"#/definitions/derived_geodetic_crs\" + },\n { \"$ref\": \"#/definitions/derived_parametric_crs\" },\n { + \"$ref\": \"#/definitions/derived_projected_crs\" },\n { \"$ref\": + \"#/definitions/derived_temporal_crs\" },\n { \"$ref\": \"#/definitions/derived_vertical_crs\" + },\n { \"$ref\": \"#/definitions/engineering_crs\" },\n { \"$ref\": + \"#/definitions/geodetic_crs\" },\n { \"$ref\": \"#/definitions/parametric_crs\" + },\n { \"$ref\": \"#/definitions/projected_crs\" },\n { \"$ref\": + \"#/definitions/temporal_crs\" },\n { \"$ref\": \"#/definitions/vertical_crs\" + }\n ]\n },\n\n \"datum\": {\n \"oneOf\": [\n { \"$ref\": + \"#/definitions/geodetic_reference_frame\" },\n { \"$ref\": \"#/definitions/vertical_reference_frame\" + },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" + },\n { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" + },\n { \"$ref\": \"#/definitions/temporal_datum\" },\n { \"$ref\": + \"#/definitions/parametric_datum\" },\n { \"$ref\": \"#/definitions/engineering_datum\" + }\n ]\n },\n\n \"datum_ensemble\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"DatumEnsemble\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"members\": {\n \"type\": + \"array\",\n \"items\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"name\": { \"type\": + \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"required\" : [ \"name\" ],\n \"allOf\": [\n + \ { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ],\n \"additionalProperties\": false\n }\n + \ },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" },\n \"accuracy\": { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", \"members\", \"accuracy\" ],\n \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"derived_engineering_crs\": + }\n ],\n \"additionalProperties\": false\n },\n\n \"deformation_model\": + {\n \"description\": \"Association to a PointMotionOperation\",\n \"type\": + \"object\",\n \"properties\": {\n \"name\": { \"type\": \"string\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" }\n },\n \"required\" + : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"derived_engineering_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\",\n \ \"enum\": [\"DerivedEngineeringCRS\"] },\n \"name\": @@ -331,30 +393,32 @@ interactions: },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n - \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": - false\n },\n\n \"derived_geodetic_crs\": {\n \"type\": \"object\",\n - \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\",\n \"enum\": - [\"DerivedGeodeticCRS\",\n \"DerivedGeographicCRS\"] + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_geodetic_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedGeodeticCRS\",\n + \ \"DerivedGeographicCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": \"#/definitions/geodetic_crs\" + },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n + \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_parametric_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedParametricCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": - \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + \"#/definitions/parametric_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n - \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": - false\n },\n\n \"derived_parametric_crs\": {\n \"type\": \"object\",\n - \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\",\n \"enum\": - [\"DerivedParametricCRS\"] },\n \"name\": { \"type\": \"string\" },\n - \ \"base_crs\": { \"$ref\": \"#/definitions/parametric_crs\" },\n \"conversion\": - { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": - { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : - {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": - {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n \ \"derived_projected_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n @@ -363,19 +427,20 @@ interactions: \"#/definitions/projected_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n - \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": - false\n },\n\n \"derived_temporal_crs\": {\n \"type\": \"object\",\n - \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\",\n \"enum\": - [\"DerivedTemporalCRS\"] },\n \"name\": { \"type\": \"string\" },\n - \ \"base_crs\": { \"$ref\": \"#/definitions/temporal_crs\" },\n \"conversion\": - { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": - { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : - {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": - {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"derived_temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedTemporalCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/temporal_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n \ \"derived_vertical_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n @@ -384,67 +449,69 @@ interactions: \"#/definitions/vertical_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n - \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": - false\n },\n\n \"dynamic_geodetic_reference_frame\": {\n \"type\": - \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/geodetic_reference_frame\" - }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": - [\"DynamicGeodeticReferenceFrame\"] },\n \"name\": {},\n \"anchor\": + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"dynamic_geodetic_reference_frame\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"DynamicGeodeticReferenceFrame\"] + },\n \"name\": {},\n \"anchor\": {},\n \"anchor_epoch\": {},\n \"ellipsoid\": {},\n \"prime_meridian\": {},\n \"frame_reference_epoch\": - { \"type\": \"number\" },\n \"deformation_model\": { \"type\": \"string\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n - \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + { \"type\": \"number\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", \"ellipsoid\", \"frame_reference_epoch\" ],\n \"additionalProperties\": false\n },\n\n \"dynamic_vertical_reference_frame\": {\n \"type\": - \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/vertical_reference_frame\" + \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"DynamicVerticalReferenceFrame\"] },\n \"name\": {},\n \"anchor\": - {},\n \"frame_reference_epoch\": { \"type\": \"number\" },\n \"deformation_model\": - { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n - \ \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"frame_reference_epoch\" ],\n \"additionalProperties\": false\n },\n\n - \ \"ellipsoid\": {\n \"type\": \"object\",\n \"oneOf\":[\n {\n - \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" - },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] - },\n \"name\": { \"type\": \"string\" },\n \"semi_major_axis\": - { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"semi_minor_axis\": - { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"name\", \"semi_major_axis\", - \"semi_minor_axis\" ],\n \"additionalProperties\": false\n },\n - \ {\n \"properties\": {\n \"$schema\" : { \"type\": - \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] - },\n \"name\": { \"type\": \"string\" },\n \"semi_major_axis\": - { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"inverse_flattening\": - { \"type\": \"number\" },\n \"id\": { \"$ref\": \"#/definitions/id\" - },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n - \ \"required\" : [ \"name\", \"semi_major_axis\", \"inverse_flattening\" - ],\n \"additionalProperties\": false\n },\n {\n \"properties\": + {},\n \"anchor_epoch\": {},\n \"frame_reference_epoch\": { \"type\": + \"number\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"frame_reference_epoch\" + ],\n \"additionalProperties\": false\n },\n\n \"ellipsoid\": {\n + \ \"type\": \"object\",\n \"oneOf\":[\n {\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] },\n \"name\": - { \"type\": \"string\" },\n \"radius\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + { \"type\": \"string\" },\n \"semi_major_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + },\n \"semi_minor_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" - : [ \"name\", \"radius\" ],\n \"additionalProperties\": false\n }\n - \ ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + : [ \"name\", \"semi_major_axis\", \"semi_minor_axis\" ],\n \"additionalProperties\": + false\n },\n {\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", + \"enum\": [\"Ellipsoid\"] },\n \"name\": { \"type\": \"string\" + },\n \"semi_major_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + },\n \"inverse_flattening\": { \"type\": \"number\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"semi_major_axis\", + \"inverse_flattening\" ],\n \"additionalProperties\": false\n },\n + \ {\n \"properties\": {\n \"$schema\" : { \"type\": + \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] + },\n \"name\": { \"type\": \"string\" },\n \"radius\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"radius\" ],\n \"additionalProperties\": + false\n }\n ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n },\n\n \"engineering_crs\": {\n \"type\": \"object\",\n \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": \"#/definitions/engineering_datum\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": - {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n - \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" - : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n - \ \"engineering_datum\": {\n \"type\": \"object\",\n \"allOf\": - [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringDatum\"] - },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": - \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"datum\" ],\n \"additionalProperties\": false\n },\n\n \"engineering_datum\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"EngineeringDatum\"] },\n \"name\": { \"type\": \"string\" },\n \"anchor\": + { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"geodetic_crs\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": @@ -454,30 +521,41 @@ interactions: },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n - \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" - ],\n \"description\": \"One and only one of datum and datum_ensemble - must be provided\",\n \"allOf\": [\n { \"$ref\": \"#/definitions/object_usage\" - },\n { \"$ref\": \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"geodetic_reference_frame\": + },\n \"deformation_models\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/deformation_model\" }\n },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\" ],\n \"description\": \"One and only + one of datum and datum_ensemble must be provided\",\n \"allOf\": [\n + \ { \"$ref\": \"#/definitions/object_usage\" },\n { \"$ref\": + \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"geodetic_reference_frame\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"GeodeticReferenceFrame\"] },\n \"name\": { \"type\": \"string\" - },\n \"anchor\": { \"type\": \"string\" },\n \"ellipsoid\": - { \"$ref\": \"#/definitions/ellipsoid\" },\n \"prime_meridian\": { - \"$ref\": \"#/definitions/prime_meridian\" },\n \"$schema\" : {},\n - \ \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"name\", \"ellipsoid\" ],\n \"additionalProperties\": - false\n },\n\n \"id\": {\n \"type\": \"object\",\n \"properties\": - {\n \"authority\": { \"type\": \"string\" },\n \"code\": {\n - \ \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"integer\" } - ]\n }\n },\n \"required\" : [ \"authority\", \"code\" ],\n - \ \"additionalProperties\": false\n },\n\n \"ids\": {\n \"type\": - \"array\",\n \"items\": { \"$ref\": \"#/definitions/id\" }\n },\n\n - \ \"method\": {\n \"type\": \"object\",\n \"properties\": {\n - \ \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + },\n \"anchor\": { \"type\": \"string\" },\n \"anchor_epoch\": + { \"type\": \"number\" },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" + },\n \"prime_meridian\": { \"$ref\": \"#/definitions/prime_meridian\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"ellipsoid\" ],\n + \ \"additionalProperties\": false\n },\n\n \"geoid_model\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"name\": { + \"type\": \"string\" },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"id\": { \"$ref\": \"#/definitions/id\" }\n },\n \"required\" + : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"id\": + {\n \"type\": \"object\",\n \"properties\": {\n \"authority\": + { \"type\": \"string\" },\n \"code\": {\n \"oneOf\": [ { \"type\": + \"string\" }, { \"type\": \"integer\" } ]\n },\n \"version\": + {\n \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"number\" + } ]\n },\n \"authority_citation\": { \"type\": \"string\" },\n + \ \"uri\": { \"type\": \"string\" }\n },\n \"required\" : + [ \"authority\", \"code\" ],\n \"additionalProperties\": false\n },\n\n + \ \"ids\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/id\" + }\n },\n\n \"method\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"OperationMethod\"]},\n \"name\": { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\" @@ -490,23 +568,33 @@ interactions: ]\n }\n },\n {\n \"oneOf\": [\n { \"type\": \"object\", \"required\": [\"datum\"] },\n { \"type\": \"object\", \"required\": [\"datum_ensemble\"] }\n ]\n }\n - \ ]\n },\n\n \"object_usage\": {\n \"anyOf\": [\n {\n - \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"scope\": { \"type\": \"string\" - },\n \"area\": { \"type\": \"string\" },\n \"bbox\": - { \"$ref\": \"#/definitions/bbox\" },\n \"remarks\": { \"type\": + \ ]\n },\n\n \"meridian\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"Meridian\"] },\n \"longitude\": { \"$ref\": + \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": { + \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"longitude\" ],\n \"allOf\": [\n + \ { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"object_usage\": {\n + \ \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"scope\": + { \"type\": \"string\" },\n \"area\": { \"type\": \"string\" },\n + \ \"bbox\": { \"$ref\": \"#/definitions/bbox\" },\n \"vertical_extent\": + { \"$ref\": \"#/definitions/vertical_extent\" },\n \"temporal_extent\": + { \"$ref\": \"#/definitions/temporal_extent\" },\n \"remarks\": + { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"usages\": + { \"$ref\": \"#/definitions/usages\" },\n \"remarks\": { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"allOf\": [\n { - \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n },\n - \ {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"usages\": { \"$ref\": \"#/definitions/usages\" - },\n \"remarks\": { \"type\": \"string\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ]\n }\n ]\n },\n\n \"parameter_value\": {\n \"type\": - \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" - },\n \"type\": { \"type\": \"string\", \"enum\": [\"ParameterValue\"] - },\n \"name\": { \"type\": \"string\" },\n \"value\": {\n \"oneOf\": + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n }\n + \ ]\n },\n\n \"parameter_value\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"ParameterValue\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"value\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n { \"type\": \"number\" }\n ]\n },\n \"unit\": { \"$ref\": \"#/definitions/unit\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": @@ -519,84 +607,107 @@ interactions: { \"$ref\": \"#/definitions/parametric_datum\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": - {}\n },\n \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n \"parametric_datum\": {\n \"type\": \"object\",\n \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"ParametricDatum\"] },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" - ],\n \"additionalProperties\": false\n },\n\n \"prime_meridian\": - {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": - [\"PrimeMeridian\"] },\n \"name\": { \"type\": \"string\" },\n \"longitude\": - { \"$ref\": \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": + false\n },\n\n \"point_motion_operation\": {\n \"$comment\": \"Not + implemented in PROJ (at least as of PROJ 9.1)\",\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"PointMotionOperation\"] + },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": { + \"$ref\": \"#/definitions/crs\" },\n \"method\": { \"$ref\": \"#/definitions/method\" + },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"accuracy\": + { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": + {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"source_crs\", \"method\", \"parameters\" ],\n \"additionalProperties\": + false\n },\n\n \"prime_meridian\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"PrimeMeridian\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"longitude\": { \"$ref\": + \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": { + \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": false\n },\n\n \"single_operation\": {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/conversion\" },\n { \"$ref\": \"#/definitions/transformation\" - }\n ]\n },\n\n \"projected_crs\": {\n \"type\": \"object\",\n - \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\",\n \"enum\": - [\"ProjectedCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": - { \"$ref\": \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": - \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": - \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": - {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n - \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" - : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": - false\n },\n\n \"temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": + },\n { \"$ref\": \"#/definitions/point_motion_operation\" }\n ]\n + \ },\n\n \"projected_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"TemporalCRS\"] },\n - \ \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": - \"#/definitions/temporal_datum\" },\n \"coordinate_system\": { \"$ref\": - \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": - {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n - \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" - : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n - \ \"temporal_datum\": {\n \"type\": \"object\",\n \"allOf\": [{ + \ \"type\": { \"type\": \"string\",\n \"enum\": [\"ProjectedCRS\"] + },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": + \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n + \ \"temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": - { \"type\": \"string\", \"enum\": [\"TemporalDatum\"] },\n \"name\": - { \"type\": \"string\" },\n \"calendar\": { \"type\": \"string\" },\n - \ \"time_origin\": { \"type\": \"string\" },\n \"$schema\" : - {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": - {}\n },\n \"required\" : [ \"name\", \"calendar\" ],\n \"additionalProperties\": - false\n },\n\n \"transformation\": {\n \"type\": \"object\",\n + { \"type\": \"string\", \"enum\": [\"TemporalCRS\"] },\n \"name\": + { \"type\": \"string\" },\n \"datum\": { \"$ref\": \"#/definitions/temporal_datum\" + },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": + false\n },\n\n \"temporal_datum\": {\n \"type\": \"object\",\n \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\", \"enum\": [\"Transformation\"] - },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": { - \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" - },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" },\n - \ \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"TemporalDatum\"] + },\n \"name\": { \"type\": \"string\" },\n \"calendar\": { \"type\": + \"string\" },\n \"time_origin\": { \"type\": \"string\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"calendar\" ],\n \"additionalProperties\": + false\n },\n\n \"temporal_extent\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"start\": { \"type\": \"string\" },\n \"end\": + { \"type\": \"string\" }\n },\n \"required\" : [ \"start\", \"end\" + ],\n \"additionalProperties\": false\n },\n\n \"transformation\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"Transformation\"] },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": + { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": + \"#/definitions/crs\" },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": - {}\n },\n \"required\" : [ \"name\", \"source_crs\", \"target_crs\", - \"method\", \"parameters\" ],\n \"additionalProperties\": false\n },\n\n - \ \"unit\": {\n \"oneOf\": [\n {\n \"type\": \"string\",\n - \ \"enum\": [\"metre\", \"degree\", \"unity\"]\n },\n {\n - \ \"type\": \"object\",\n \"properties\": {\n \"type\": - { \"type\": \"string\",\n \"enum\": [\"LinearUnit\", \"AngularUnit\", - \"ScaleUnit\",\n \"TimeUnit\", \"ParametricUnit\", - \"Unit\"] },\n \"name\": { \"type\": \"string\" },\n \"conversion_factor\": - { \"type\": \"number\" },\n \"id\": { \"$ref\": \"#/definitions/id\" - },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n - \ \"required\" : [ \"type\", \"name\" ],\n \"allOf\": [\n { - \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": - false\n }\n ]\n },\n\n \"usages\": {\n \"type\": \"array\",\n - \ \"items\": {\n \"type\": \"object\",\n \"properties\": - {\n \"scope\": { \"type\": \"string\" },\n \"area\": - { \"type\": \"string\" },\n \"bbox\": { \"$ref\": \"#/definitions/bbox\" - }\n },\n \"additionalProperties\": false\n }\n },\n\n - \ \"value_and_unit\": {\n \"type\": \"object\",\n \"properties\": - {\n \"value\": { \"type\": \"number\" },\n \"unit\": { \"$ref\": - \"#/definitions/unit\" }\n },\n \"required\" : [ \"value\", \"unit\" - ],\n \"additionalProperties\": false\n },\n\n \"value_in_degree_or_value_and_unit\": + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"source_crs\", \"target_crs\", \"method\", + \"parameters\" ],\n \"additionalProperties\": false\n },\n\n \"unit\": + {\n \"oneOf\": [\n {\n \"type\": \"string\",\n \"enum\": + [\"metre\", \"degree\", \"unity\"]\n },\n {\n \"type\": \"object\",\n + \ \"properties\": {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"LinearUnit\", \"AngularUnit\", \"ScaleUnit\",\n \"TimeUnit\", + \"ParametricUnit\", \"Unit\"] },\n \"name\": { \"type\": \"string\" + },\n \"conversion_factor\": { \"type\": \"number\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"type\", \"name\" ],\n \"allOf\": + [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n + \ ],\n \"additionalProperties\": false\n }\n ]\n + \ },\n\n \"usages\": {\n \"type\": \"array\",\n \"items\": + {\n \"type\": \"object\",\n \"properties\": {\n \"scope\": + { \"type\": \"string\" },\n \"area\": { \"type\": \"string\" },\n + \ \"bbox\": { \"$ref\": \"#/definitions/bbox\" },\n \"vertical_extent\": + { \"$ref\": \"#/definitions/vertical_extent\" },\n \"temporal_extent\": + { \"$ref\": \"#/definitions/temporal_extent\" }\n },\n \"additionalProperties\": + false\n }\n },\n\n \"value_and_unit\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"value\": { \"type\": \"number\" },\n \"unit\": + { \"$ref\": \"#/definitions/unit\" }\n },\n \"required\" : [ \"value\", + \"unit\" ],\n \"additionalProperties\": false\n },\n\n \"value_in_degree_or_value_and_unit\": {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": \"#/definitions/value_and_unit\" }\n ]\n },\n\n \"value_in_metre_or_value_and_unit\": {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": @@ -608,22 +719,31 @@ interactions: \ { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"geoid_model\": {\n \"type\": \"object\",\n \"properties\": - {\n \"name\": { \"type\": \"string\" },\n \"interpolation_crs\": - { \"$ref\": \"#/definitions/crs\" },\n \"id\": { \"$ref\": \"#/definitions/id\" - }\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": - false\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\"],\n - \ \"description\": \"One and only one of datum and datum_ensemble must - be provided\",\n \"allOf\": [\n { \"$ref\": \"#/definitions/object_usage\" - },\n { \"$ref\": \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"vertical_reference_frame\": - {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" - }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": - [\"VerticalReferenceFrame\"] },\n \"name\": { \"type\": \"string\" - },\n \"anchor\": { \"type\": \"string\" },\n \"$schema\" : {},\n - \ \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": + },\n \"geoid_model\": { \"$ref\": \"#/definitions/geoid_model\" },\n + \ \"geoid_models\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/geoid_model\" }\n },\n \"deformation_models\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/deformation_model\" + }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": + {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, + \"ids\": {}\n },\n \"required\" : [ \"name\"],\n \"description\": + \"One and only one of datum and datum_ensemble must be provided\",\n \"allOf\": + [\n { \"$ref\": \"#/definitions/object_usage\" },\n { \"$ref\": + \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" },\n {\n + \ \"not\": {\n \"type\": \"object\",\n \"required\": + [ \"geoid_model\", \"geoid_models\" ]\n }\n }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"vertical_extent\": + {\n \"type\": \"object\",\n \"properties\": {\n \"minimum\": + { \"type\": \"number\" },\n \"maximum\": { \"type\": \"number\" },\n + \ \"unit\": { \"$ref\": \"#/definitions/unit\" }\n },\n \"required\" + : [ \"minimum\", \"maximum\" ],\n \"additionalProperties\": false\n },\n\n + \ \"vertical_reference_frame\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"VerticalReferenceFrame\"] + },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": + \"string\" },\n \"anchor_epoch\": { \"type\": \"number\" },\n \"$schema\" + : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \ \"required\" : [ \"name\" ],\n \"additionalProperties\": false\n \ }\n\n }\n}\n" diff --git a/tests/cassettes/test_examples/test_example_from_dict[path16].yaml b/tests/cassettes/test_examples/test_example_from_dict[path16].yaml index 6e35922c0..a0a42e384 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path16].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path16].yaml @@ -6,124 +6,71 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -135,8 +82,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json response: @@ -201,8 +146,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path17].yaml b/tests/cassettes/test_examples/test_example_from_dict[path17].yaml index 6f78bf269..92b20ee3b 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path17].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path17].yaml @@ -6,124 +6,71 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -135,47 +82,49 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n @@ -209,8 +158,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path19].yaml b/tests/cassettes/test_examples/test_example_from_dict[path19].yaml index 667795967..5c30837a3 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path19].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path19].yaml @@ -6,124 +6,71 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -135,47 +82,49 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n @@ -209,16 +158,14 @@ interactions: - close Host: - proj.org - User-Agent: - - pystac/1.14.2 method: GET - uri: https://proj.org/schemas/v0.7/projjson.schema.json + uri: https://proj.org/schemas/v0.2/projjson.schema.json response: body: string: '' headers: Location: - - https://proj.org/en/latest/schemas/v0.7/projjson.schema.json + - https://proj.org/en/latest/schemas/v0.2/projjson.schema.json status: code: 302 message: Found @@ -229,35 +176,30 @@ interactions: - close Host: - proj.org - User-Agent: - - pystac/1.14.2 method: GET - uri: https://proj.org/en/latest/schemas/v0.7/projjson.schema.json + uri: https://proj.org/en/latest/schemas/v0.2/projjson.schema.json response: body: - string: "{\n \"$id\": \"https://proj.org/schemas/v0.7/projjson.schema.json\",\n + string: "{\n \"$id\": \"https://proj.org/schemas/v0.2/projjson.schema.json\",\n \ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"description\": - \"Schema for PROJJSON (v0.7)\",\n \"$comment\": \"This document is copyright - Even Rouault and PROJ contributors, 2019-2023, and subject to the MIT license. + \"Schema for PROJJSON (v0.2.1)\",\n \"$comment\": \"This document is copyright + Even Rouault and PROJ contributors, 2019-2020, and subject to the MIT license. This file exists both in data/ and in schemas/vXXX/. Keep both in sync. And - if changing the value of $id, change PROJJSON_DEFAULT_VERSION accordingly + if changing the value of $id, change PROJJSON_CURRENT_VERSION accordingly in io.cpp\",\n\n \"oneOf\": [\n { \"$ref\": \"#/definitions/crs\" },\n \ { \"$ref\": \"#/definitions/datum\" },\n { \"$ref\": \"#/definitions/datum_ensemble\" },\n { \"$ref\": \"#/definitions/ellipsoid\" },\n { \"$ref\": \"#/definitions/prime_meridian\" },\n { \"$ref\": \"#/definitions/single_operation\" },\n { \"$ref\": - \"#/definitions/concatenated_operation\" },\n { \"$ref\": \"#/definitions/coordinate_metadata\" - }\n ],\n\n \"definitions\": {\n\n \"abridged_transformation\": {\n \"type\": - \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" - },\n \"type\": { \"type\": \"string\", \"enum\": [\"AbridgedTransformation\"] - },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": {\n - \ \"$ref\": \"#/definitions/crs\",\n \"$comment\": \"Only - present when the source_crs of the bound_crs does not match the source_crs - of the AbridgedTransformation. No equivalent in WKT\"\n },\n \"method\": - { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": - \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" - }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": - { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", - \"method\", \"parameters\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + \"#/definitions/concatenated_operation\" }\n ],\n\n \"definitions\": {\n\n + \ \"abridged_transformation\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"AbridgedTransformation\"] },\n \"name\": { + \"type\": \"string\" },\n \"method\": { \"$ref\": \"#/definitions/method\" + },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"method\", \"parameters\" ],\n + \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": false\n },\n\n \"axis\": {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": @@ -283,10 +225,7 @@ interactions: \ \"counterClockwise\",\n \"towards\",\n \ \"awayFrom\",\n \"future\",\n \ \"past\",\n \"unspecified\" - ] },\n \"meridian\": { \"$ref\": \"#/definitions/meridian\" },\n \"unit\": - { \"$ref\": \"#/definitions/unit\" },\n \"minimum_value\": { \"type\": - \"number\" },\n \"maximum_value\": { \"type\": \"number\" },\n \"range_meaning\": - { \"type\": \"string\", \"enum\": [ \"exact\", \"wraparound\"] },\n \"id\": + ] },\n \"unit\": { \"$ref\": \"#/definitions/unit\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", \"abbreviation\", \"direction\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" @@ -297,103 +236,86 @@ interactions: { \"type\": \"number\" }\n },\n \"required\" : [ \"east_longitude\", \"west_longitude\",\n \"south_latitude\", \"north_latitude\" ],\n \"additionalProperties\": false\n },\n\n \"bound_crs\": {\n - \ \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" - }],\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" - },\n \"type\": { \"type\": \"string\", \"enum\": [\"BoundCRS\"] },\n - \ \"name\": { \"type\": \"string\" },\n \"source_crs\": { \"$ref\": - \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" - },\n \"transformation\": { \"$ref\": \"#/definitions/abridged_transformation\" - },\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"source_crs\", \"target_crs\", \"transformation\" ],\n - \ \"additionalProperties\": false\n },\n\n \"compound_crs\": {\n - \ \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" - }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": - [\"CompoundCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"components\": - \ {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/crs\" + \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"BoundCRS\"] },\n \"source_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"transformation\": + { \"$ref\": \"#/definitions/abridged_transformation\" }\n },\n \"required\" + : [ \"source_crs\", \"target_crs\", \"transformation\" ],\n \"additionalProperties\": + false\n },\n\n \"compound_crs\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"CompoundCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"components\": {\n + \ \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/crs\" }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"components\" ],\n - \ \"additionalProperties\": false\n },\n\n \"concatenated_operation\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"components\" ],\n \"additionalProperties\": false\n },\n\n \"concatenated_operation\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"ConcatenatedOperation\"] },\n \"name\": { \"type\": \"string\" },\n \ \"source_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" },\n \"steps\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/single_operation\" - }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" - : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"name\", \"source_crs\", \"target_crs\", \"steps\" - ],\n \"additionalProperties\": false\n },\n\n \"conversion\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": - [\"Conversion\"] },\n \"name\": { \"type\": \"string\" },\n \"method\": - { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": - \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" + }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"source_crs\", \"target_crs\", \"steps\" ],\n \"additionalProperties\": + false\n },\n\n \"conversion\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"Conversion\"] },\n \"name\": { \"type\": \"string\" + },\n \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": + {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", \"method\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"coordinate_metadata\": - {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": - [\"CoordinateMetadata\"] },\n \"crs\": { \"$ref\": \"#/definitions/crs\" - },\n \"coordinateEpoch\": { \"type\": \"number\" }\n },\n \"required\" - : [ \"crs\" ],\n \"additionalProperties\": false\n },\n\n \"coordinate_system\": + }\n ],\n \"additionalProperties\": false\n },\n\n \"coordinate_system\": {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"CoordinateSystem\"] },\n \"name\": { \"type\": \"string\" },\n \"subtype\": { \"type\": \"string\",\n \"enum\": [\"Cartesian\",\n \ \"spherical\",\n \"ellipsoidal\",\n \ \"vertical\",\n \"ordinal\",\n - \ \"parametric\",\n \"affine\",\n - \ \"TemporalDateTime\",\n \"TemporalCount\",\n - \ \"TemporalMeasure\"] },\n \"axis\": - {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/axis\" - }\n },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": - { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"subtype\", - \"axis\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"crs\": - {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/bound_crs\" },\n - \ { \"$ref\": \"#/definitions/compound_crs\" },\n { \"$ref\": - \"#/definitions/derived_engineering_crs\" },\n { \"$ref\": \"#/definitions/derived_geodetic_crs\" - },\n { \"$ref\": \"#/definitions/derived_parametric_crs\" },\n { - \"$ref\": \"#/definitions/derived_projected_crs\" },\n { \"$ref\": - \"#/definitions/derived_temporal_crs\" },\n { \"$ref\": \"#/definitions/derived_vertical_crs\" - },\n { \"$ref\": \"#/definitions/engineering_crs\" },\n { \"$ref\": - \"#/definitions/geodetic_crs\" },\n { \"$ref\": \"#/definitions/parametric_crs\" - },\n { \"$ref\": \"#/definitions/projected_crs\" },\n { \"$ref\": - \"#/definitions/temporal_crs\" },\n { \"$ref\": \"#/definitions/vertical_crs\" - }\n ]\n },\n\n \"datum\": {\n \"oneOf\": [\n { \"$ref\": - \"#/definitions/geodetic_reference_frame\" },\n { \"$ref\": \"#/definitions/vertical_reference_frame\" - },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" - },\n { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" - },\n { \"$ref\": \"#/definitions/temporal_datum\" },\n { \"$ref\": - \"#/definitions/parametric_datum\" },\n { \"$ref\": \"#/definitions/engineering_datum\" - }\n ]\n },\n\n \"datum_ensemble\": {\n \"type\": \"object\",\n - \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"DatumEnsemble\"] },\n - \ \"name\": { \"type\": \"string\" },\n \"members\": {\n \"type\": - \"array\",\n \"items\": {\n \"type\": \"object\",\n - \ \"properties\": {\n \"name\": { \"type\": - \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" - },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n - \ \"required\" : [ \"name\" ],\n \"allOf\": [\n - \ { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ],\n \"additionalProperties\": false\n }\n - \ },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" + \ \"parametric\",\n \"TemporalDateTime\",\n + \ \"TemporalCount\",\n \"TemporalMeasure\"] + \ },\n \"axis\": {\n \"type\": \"array\",\n \"items\": + { \"$ref\": \"#/definitions/axis\" }\n },\n \"id\": { \"$ref\": + \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"subtype\", \"axis\" ],\n \"allOf\": + [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n + \ \"additionalProperties\": false\n },\n\n \"crs\": {\n \"oneOf\": + [\n { \"$ref\": \"#/definitions/bound_crs\" },\n { \"$ref\": + \"#/definitions/compound_crs\" },\n { \"$ref\": \"#/definitions/derived_engineering_crs\" + },\n { \"$ref\": \"#/definitions/derived_geodetic_crs\" },\n { + \"$ref\": \"#/definitions/derived_parametric_crs\" },\n { \"$ref\": + \"#/definitions/derived_projected_crs\" },\n { \"$ref\": \"#/definitions/derived_temporal_crs\" + },\n { \"$ref\": \"#/definitions/derived_vertical_crs\" },\n { + \"$ref\": \"#/definitions/engineering_crs\" },\n { \"$ref\": \"#/definitions/geodetic_crs\" + },\n { \"$ref\": \"#/definitions/parametric_crs\" },\n { \"$ref\": + \"#/definitions/projected_crs\" },\n { \"$ref\": \"#/definitions/temporal_crs\" + },\n { \"$ref\": \"#/definitions/vertical_crs\" }\n ]\n },\n\n + \ \"datum\": {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/geodetic_reference_frame\" + },\n { \"$ref\": \"#/definitions/vertical_reference_frame\" },\n { + \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" },\n { + \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" },\n { + \"$ref\": \"#/definitions/temporal_datum\" },\n { \"$ref\": \"#/definitions/parametric_datum\" + },\n { \"$ref\": \"#/definitions/engineering_datum\" }\n ]\n },\n\n + \ \"datum_ensemble\": {\n \"type\": \"object\",\n \"properties\": + {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + \"string\", \"enum\": [\"DatumEnsemble\"] },\n \"name\": { \"type\": + \"string\" },\n \"members\": {\n \"type\": \"array\",\n + \ \"items\": {\n \"type\": \"object\",\n \"properties\": + {\n \"name\": { \"type\": \"string\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": + \"#/definitions/ids\" }\n },\n \"required\" + : [ \"name\" ],\n \"allOf\": [\n { \"$ref\": + \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": + false\n }\n },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" },\n \"accuracy\": { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\", \"members\", \"accuracy\" ],\n \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ],\n \"additionalProperties\": false\n },\n\n \"deformation_model\": - {\n \"description\": \"Association to a PointMotionOperation\",\n \"type\": - \"object\",\n \"properties\": {\n \"name\": { \"type\": \"string\" - },\n \"id\": { \"$ref\": \"#/definitions/id\" }\n },\n \"required\" - : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"derived_engineering_crs\": + }\n ],\n \"additionalProperties\": false\n },\n\n \"derived_engineering_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\",\n \ \"enum\": [\"DerivedEngineeringCRS\"] },\n \"name\": @@ -401,32 +323,30 @@ interactions: },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", - \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n - \ \"derived_geodetic_crs\": {\n \"type\": \"object\",\n \"allOf\": - [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedGeodeticCRS\",\n - \ \"DerivedGeographicCRS\"] },\n \"name\": - { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": \"#/definitions/geodetic_crs\" - },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n - \ \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", - \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n - \ \"derived_parametric_crs\": {\n \"type\": \"object\",\n \"allOf\": - [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedParametricCRS\"] + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"derived_geodetic_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"DerivedGeodeticCRS\",\n \"DerivedGeographicCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": - \"#/definitions/parametric_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" + \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"derived_parametric_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"DerivedParametricCRS\"] },\n \"name\": { \"type\": \"string\" },\n + \ \"base_crs\": { \"$ref\": \"#/definitions/parametric_crs\" },\n \"conversion\": + { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": + { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n \ \"derived_projected_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n @@ -435,20 +355,19 @@ interactions: \"#/definitions/projected_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", - \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n - \ \"derived_temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": - [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\",\n \"enum\": [\"DerivedTemporalCRS\"] - },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": - \"#/definitions/temporal_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" - },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"derived_temporal_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"DerivedTemporalCRS\"] },\n \"name\": { \"type\": \"string\" },\n + \ \"base_crs\": { \"$ref\": \"#/definitions/temporal_crs\" },\n \"conversion\": + { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": + { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n \ \"derived_vertical_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n @@ -457,69 +376,67 @@ interactions: \"#/definitions/vertical_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", - \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n - \ \"dynamic_geodetic_reference_frame\": {\n \"type\": \"object\",\n - \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\", \"enum\": [\"DynamicGeodeticReferenceFrame\"] - },\n \"name\": {},\n \"anchor\": {},\n \"anchor_epoch\": + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"dynamic_geodetic_reference_frame\": {\n \"type\": + \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/geodetic_reference_frame\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"DynamicGeodeticReferenceFrame\"] },\n \"name\": {},\n \"anchor\": {},\n \"ellipsoid\": {},\n \"prime_meridian\": {},\n \"frame_reference_epoch\": - { \"type\": \"number\" },\n \"$schema\" : {},\n \"scope\": {},\n - \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": - {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + { \"type\": \"number\" },\n \"deformation_model\": { \"type\": \"string\" + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", \"ellipsoid\", \"frame_reference_epoch\" ],\n \"additionalProperties\": false\n },\n\n \"dynamic_vertical_reference_frame\": {\n \"type\": - \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/vertical_reference_frame\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"DynamicVerticalReferenceFrame\"] },\n \"name\": {},\n \"anchor\": - {},\n \"anchor_epoch\": {},\n \"frame_reference_epoch\": { \"type\": - \"number\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"frame_reference_epoch\" - ],\n \"additionalProperties\": false\n },\n\n \"ellipsoid\": {\n - \ \"type\": \"object\",\n \"oneOf\":[\n {\n \"properties\": + {},\n \"frame_reference_epoch\": { \"type\": \"number\" },\n \"deformation_model\": + { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n + \ \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", + \"frame_reference_epoch\" ],\n \"additionalProperties\": false\n },\n\n + \ \"ellipsoid\": {\n \"type\": \"object\",\n \"oneOf\":[\n {\n + \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] + },\n \"name\": { \"type\": \"string\" },\n \"semi_major_axis\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"semi_minor_axis\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"required\" : [ \"name\", \"semi_major_axis\", + \"semi_minor_axis\" ],\n \"additionalProperties\": false\n },\n + \ {\n \"properties\": {\n \"$schema\" : { \"type\": + \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] + },\n \"name\": { \"type\": \"string\" },\n \"semi_major_axis\": + { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"inverse_flattening\": + { \"type\": \"number\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"required\" : [ \"name\", \"semi_major_axis\", \"inverse_flattening\" + ],\n \"additionalProperties\": false\n },\n {\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] },\n \"name\": - { \"type\": \"string\" },\n \"semi_major_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" - },\n \"semi_minor_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" + { \"type\": \"string\" },\n \"radius\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" - : [ \"name\", \"semi_major_axis\", \"semi_minor_axis\" ],\n \"additionalProperties\": - false\n },\n {\n \"properties\": {\n \"$schema\" - : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", - \"enum\": [\"Ellipsoid\"] },\n \"name\": { \"type\": \"string\" - },\n \"semi_major_axis\": { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" - },\n \"inverse_flattening\": { \"type\": \"number\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"name\", \"semi_major_axis\", - \"inverse_flattening\" ],\n \"additionalProperties\": false\n },\n - \ {\n \"properties\": {\n \"$schema\" : { \"type\": - \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"Ellipsoid\"] - },\n \"name\": { \"type\": \"string\" },\n \"radius\": - { \"$ref\": \"#/definitions/value_in_metre_or_value_and_unit\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"name\", \"radius\" ],\n \"additionalProperties\": - false\n }\n ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + : [ \"name\", \"radius\" ],\n \"additionalProperties\": false\n }\n + \ ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n },\n\n \"engineering_crs\": {\n \"type\": \"object\",\n \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": \"#/definitions/engineering_datum\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": - {},\n \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": - {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"datum\" ],\n \"additionalProperties\": false\n },\n\n \"engineering_datum\": - {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" - }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": - [\"EngineeringDatum\"] },\n \"name\": { \"type\": \"string\" },\n \"anchor\": - { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n - \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": - {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n + \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" + : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n + \ \"engineering_datum\": {\n \"type\": \"object\",\n \"allOf\": + [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n + \ \"type\": { \"type\": \"string\", \"enum\": [\"EngineeringDatum\"] + },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": + \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"geodetic_crs\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": @@ -529,41 +446,30 @@ interactions: },\n { \"$ref\": \"#/definitions/dynamic_geodetic_reference_frame\" }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"deformation_models\": {\n \"type\": \"array\",\n \"items\": - { \"$ref\": \"#/definitions/deformation_model\" }\n },\n \"$schema\" - : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"name\" ],\n \"description\": \"One and only - one of datum and datum_ensemble must be provided\",\n \"allOf\": [\n - \ { \"$ref\": \"#/definitions/object_usage\" },\n { \"$ref\": - \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" }\n ],\n - \ \"additionalProperties\": false\n },\n\n \"geodetic_reference_frame\": + },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n + \ \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n + \ \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" + ],\n \"description\": \"One and only one of datum and datum_ensemble + must be provided\",\n \"allOf\": [\n { \"$ref\": \"#/definitions/object_usage\" + },\n { \"$ref\": \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"geodetic_reference_frame\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"GeodeticReferenceFrame\"] },\n \"name\": { \"type\": \"string\" - },\n \"anchor\": { \"type\": \"string\" },\n \"anchor_epoch\": - { \"type\": \"number\" },\n \"ellipsoid\": { \"$ref\": \"#/definitions/ellipsoid\" - },\n \"prime_meridian\": { \"$ref\": \"#/definitions/prime_meridian\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"ellipsoid\" ],\n - \ \"additionalProperties\": false\n },\n\n \"geoid_model\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"name\": { - \"type\": \"string\" },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" - },\n \"id\": { \"$ref\": \"#/definitions/id\" }\n },\n \"required\" - : [ \"name\" ],\n \"additionalProperties\": false\n },\n\n \"id\": - {\n \"type\": \"object\",\n \"properties\": {\n \"authority\": - { \"type\": \"string\" },\n \"code\": {\n \"oneOf\": [ { \"type\": - \"string\" }, { \"type\": \"integer\" } ]\n },\n \"version\": - {\n \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"number\" - } ]\n },\n \"authority_citation\": { \"type\": \"string\" },\n - \ \"uri\": { \"type\": \"string\" }\n },\n \"required\" : - [ \"authority\", \"code\" ],\n \"additionalProperties\": false\n },\n\n - \ \"ids\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/id\" - }\n },\n\n \"method\": {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": + },\n \"anchor\": { \"type\": \"string\" },\n \"ellipsoid\": + { \"$ref\": \"#/definitions/ellipsoid\" },\n \"prime_meridian\": { + \"$ref\": \"#/definitions/prime_meridian\" },\n \"$schema\" : {},\n + \ \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": + {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n + \ \"required\" : [ \"name\", \"ellipsoid\" ],\n \"additionalProperties\": + false\n },\n\n \"id\": {\n \"type\": \"object\",\n \"properties\": + {\n \"authority\": { \"type\": \"string\" },\n \"code\": {\n + \ \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"integer\" } + ]\n }\n },\n \"required\" : [ \"authority\", \"code\" ],\n + \ \"additionalProperties\": false\n },\n\n \"ids\": {\n \"type\": + \"array\",\n \"items\": { \"$ref\": \"#/definitions/id\" }\n },\n\n + \ \"method\": {\n \"type\": \"object\",\n \"properties\": {\n + \ \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": [\"OperationMethod\"]},\n \"name\": { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\" @@ -576,33 +482,23 @@ interactions: ]\n }\n },\n {\n \"oneOf\": [\n { \"type\": \"object\", \"required\": [\"datum\"] },\n { \"type\": \"object\", \"required\": [\"datum_ensemble\"] }\n ]\n }\n - \ ]\n },\n\n \"meridian\": {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"type\": { \"type\": - \"string\", \"enum\": [\"Meridian\"] },\n \"longitude\": { \"$ref\": - \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": { - \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"longitude\" ],\n \"allOf\": [\n - \ { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n - \ \"additionalProperties\": false\n },\n\n \"object_usage\": {\n - \ \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"scope\": - { \"type\": \"string\" },\n \"area\": { \"type\": \"string\" },\n - \ \"bbox\": { \"$ref\": \"#/definitions/bbox\" },\n \"vertical_extent\": - { \"$ref\": \"#/definitions/vertical_extent\" },\n \"temporal_extent\": - { \"$ref\": \"#/definitions/temporal_extent\" },\n \"remarks\": - { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" - },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n - \ \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" - }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": - {\n \"$schema\" : { \"type\": \"string\" },\n \"usages\": - { \"$ref\": \"#/definitions/usages\" },\n \"remarks\": { \"type\": + \ ]\n },\n\n \"object_usage\": {\n \"anyOf\": [\n {\n + \ \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"scope\": { \"type\": \"string\" + },\n \"area\": { \"type\": \"string\" },\n \"bbox\": + { \"$ref\": \"#/definitions/bbox\" },\n \"remarks\": { \"type\": \"string\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"allOf\": [\n { - \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n }\n - \ ]\n },\n\n \"parameter_value\": {\n \"type\": \"object\",\n - \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"ParameterValue\"] },\n - \ \"name\": { \"type\": \"string\" },\n \"value\": {\n \"oneOf\": + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ]\n },\n + \ {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"usages\": { \"$ref\": \"#/definitions/usages\" + },\n \"remarks\": { \"type\": \"string\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + }\n },\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" + }\n ]\n }\n ]\n },\n\n \"parameter_value\": {\n \"type\": + \"object\",\n \"properties\": {\n \"$schema\" : { \"type\": \"string\" + },\n \"type\": { \"type\": \"string\", \"enum\": [\"ParameterValue\"] + },\n \"name\": { \"type\": \"string\" },\n \"value\": {\n \"oneOf\": [\n { \"type\": \"string\" },\n { \"type\": \"number\" }\n ]\n },\n \"unit\": { \"$ref\": \"#/definitions/unit\" },\n \"id\": { \"$ref\": \"#/definitions/id\" },\n \"ids\": @@ -615,107 +511,84 @@ interactions: { \"$ref\": \"#/definitions/parametric_datum\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n \"parametric_datum\": {\n \"type\": \"object\",\n \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": [\"ParametricDatum\"] },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": - false\n },\n\n \"point_motion_operation\": {\n \"$comment\": \"Not - implemented in PROJ (at least as of PROJ 9.1)\",\n \"type\": \"object\",\n - \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\", \"enum\": [\"PointMotionOperation\"] - },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": { - \"$ref\": \"#/definitions/crs\" },\n \"method\": { \"$ref\": \"#/definitions/method\" - },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": - { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"accuracy\": - { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n - \ \"area\": {},\n \"bbox\": {},\n \"vertical_extent\": - {},\n \"temporal_extent\": {},\n \"usages\": {},\n \"remarks\": - {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\", - \"source_crs\", \"method\", \"parameters\" ],\n \"additionalProperties\": - false\n },\n\n \"prime_meridian\": {\n \"type\": \"object\",\n - \ \"properties\": {\n \"$schema\" : { \"type\": \"string\" },\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"PrimeMeridian\"] },\n - \ \"name\": { \"type\": \"string\" },\n \"longitude\": { \"$ref\": - \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": { - \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\" + ],\n \"additionalProperties\": false\n },\n\n \"prime_meridian\": + {\n \"type\": \"object\",\n \"properties\": {\n \"$schema\" + : { \"type\": \"string\" },\n \"type\": { \"type\": \"string\", \"enum\": + [\"PrimeMeridian\"] },\n \"name\": { \"type\": \"string\" },\n \"longitude\": + { \"$ref\": \"#/definitions/value_in_degree_or_value_and_unit\" },\n \"id\": + { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n \"required\" : [ \"name\" ],\n \"allOf\": [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": false\n },\n\n \"single_operation\": {\n \"oneOf\": [\n { \"$ref\": \"#/definitions/conversion\" },\n { \"$ref\": \"#/definitions/transformation\" - },\n { \"$ref\": \"#/definitions/point_motion_operation\" }\n ]\n - \ },\n\n \"projected_crs\": {\n \"type\": \"object\",\n \"allOf\": + }\n ]\n },\n\n \"projected_crs\": {\n \"type\": \"object\",\n + \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": + {\n \"type\": { \"type\": \"string\",\n \"enum\": + [\"ProjectedCRS\"] },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": + { \"$ref\": \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": + \"#/definitions/conversion\" },\n \"coordinate_system\": { \"$ref\": + \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": + {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n + \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" + : [ \"name\", \"base_crs\", \"conversion\", \"coordinate_system\" ],\n \"additionalProperties\": + false\n },\n\n \"temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\",\n \"enum\": [\"ProjectedCRS\"] - },\n \"name\": { \"type\": \"string\" },\n \"base_crs\": { \"$ref\": - \"#/definitions/geodetic_crs\" },\n \"conversion\": { \"$ref\": \"#/definitions/conversion\" - },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"base_crs\", \"conversion\", - \"coordinate_system\" ],\n \"additionalProperties\": false\n },\n\n - \ \"temporal_crs\": {\n \"type\": \"object\",\n \"allOf\": [{ + \ \"type\": { \"type\": \"string\", \"enum\": [\"TemporalCRS\"] },\n + \ \"name\": { \"type\": \"string\" },\n \"datum\": { \"$ref\": + \"#/definitions/temporal_datum\" },\n \"coordinate_system\": { \"$ref\": + \"#/definitions/coordinate_system\" },\n \"$schema\" : {},\n \"scope\": + {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n + \ \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" + : [ \"name\", \"datum\" ],\n \"additionalProperties\": false\n },\n\n + \ \"temporal_datum\": {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n \"type\": - { \"type\": \"string\", \"enum\": [\"TemporalCRS\"] },\n \"name\": - { \"type\": \"string\" },\n \"datum\": { \"$ref\": \"#/definitions/temporal_datum\" - },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n - \ \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\", \"datum\" ],\n \"additionalProperties\": - false\n },\n\n \"temporal_datum\": {\n \"type\": \"object\",\n + { \"type\": \"string\", \"enum\": [\"TemporalDatum\"] },\n \"name\": + { \"type\": \"string\" },\n \"calendar\": { \"type\": \"string\" },\n + \ \"time_origin\": { \"type\": \"string\" },\n \"$schema\" : + {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"calendar\" ],\n \"additionalProperties\": + false\n },\n\n \"transformation\": {\n \"type\": \"object\",\n \ \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": - {\n \"type\": { \"type\": \"string\", \"enum\": [\"TemporalDatum\"] - },\n \"name\": { \"type\": \"string\" },\n \"calendar\": { \"type\": - \"string\" },\n \"time_origin\": { \"type\": \"string\" },\n \"$schema\" - : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"name\", \"calendar\" ],\n \"additionalProperties\": - false\n },\n\n \"temporal_extent\": {\n \"type\": \"object\",\n - \ \"properties\": {\n \"start\": { \"type\": \"string\" },\n \"end\": - { \"type\": \"string\" }\n },\n \"required\" : [ \"start\", \"end\" - ],\n \"additionalProperties\": false\n },\n\n \"transformation\": - {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" - }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": - [\"Transformation\"] },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": - { \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": - \"#/definitions/crs\" },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" - },\n \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": + {\n \"type\": { \"type\": \"string\", \"enum\": [\"Transformation\"] + },\n \"name\": { \"type\": \"string\" },\n \"source_crs\": { + \"$ref\": \"#/definitions/crs\" },\n \"target_crs\": { \"$ref\": \"#/definitions/crs\" + },\n \"interpolation_crs\": { \"$ref\": \"#/definitions/crs\" },\n + \ \"method\": { \"$ref\": \"#/definitions/method\" },\n \"parameters\": {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/parameter_value\" }\n },\n \"accuracy\": { \"type\": \"string\" },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": - {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n - \ \"required\" : [ \"name\", \"source_crs\", \"target_crs\", \"method\", - \"parameters\" ],\n \"additionalProperties\": false\n },\n\n \"unit\": - {\n \"oneOf\": [\n {\n \"type\": \"string\",\n \"enum\": - [\"metre\", \"degree\", \"unity\"]\n },\n {\n \"type\": \"object\",\n - \ \"properties\": {\n \"type\": { \"type\": \"string\",\n \"enum\": - [\"LinearUnit\", \"AngularUnit\", \"ScaleUnit\",\n \"TimeUnit\", - \"ParametricUnit\", \"Unit\"] },\n \"name\": { \"type\": \"string\" - },\n \"conversion_factor\": { \"type\": \"number\" },\n \"id\": - { \"$ref\": \"#/definitions/id\" },\n \"ids\": { \"$ref\": \"#/definitions/ids\" - }\n },\n \"required\" : [ \"type\", \"name\" ],\n \"allOf\": - [\n { \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n - \ ],\n \"additionalProperties\": false\n }\n ]\n - \ },\n\n \"usages\": {\n \"type\": \"array\",\n \"items\": - {\n \"type\": \"object\",\n \"properties\": {\n \"scope\": - { \"type\": \"string\" },\n \"area\": { \"type\": \"string\" },\n - \ \"bbox\": { \"$ref\": \"#/definitions/bbox\" },\n \"vertical_extent\": - { \"$ref\": \"#/definitions/vertical_extent\" },\n \"temporal_extent\": - { \"$ref\": \"#/definitions/temporal_extent\" }\n },\n \"additionalProperties\": - false\n }\n },\n\n \"value_and_unit\": {\n \"type\": \"object\",\n - \ \"properties\": {\n \"value\": { \"type\": \"number\" },\n \"unit\": - { \"$ref\": \"#/definitions/unit\" }\n },\n \"required\" : [ \"value\", - \"unit\" ],\n \"additionalProperties\": false\n },\n\n \"value_in_degree_or_value_and_unit\": + \ \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": + {}\n },\n \"required\" : [ \"name\", \"source_crs\", \"target_crs\", + \"method\", \"parameters\" ],\n \"additionalProperties\": false\n },\n\n + \ \"unit\": {\n \"oneOf\": [\n {\n \"type\": \"string\",\n + \ \"enum\": [\"metre\", \"degree\", \"unity\"]\n },\n {\n + \ \"type\": \"object\",\n \"properties\": {\n \"type\": + { \"type\": \"string\",\n \"enum\": [\"LinearUnit\", \"AngularUnit\", + \"ScaleUnit\",\n \"TimeUnit\", \"ParametricUnit\", + \"Unit\"] },\n \"name\": { \"type\": \"string\" },\n \"conversion_factor\": + { \"type\": \"number\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + },\n \"ids\": { \"$ref\": \"#/definitions/ids\" }\n },\n + \ \"required\" : [ \"type\", \"name\" ],\n \"allOf\": [\n { + \"$ref\": \"#/definitions/id_ids_mutually_exclusive\" }\n ],\n \"additionalProperties\": + false\n }\n ]\n },\n\n \"usages\": {\n \"type\": \"array\",\n + \ \"items\": {\n \"type\": \"object\",\n \"properties\": + {\n \"scope\": { \"type\": \"string\" },\n \"area\": + { \"type\": \"string\" },\n \"bbox\": { \"$ref\": \"#/definitions/bbox\" + }\n },\n \"additionalProperties\": false\n }\n },\n\n + \ \"value_and_unit\": {\n \"type\": \"object\",\n \"properties\": + {\n \"value\": { \"type\": \"number\" },\n \"unit\": { \"$ref\": + \"#/definitions/unit\" }\n },\n \"required\" : [ \"value\", \"unit\" + ],\n \"additionalProperties\": false\n },\n\n \"value_in_degree_or_value_and_unit\": {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": \"#/definitions/value_and_unit\" }\n ]\n },\n\n \"value_in_metre_or_value_and_unit\": {\n \"oneOf\": [\n { \"type\": \"number\" },\n { \"$ref\": @@ -727,31 +600,22 @@ interactions: \ { \"$ref\": \"#/definitions/dynamic_vertical_reference_frame\" }\n ]\n },\n \"datum_ensemble\": { \"$ref\": \"#/definitions/datum_ensemble\" },\n \"coordinate_system\": { \"$ref\": \"#/definitions/coordinate_system\" - },\n \"geoid_model\": { \"$ref\": \"#/definitions/geoid_model\" },\n - \ \"geoid_models\": {\n \"type\": \"array\",\n \"items\": - { \"$ref\": \"#/definitions/geoid_model\" }\n },\n \"deformation_models\": - {\n \"type\": \"array\",\n \"items\": { \"$ref\": \"#/definitions/deformation_model\" - }\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": - {},\n \"bbox\": {},\n \"vertical_extent\": {},\n \"temporal_extent\": - {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, - \"ids\": {}\n },\n \"required\" : [ \"name\"],\n \"description\": - \"One and only one of datum and datum_ensemble must be provided\",\n \"allOf\": - [\n { \"$ref\": \"#/definitions/object_usage\" },\n { \"$ref\": - \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" },\n {\n - \ \"not\": {\n \"type\": \"object\",\n \"required\": - [ \"geoid_model\", \"geoid_models\" ]\n }\n }\n ],\n - \ \"additionalProperties\": false\n },\n\n \"vertical_extent\": - {\n \"type\": \"object\",\n \"properties\": {\n \"minimum\": - { \"type\": \"number\" },\n \"maximum\": { \"type\": \"number\" },\n - \ \"unit\": { \"$ref\": \"#/definitions/unit\" }\n },\n \"required\" - : [ \"minimum\", \"maximum\" ],\n \"additionalProperties\": false\n },\n\n - \ \"vertical_reference_frame\": {\n \"type\": \"object\",\n \"allOf\": - [{ \"$ref\": \"#/definitions/object_usage\" }],\n \"properties\": {\n - \ \"type\": { \"type\": \"string\", \"enum\": [\"VerticalReferenceFrame\"] - },\n \"name\": { \"type\": \"string\" },\n \"anchor\": { \"type\": - \"string\" },\n \"anchor_epoch\": { \"type\": \"number\" },\n \"$schema\" - : {},\n \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n - \ \"vertical_extent\": {},\n \"temporal_extent\": {},\n \"usages\": + },\n \"geoid_model\": {\n \"type\": \"object\",\n \"properties\": + {\n \"name\": { \"type\": \"string\" },\n \"interpolation_crs\": + { \"$ref\": \"#/definitions/crs\" },\n \"id\": { \"$ref\": \"#/definitions/id\" + }\n },\n \"required\" : [ \"name\" ],\n \"additionalProperties\": + false\n },\n \"$schema\" : {},\n \"scope\": {},\n \"area\": + {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": + {},\n \"id\": {}, \"ids\": {}\n },\n \"required\" : [ \"name\"],\n + \ \"description\": \"One and only one of datum and datum_ensemble must + be provided\",\n \"allOf\": [\n { \"$ref\": \"#/definitions/object_usage\" + },\n { \"$ref\": \"#/definitions/one_and_only_one_of_datum_or_datum_ensemble\" + }\n ],\n \"additionalProperties\": false\n },\n\n \"vertical_reference_frame\": + {\n \"type\": \"object\",\n \"allOf\": [{ \"$ref\": \"#/definitions/object_usage\" + }],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"enum\": + [\"VerticalReferenceFrame\"] },\n \"name\": { \"type\": \"string\" + },\n \"anchor\": { \"type\": \"string\" },\n \"$schema\" : {},\n + \ \"scope\": {},\n \"area\": {},\n \"bbox\": {},\n \"usages\": {},\n \"remarks\": {},\n \"id\": {}, \"ids\": {}\n },\n \ \"required\" : [ \"name\" ],\n \"additionalProperties\": false\n \ }\n\n }\n}\n" diff --git a/tests/cassettes/test_examples/test_example_from_dict[path1].yaml b/tests/cassettes/test_examples/test_example_from_dict[path1].yaml new file mode 100644 index 000000000..c6a6f459c --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path1].yaml @@ -0,0 +1,64 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path3].yaml b/tests/cassettes/test_examples/test_example_from_dict[path3].yaml deleted file mode 100644 index 9f12e87bf..000000000 --- a/tests/cassettes/test_examples/test_example_from_dict[path3].yaml +++ /dev/null @@ -1,385 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": - \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation - Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items. Remove this object - if this extension only applies to Collections.\",\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"$comment\": \"This validates the fields in Item Assets, - but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n },\n {\n \"$comment\": \"This - is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": - [\n {\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n {\n \"$ref\": - \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n - \ {\n \"$comment\": \"This is the schema for the top-level - fields in a Collection. Remove this if this extension does not define top-level - fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": - \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": - \"#/definitions/fields\"\n }\n ]\n },\n {\n - \ \"$comment\": \"This validates the fields in Collection Assets, - but does not require them.\",\n \"required\": [\n \"assets\"\n - \ ],\n \"properties\": {\n \"assets\": {\n \"type\": - \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Item Asset Definitions. It doesn't - require any fields.\",\n \"required\": [\n \"item_assets\"\n - \ ],\n \"properties\": {\n \"item_assets\": {\n - \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Summaries. By default, only checks - the existance of the properties, but not the schema of the summaries.\",\n - \ \"required\": [\n \"summaries\"\n ],\n \"properties\": - {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n - \ }\n }\n }\n ]\n }\n ],\n \"definitions\": - {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": - [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": - {\n \"type\": \"array\",\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n - \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": - \"Please list all fields here so that we can force the existance of one of - them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": - [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": - [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": - {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": - \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": - {\n \"type\": \"string\", \n \"title\": \"Proposed Data - Citation\"\n },\n \"sci:publications\": {\n \"type\": - \"array\",\n \"title\": \"Publications\",\n \"items\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"doi\": - {\n \"type\": \"string\",\n \"title\": \"Publication - DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n - \ }, \n \"citation\": { \n \"type\": - \"string\", \n \"title\": \"Publication Citation\"\n }\n - \ }\n }\n }\n },\n \"patternProperties\": - {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n - \ }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/view/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": - \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension - for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": - [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n - \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": - [\"view:sun_elevation\"]}\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": - {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": - {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": - {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": - {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": - {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n - \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n - \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": - false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": - \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension - for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Required fields here for item properties.\",\n \"required\": - [\n \"rd:type\",\n \"rd:product_level\",\n - \ \"rd:sat_id\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$comment\": \"Remove this - and the following object if this is not an extension to a Collection.\",\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n - \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": - \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n - \ \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": - {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": - [\n \"public\",\n \"protected\",\n \"private\"\n - \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": - {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": - [\n \"scene\"\n ]\n },\n \"rd:product_level\": - {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n - \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n - \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": - \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n - \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": - {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": - {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n - \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": - {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": - {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": - {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields - with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n - \ }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path4].yaml b/tests/cassettes/test_examples/test_example_from_dict[path4].yaml index e27d3e227..7f15ee3df 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path4].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path4].yaml @@ -6,73 +6,284 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" headers: {} status: code: 200 @@ -84,8 +295,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: @@ -141,4 +350,73 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path8].yaml b/tests/cassettes/test_examples/test_example_from_dict[path5].yaml similarity index 99% rename from tests/cassettes/test_examples/test_example_read_file[path8].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path5].yaml index 6f78bf269..b543e00c0 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path8].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path5].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: @@ -135,8 +133,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: @@ -209,8 +205,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path7].yaml b/tests/cassettes/test_examples/test_example_from_dict[path7].yaml new file mode 100644 index 000000000..8a8c2e0e6 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path7].yaml @@ -0,0 +1,140 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path9].yaml b/tests/cassettes/test_examples/test_example_from_dict[path9].yaml deleted file mode 100644 index 23fc5b18f..000000000 --- a/tests/cassettes/test_examples/test_example_from_dict[path9].yaml +++ /dev/null @@ -1,432 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": - \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation - Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items. Remove this object - if this extension only applies to Collections.\",\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"$comment\": \"This validates the fields in Item Assets, - but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n },\n {\n \"$comment\": \"This - is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": - [\n {\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n {\n \"$ref\": - \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n - \ {\n \"$comment\": \"This is the schema for the top-level - fields in a Collection. Remove this if this extension does not define top-level - fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": - \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": - \"#/definitions/fields\"\n }\n ]\n },\n {\n - \ \"$comment\": \"This validates the fields in Collection Assets, - but does not require them.\",\n \"required\": [\n \"assets\"\n - \ ],\n \"properties\": {\n \"assets\": {\n \"type\": - \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Item Asset Definitions. It doesn't - require any fields.\",\n \"required\": [\n \"item_assets\"\n - \ ],\n \"properties\": {\n \"item_assets\": {\n - \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Summaries. By default, only checks - the existance of the properties, but not the schema of the summaries.\",\n - \ \"required\": [\n \"summaries\"\n ],\n \"properties\": - {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n - \ }\n }\n }\n ]\n }\n ],\n \"definitions\": - {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": - [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": - {\n \"type\": \"array\",\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n - \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": - \"Please list all fields here so that we can force the existance of one of - them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": - [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": - [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": - {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": - \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": - {\n \"type\": \"string\", \n \"title\": \"Proposed Data - Citation\"\n },\n \"sci:publications\": {\n \"type\": - \"array\",\n \"title\": \"Publications\",\n \"items\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"doi\": - {\n \"type\": \"string\",\n \"title\": \"Publication - DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n - \ }, \n \"citation\": { \n \"type\": - \"string\", \n \"title\": \"Publication Citation\"\n }\n - \ }\n }\n }\n },\n \"patternProperties\": - {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n - \ }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/view/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": - \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension - for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": - [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n - \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": - [\"view:sun_elevation\"]}\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": - {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": - {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": - {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": - {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": - {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n - \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n - \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": - false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": - \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension - for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Required fields here for item properties.\",\n \"required\": - [\n \"rd:type\",\n \"rd:product_level\",\n - \ \"rd:sat_id\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$comment\": \"Remove this - and the following object if this is not an extension to a Collection.\",\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n - \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": - \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n - \ \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": - {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": - [\n \"public\",\n \"protected\",\n \"private\"\n - \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": - {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": - [\n \"scene\"\n ]\n },\n \"rd:product_level\": - {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n - \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n - \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": - \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n - \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": - {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": - {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n - \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": - {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": - {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": - {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields - with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n - \ }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path10].yaml b/tests/cassettes/test_examples/test_example_read_file[path10].yaml index b60cc96ec..e799c741e 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path10].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path10].yaml @@ -6,8 +6,248 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: @@ -63,4 +303,73 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path2].yaml b/tests/cassettes/test_examples/test_example_read_file[path11].yaml similarity index 99% rename from tests/cassettes/test_examples/test_example_read_file[path2].yaml rename to tests/cassettes/test_examples/test_example_read_file[path11].yaml index cb75f6e95..92b20ee3b 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path2].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path11].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: @@ -84,8 +82,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: @@ -162,8 +158,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_read_file[path12].yaml b/tests/cassettes/test_examples/test_example_read_file[path12].yaml index edd89ac14..5562e8a96 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path12].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path12].yaml @@ -6,73 +6,122 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -84,8 +133,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json response: @@ -150,8 +197,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_read_file[path13].yaml b/tests/cassettes/test_examples/test_example_read_file[path13].yaml index cb75f6e95..b543e00c0 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path13].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path13].yaml @@ -6,73 +6,122 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -84,51 +133,45 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n @@ -162,8 +205,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_read_file[path15].yaml b/tests/cassettes/test_examples/test_example_read_file[path15].yaml index f7f2e4475..41e4e31cb 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path15].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path15].yaml @@ -6,73 +6,122 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -84,51 +133,45 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n diff --git a/tests/cassettes/test_examples/test_example_read_file[path16].yaml b/tests/cassettes/test_examples/test_example_read_file[path16].yaml index 6e35922c0..a0a42e384 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path16].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path16].yaml @@ -6,124 +6,71 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -135,8 +82,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json response: @@ -201,8 +146,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_read_file[path17].yaml b/tests/cassettes/test_examples/test_example_read_file[path17].yaml index 6f78bf269..92b20ee3b 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path17].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path17].yaml @@ -6,124 +6,71 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -135,47 +82,49 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n @@ -209,8 +158,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_read_file[path19].yaml b/tests/cassettes/test_examples/test_example_read_file[path19].yaml index 6a841f075..365856314 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path19].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path19].yaml @@ -6,124 +6,71 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -135,47 +82,49 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n \ \"null\"\n ]\n },\n \"proj:projjson\": {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n \ },\n {\n \"type\": \"null\"\n }\n \ ]\n },\n \"proj:geometry\":{\n \"$ref\": \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n diff --git a/tests/cassettes/test_examples/test_example_read_file[path1].yaml b/tests/cassettes/test_examples/test_example_read_file[path1].yaml new file mode 100644 index 000000000..c6a6f459c --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path1].yaml @@ -0,0 +1,64 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path3].yaml b/tests/cassettes/test_examples/test_example_read_file[path3].yaml deleted file mode 100644 index 9f12e87bf..000000000 --- a/tests/cassettes/test_examples/test_example_read_file[path3].yaml +++ /dev/null @@ -1,385 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": - \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation - Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items. Remove this object - if this extension only applies to Collections.\",\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"$comment\": \"This validates the fields in Item Assets, - but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n },\n {\n \"$comment\": \"This - is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": - [\n {\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n {\n \"$ref\": - \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n - \ {\n \"$comment\": \"This is the schema for the top-level - fields in a Collection. Remove this if this extension does not define top-level - fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": - \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": - \"#/definitions/fields\"\n }\n ]\n },\n {\n - \ \"$comment\": \"This validates the fields in Collection Assets, - but does not require them.\",\n \"required\": [\n \"assets\"\n - \ ],\n \"properties\": {\n \"assets\": {\n \"type\": - \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Item Asset Definitions. It doesn't - require any fields.\",\n \"required\": [\n \"item_assets\"\n - \ ],\n \"properties\": {\n \"item_assets\": {\n - \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Summaries. By default, only checks - the existance of the properties, but not the schema of the summaries.\",\n - \ \"required\": [\n \"summaries\"\n ],\n \"properties\": - {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n - \ }\n }\n }\n ]\n }\n ],\n \"definitions\": - {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": - [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": - {\n \"type\": \"array\",\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n - \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": - \"Please list all fields here so that we can force the existance of one of - them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": - [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": - [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": - {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": - \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": - {\n \"type\": \"string\", \n \"title\": \"Proposed Data - Citation\"\n },\n \"sci:publications\": {\n \"type\": - \"array\",\n \"title\": \"Publications\",\n \"items\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"doi\": - {\n \"type\": \"string\",\n \"title\": \"Publication - DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n - \ }, \n \"citation\": { \n \"type\": - \"string\", \n \"title\": \"Publication Citation\"\n }\n - \ }\n }\n }\n },\n \"patternProperties\": - {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n - \ }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/view/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": - \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension - for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": - [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n - \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": - [\"view:sun_elevation\"]}\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": - {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": - {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": - {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": - {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": - {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n - \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n - \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": - false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": - \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension - for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Required fields here for item properties.\",\n \"required\": - [\n \"rd:type\",\n \"rd:product_level\",\n - \ \"rd:sat_id\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$comment\": \"Remove this - and the following object if this is not an extension to a Collection.\",\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n - \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": - \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n - \ \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": - {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": - [\n \"public\",\n \"protected\",\n \"private\"\n - \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": - {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": - [\n \"scene\"\n ]\n },\n \"rd:product_level\": - {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n - \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n - \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": - \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n - \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": - {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": - {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n - \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": - {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": - {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": - {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields - with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n - \ }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path4].yaml b/tests/cassettes/test_examples/test_example_read_file[path4].yaml index e27d3e227..7f15ee3df 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path4].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path4].yaml @@ -6,73 +6,284 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" headers: {} status: code: 200 @@ -84,8 +295,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: @@ -141,4 +350,73 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path8].yaml b/tests/cassettes/test_examples/test_example_read_file[path5].yaml similarity index 99% rename from tests/cassettes/test_examples/test_example_from_dict[path8].yaml rename to tests/cassettes/test_examples/test_example_read_file[path5].yaml index 6f78bf269..b543e00c0 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path8].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path5].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: @@ -135,8 +133,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: @@ -209,8 +205,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/view/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_examples/test_example_read_file[path7].yaml b/tests/cassettes/test_examples/test_example_read_file[path7].yaml new file mode 100644 index 000000000..8a8c2e0e6 --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path7].yaml @@ -0,0 +1,140 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path9].yaml b/tests/cassettes/test_examples/test_example_read_file[path9].yaml deleted file mode 100644 index 23fc5b18f..000000000 --- a/tests/cassettes/test_examples/test_example_read_file[path9].yaml +++ /dev/null @@ -1,432 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": - \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation - Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items. Remove this object - if this extension only applies to Collections.\",\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"$comment\": \"This validates the fields in Item Assets, - but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n },\n {\n \"$comment\": \"This - is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": - [\n {\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n {\n \"$ref\": - \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n - \ {\n \"$comment\": \"This is the schema for the top-level - fields in a Collection. Remove this if this extension does not define top-level - fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": - \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": - \"#/definitions/fields\"\n }\n ]\n },\n {\n - \ \"$comment\": \"This validates the fields in Collection Assets, - but does not require them.\",\n \"required\": [\n \"assets\"\n - \ ],\n \"properties\": {\n \"assets\": {\n \"type\": - \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Item Asset Definitions. It doesn't - require any fields.\",\n \"required\": [\n \"item_assets\"\n - \ ],\n \"properties\": {\n \"item_assets\": {\n - \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Summaries. By default, only checks - the existance of the properties, but not the schema of the summaries.\",\n - \ \"required\": [\n \"summaries\"\n ],\n \"properties\": - {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n - \ }\n }\n }\n ]\n }\n ],\n \"definitions\": - {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": - [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": - {\n \"type\": \"array\",\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n - \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": - \"Please list all fields here so that we can force the existance of one of - them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": - [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": - [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": - {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": - \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": - {\n \"type\": \"string\", \n \"title\": \"Proposed Data - Citation\"\n },\n \"sci:publications\": {\n \"type\": - \"array\",\n \"title\": \"Publications\",\n \"items\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"doi\": - {\n \"type\": \"string\",\n \"title\": \"Publication - DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n - \ }, \n \"citation\": { \n \"type\": - \"string\", \n \"title\": \"Publication Citation\"\n }\n - \ }\n }\n }\n },\n \"patternProperties\": - {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n - \ }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/view/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": - \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension - for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": - [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n - \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": - [\"view:sun_elevation\"]}\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": - {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": - {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": - {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": - {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": - {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n - \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n - \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": - false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 - method: GET - uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": - \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension - for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Required fields here for item properties.\",\n \"required\": - [\n \"rd:type\",\n \"rd:product_level\",\n - \ \"rd:sat_id\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$comment\": \"Remove this - and the following object if this is not an extension to a Collection.\",\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n - \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": - \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n - \ \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": - {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": - [\n \"public\",\n \"protected\",\n \"private\"\n - \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": - {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": - [\n \"scene\"\n ]\n },\n \"rd:product_level\": - {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n - \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n - \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": - \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n - \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": - {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": - {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n - \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": - {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": - {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": - {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields - with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n - \ }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml b/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml index f7f2e4475..365856314 100644 --- a/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml +++ b/tests/cassettes/test_item/test_validate_extension[v1.0.0].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: @@ -84,8 +82,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json response: diff --git a/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml b/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml index 6a841f075..41e4e31cb 100644 --- a/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml +++ b/tests/cassettes/test_item/test_validate_extension[v1.1.0].yaml @@ -6,8 +6,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: @@ -135,8 +133,6 @@ interactions: - close Host: - stac-extensions.github.io - User-Agent: - - pystac/1.14.2 method: GET uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: From 36e3a0b508aa45b3416f80f54f1436f66f895f0e Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 11:11:09 -0500 Subject: [PATCH 41/60] Run some v1 tests on CI * Adds skip to all the v1 tests by default * Unskip v1 tests or files using `passing_v2` marker --- pyproject.toml | 3 +++ tests/conftest.py | 8 +------- tests/v1/conftest.py | 10 ++++++++++ tests/v1/test_item.py | 3 +++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ac47c4cd..4132d57cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,9 @@ filterwarnings = [ "ignore", # FIXME: turn this into error ] addopts = "--block-network --record-mode=none" +markers = [ + "passing_v2: v1 tests that are passing on pystac v2", +] [tool.uv] default-groups = ["dev", "docs"] diff --git a/tests/conftest.py b/tests/conftest.py index 7db956954..609f37085 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ from urllib.request import Request import pytest -from pytest import Config, FixtureRequest, Parser +from pytest import FixtureRequest, Parser import pystac.jsonschema from pystac import Catalog, Item @@ -21,12 +21,6 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: - if collection_path.is_relative_to(V1_DIR) and not config.getoption("--v1"): - return True - return None - - @pytest.fixture(scope="module") def vcr_config() -> dict[str, Any]: def scrub_response_headers(response: dict[str, Any]) -> dict[str, Any]: diff --git a/tests/v1/conftest.py b/tests/v1/conftest.py index 64a44ef7b..ff1a7aa05 100644 --- a/tests/v1/conftest.py +++ b/tests/v1/conftest.py @@ -16,6 +16,16 @@ HERE = Path(__file__).resolve().parent +def pytest_collection_modifyitems(config, items): + """Skip all tests in v1 unless they are marked with `passing_v2` flag""" + if config.getoption("--v1"): + return + skip_v1 = pytest.mark.skip(reason="need --v1 option to run") + for item in items: + if "passing_v2" not in item.keywords: + item.add_marker(skip_v1) + + @pytest.fixture def catalog() -> Catalog: return Catalog("test-catalog", "A test catalog") diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index 16bc1540b..2771c9171 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -27,6 +27,9 @@ from .utils import TestCases, assert_to_from_dict +pytestmark = pytest.mark.passing_v2 + + def test_to_from_dict(sample_item_dict: dict[str, Any]) -> None: param_dict = deepcopy(sample_item_dict) From 8d615c2c720472cc0526998a89a758b36915020d Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 11:16:47 -0500 Subject: [PATCH 42/60] Skip `item.ext` test in tests/v1/test_item.py --- tests/v1/test_item.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index 2771c9171..93a47081b 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -688,6 +688,7 @@ def test_no_collection(item: Item) -> None: assert item.collection is None +@pytest.mark.skip(reason="Still figuring out how to handle ext") def test_migrate_by_default() -> None: with open( TestCases.get_path("data-files/projection/example-with-version-1.1.json") From a49bb622e5e8c79841dcf36ca58bd17ae8c206ac Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 11:27:36 -0500 Subject: [PATCH 43/60] Switch to using an xfail rather than skip --- tests/v1/test_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index 93a47081b..b8b3b2889 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -688,7 +688,7 @@ def test_no_collection(item: Item) -> None: assert item.collection is None -@pytest.mark.skip(reason="Still figuring out how to handle ext") +@pytest.mark.xfail(reason=".ext is not supported for pystac v2") def test_migrate_by_default() -> None: with open( TestCases.get_path("data-files/projection/example-with-version-1.1.json") From 1a48558f44c47dab0e6382d21c34da39a92320ab Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 11:39:46 -0500 Subject: [PATCH 44/60] Exclude certain CI tests without deleting them --- .github/workflows/continuous-integration.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 7de020b36..616149f02 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -44,6 +44,7 @@ jobs: run: uv run pytest tests --block-network --record-mode=none coverage: + if: false # exclude on pystac v2 for now name: coverage runs-on: ubuntu-latest permissions: @@ -72,6 +73,7 @@ jobs: run: uv run coverage report without-orjson: + if: false # exclude on pystac v2 for now runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -87,6 +89,7 @@ jobs: # This checks to make sure any API changes haven't broken any of the # benchmarks. It doesn't do any actual benchmarking, since (IMO) that's not # appropriate for CI on GitHub actions. + if: false # exclude on pystac v2 for now runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -105,6 +108,7 @@ jobs: docs: runs-on: ubuntu-latest + if: false # exclude on pystac v2 for now steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 From 8b9fe7fcbac226804e070774f3af0231d333f34c Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 11:50:50 -0500 Subject: [PATCH 45/60] Longer error message --- src/pystac/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pystac/reader.py b/src/pystac/reader.py index 8f062c2ce..add5c5d88 100644 --- a/src/pystac/reader.py +++ b/src/pystac/reader.py @@ -26,7 +26,7 @@ def get_json(self, href: str | Path) -> dict[str, Any]: with open(href) as f: return json.load(f) else: - raise ValueError(f"Unsupported scheme: {parsed_url.scheme}") + raise ValueError(f"Unsupported scheme: {parsed_url.scheme} for {href}") def set_default_reader(reader: Reader) -> None: From 802fb8ac68e16e38f1ab1ccfe0d7039c4ddb0110 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 12:48:02 -0500 Subject: [PATCH 46/60] Make windows read and write (#1649) * If the file exists, read it * Just write it if it's windows and not obvioiusly a url * Use the DEFAULT_READER even in the validator --- src/pystac/jsonschema.py | 22 +++++----------------- src/pystac/reader.py | 3 ++- src/pystac/writer.py | 13 +++++++++++-- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/pystac/jsonschema.py b/src/pystac/jsonschema.py index 97fe25f3f..a2829b97d 100644 --- a/src/pystac/jsonschema.py +++ b/src/pystac/jsonschema.py @@ -1,11 +1,8 @@ import importlib.resources import json -import urllib.parse -import urllib.request import warnings from collections.abc import Iterator from typing import Any -from urllib.request import Request import referencing.retrieval from jsonschema.exceptions import ValidationError @@ -16,7 +13,8 @@ from pystac.errors import STACValidationError from .constants import STAC_OBJECT_TYPE -from .utils import get_stac_type, get_user_agent +from .reader import DEFAULT_READER +from .utils import get_stac_type class JSONSchemaValidator: @@ -35,7 +33,7 @@ def get_validator(self, type: STAC_OBJECT_TYPE, version: str) -> Any: except FileNotFoundError: warnings.warn(f"Local schema not found for {stac_type} v{version}") url = f"https://schemas.stacspec.org/v{version}/{stac_type}-spec/json-schema/{stac_type}.json" - schema_data = json.loads(get_text(url)) + schema_data = DEFAULT_READER.get_json(url) self.cache[path] = Draft7Validator(schema_data, registry=self.registry) return self.cache[path] @@ -50,7 +48,7 @@ def validate_core( def validate_extension(self, extension: str, data: dict[str, Any]) -> None: if extension not in self.cache: - schema_data = json.loads(get_text(extension)) + schema_data = DEFAULT_READER.get_json(extension) self.cache[extension] = Draft7Validator(schema_data, registry=self.registry) validator = self.cache[extension] try: @@ -61,17 +59,7 @@ def validate_extension(self, extension: str, data: dict[str, Any]) -> None: @referencing.retrieval.to_cached_resource() def cached_retrieve(uri: str) -> str: - return get_text(uri) - - -def get_text(uri: str) -> str: - if urllib.parse.urlparse(uri).scheme: - request = Request(uri, headers={"User-Agent": get_user_agent()}) - with urllib.request.urlopen(request) as response: - return str(response.read(), encoding="utf-8") - else: - with open(uri) as f: - return f.read() + return json.dumps(DEFAULT_READER.get_json(uri)) def registry_contents() -> Iterator[tuple[str, dict[str, Any]]]: diff --git a/src/pystac/reader.py b/src/pystac/reader.py index add5c5d88..aceca1915 100644 --- a/src/pystac/reader.py +++ b/src/pystac/reader.py @@ -1,4 +1,5 @@ import json +import os import urllib.parse import urllib.request from pathlib import Path @@ -22,7 +23,7 @@ def get_json(self, href: str | Path) -> dict[str, Any]: request = Request(href, headers={"User-Agent": get_user_agent()}) with urllib.request.urlopen(request) as f: return json.load(f) - elif not parsed_url.scheme: + elif not parsed_url.scheme or os.path.exists(href): with open(href) as f: return json.load(f) else: diff --git a/src/pystac/writer.py b/src/pystac/writer.py index b094e5d36..341c47a70 100644 --- a/src/pystac/writer.py +++ b/src/pystac/writer.py @@ -1,4 +1,5 @@ import json +import os import urllib.parse from pathlib import Path from typing import Any, Protocol @@ -11,7 +12,11 @@ def delete(self, href: str | Path) -> None: ... class StandardLibraryWriter: def put_json(self, data: dict[str, Any], href: str | Path) -> None: - if isinstance(href, Path) or not urllib.parse.urlparse(href).scheme: + if ( + isinstance(href, Path) + or not (scheme := urllib.parse.urlparse(href).scheme) + or (os.name == "nt" and scheme not in ("http", "https")) + ): Path(href).parent.mkdir(parents=True, exist_ok=True) with open(href, "w") as f: json.dump( @@ -22,7 +27,11 @@ def put_json(self, data: dict[str, Any], href: str | Path) -> None: raise ValueError("StandardLibraryWriter cannot write to urls") def delete(self, href: str | Path) -> None: - if isinstance(href, Path) or not urllib.parse.urlparse(href).scheme: + if ( + isinstance(href, Path) + or not (scheme := urllib.parse.urlparse(href).scheme) + or (os.name == "nt" and scheme not in ("http", "https")) + ): Path(href).unlink() else: raise ValueError("StandardLibraryWriter cannot delete urls") From 7abc27a597da80ce7d9ee04eb606db012f731487 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 13:11:20 -0500 Subject: [PATCH 47/60] Now with v2 tests --- tests/v1/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v1/conftest.py b/tests/v1/conftest.py index ff1a7aa05..72b0aed00 100644 --- a/tests/v1/conftest.py +++ b/tests/v1/conftest.py @@ -22,7 +22,7 @@ def pytest_collection_modifyitems(config, items): return skip_v1 = pytest.mark.skip(reason="need --v1 option to run") for item in items: - if "passing_v2" not in item.keywords: + if item.parent.path.is_relative_to(HERE) and "passing_v2" not in item.keywords: item.add_marker(skip_v1) From d27261536ea5146ffea5689acab8383e4f3d2b8a Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 13:24:10 -0500 Subject: [PATCH 48/60] Update last cassette --- .../test_item/test_null_geometry.yaml | 36 ++++--------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/tests/v1/cassettes/test_item/test_null_geometry.yaml b/tests/v1/cassettes/test_item/test_null_geometry.yaml index aa24cdd26..5ef31d732 100644 --- a/tests/v1/cassettes/test_item/test_null_geometry.yaml +++ b/tests/v1/cassettes/test_item/test_null_geometry.yaml @@ -1,9 +1,7 @@ interactions: - request: body: null - headers: - User-Agent: - - pystac/1.14.2 + headers: {} method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/item.json response: @@ -85,9 +83,7 @@ interactions: message: OK - request: body: null - headers: - User-Agent: - - pystac/1.14.2 + headers: {} method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/basics.json response: @@ -106,9 +102,7 @@ interactions: message: OK - request: body: null - headers: - User-Agent: - - pystac/1.14.2 + headers: {} method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/datetime.json response: @@ -157,9 +151,7 @@ interactions: message: OK - request: body: null - headers: - User-Agent: - - pystac/1.14.2 + headers: {} method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/instrument.json response: @@ -180,9 +172,7 @@ interactions: message: OK - request: body: null - headers: - User-Agent: - - pystac/1.14.2 + headers: {} method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/licensing.json response: @@ -198,9 +188,7 @@ interactions: message: OK - request: body: null - headers: - User-Agent: - - pystac/1.14.2 + headers: {} method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/provider.json response: @@ -231,8 +219,6 @@ interactions: - close Host: - schemas.stacspec.org - User-Agent: - - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/item.json response: @@ -319,8 +305,6 @@ interactions: - close Host: - schemas.stacspec.org - User-Agent: - - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/basics.json response: @@ -344,8 +328,6 @@ interactions: - close Host: - schemas.stacspec.org - User-Agent: - - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/datetime.json response: @@ -399,8 +381,6 @@ interactions: - close Host: - schemas.stacspec.org - User-Agent: - - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/instrument.json response: @@ -426,8 +406,6 @@ interactions: - close Host: - schemas.stacspec.org - User-Agent: - - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/licensing.json response: @@ -448,8 +426,6 @@ interactions: - close Host: - schemas.stacspec.org - User-Agent: - - pystac/1.14.2 method: GET uri: https://schemas.stacspec.org/v1.0.0-beta.2/item-spec/json-schema/provider.json response: From 078e25ed2654b675af2ffceca558c2909a8613cd Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 12 Feb 2026 13:43:46 -0500 Subject: [PATCH 49/60] Maybe the examples need to be sorted? --- .../test_example_from_dict[path11].yaml | 271 ++++++----- .../test_example_from_dict[path12].yaml | 94 ++-- .../test_example_from_dict[path14].yaml | 64 +++ .../test_example_from_dict[path16].yaml | 420 +++++++++++++----- ...ml => test_example_from_dict[path18].yaml} | 0 .../test_example_from_dict[path1].yaml | 140 ++++++ ...aml => test_example_from_dict[path2].yaml} | 0 ...aml => test_example_from_dict[path3].yaml} | 0 .../test_example_from_dict[path4].yaml | 388 +++------------- .../test_example_from_dict[path5].yaml | 263 ----------- ...aml => test_example_from_dict[path6].yaml} | 0 ...aml => test_example_from_dict[path8].yaml} | 0 .../test_example_read_file[path11].yaml | 271 ++++++----- .../test_example_read_file[path12].yaml | 94 ++-- .../test_example_read_file[path14].yaml | 64 +++ .../test_example_read_file[path16].yaml | 420 +++++++++++++----- ...ml => test_example_read_file[path18].yaml} | 0 .../test_example_read_file[path1].yaml | 140 ++++++ ...aml => test_example_read_file[path2].yaml} | 76 ++++ ...aml => test_example_read_file[path3].yaml} | 76 ++++ .../test_example_read_file[path4].yaml | 388 +++------------- .../test_example_read_file[path5].yaml | 263 ----------- ...aml => test_example_read_file[path6].yaml} | 0 ...aml => test_example_read_file[path8].yaml} | 0 tests/test_examples.py | 6 +- 25 files changed, 1720 insertions(+), 1718 deletions(-) create mode 100644 tests/cassettes/test_examples/test_example_from_dict[path14].yaml rename tests/cassettes/test_examples/{test_example_from_dict[path15].yaml => test_example_from_dict[path18].yaml} (100%) rename tests/cassettes/test_examples/{test_example_from_dict[path17].yaml => test_example_from_dict[path2].yaml} (100%) rename tests/cassettes/test_examples/{test_example_read_file[path17].yaml => test_example_from_dict[path3].yaml} (100%) delete mode 100644 tests/cassettes/test_examples/test_example_from_dict[path5].yaml rename tests/cassettes/test_examples/{test_example_from_dict[path10].yaml => test_example_from_dict[path6].yaml} (100%) rename tests/cassettes/test_examples/{test_example_from_dict[path19].yaml => test_example_from_dict[path8].yaml} (100%) create mode 100644 tests/cassettes/test_examples/test_example_read_file[path14].yaml rename tests/cassettes/test_examples/{test_example_read_file[path15].yaml => test_example_read_file[path18].yaml} (100%) rename tests/cassettes/test_examples/{test_example_from_dict[path7].yaml => test_example_read_file[path2].yaml} (63%) rename tests/cassettes/test_examples/{test_example_read_file[path7].yaml => test_example_read_file[path3].yaml} (63%) delete mode 100644 tests/cassettes/test_examples/test_example_read_file[path5].yaml rename tests/cassettes/test_examples/{test_example_read_file[path10].yaml => test_example_read_file[path6].yaml} (100%) rename tests/cassettes/test_examples/{test_example_read_file[path19].yaml => test_example_read_file[path8].yaml} (100%) diff --git a/tests/cassettes/test_examples/test_example_from_dict[path11].yaml b/tests/cassettes/test_examples/test_example_from_dict[path11].yaml index 92b20ee3b..5562e8a96 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path11].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path11].yaml @@ -7,70 +7,121 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -83,70 +134,58 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n \ \"assets\"\n ],\n \"properties\": {\n \"type\": {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" headers: {} status: code: 200 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml index 5562e8a96..b543e00c0 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path12].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path12].yaml @@ -134,58 +134,66 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": - \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC - Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema - for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n - \ ]\n },\n {\n \"required\": - [\n \"sat:orbit_state\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:absolute_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:relative_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:anx_datetime\"\n ]\n - \ }\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": - {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": - {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n - \ \"enum\": [\n \"ascending\",\n \"descending\",\n - \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": - {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n - \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": - 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n - \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": - {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified - fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": - false\n }\n }\n}" + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" headers: {} status: code: 200 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path14].yaml b/tests/cassettes/test_examples/test_example_from_dict[path14].yaml new file mode 100644 index 000000000..c6a6f459c --- /dev/null +++ b/tests/cassettes/test_examples/test_example_from_dict[path14].yaml @@ -0,0 +1,64 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path16].yaml b/tests/cassettes/test_examples/test_example_from_dict[path16].yaml index a0a42e384..7f15ee3df 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path16].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path16].yaml @@ -7,70 +7,193 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" headers: {} status: code: 200 @@ -83,58 +206,84 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": - \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC - Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema - for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n - \ ]\n },\n {\n \"required\": - [\n \"sat:orbit_state\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:absolute_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:relative_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:anx_datetime\"\n ]\n - \ }\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": - {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": - {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n - \ \"enum\": [\n \"ascending\",\n \"descending\",\n - \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": - {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n - \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": - 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n - \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": - {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified - fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": - false\n }\n }\n}" + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" headers: {} status: code: 200 @@ -201,4 +350,73 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path15].yaml b/tests/cassettes/test_examples/test_example_from_dict[path18].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_from_dict[path15].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path18].yaml diff --git a/tests/cassettes/test_examples/test_example_from_dict[path1].yaml b/tests/cassettes/test_examples/test_example_from_dict[path1].yaml index c6a6f459c..a0a42e384 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path1].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path1].yaml @@ -1,4 +1,144 @@ interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path17].yaml b/tests/cassettes/test_examples/test_example_from_dict[path2].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_from_dict[path17].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path2].yaml diff --git a/tests/cassettes/test_examples/test_example_read_file[path17].yaml b/tests/cassettes/test_examples/test_example_from_dict[path3].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_read_file[path17].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path3].yaml diff --git a/tests/cassettes/test_examples/test_example_from_dict[path4].yaml b/tests/cassettes/test_examples/test_example_from_dict[path4].yaml index 7f15ee3df..8a8c2e0e6 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path4].yaml +++ b/tests/cassettes/test_examples/test_example_from_dict[path4].yaml @@ -7,283 +7,70 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": - \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation - Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items. Remove this object - if this extension only applies to Collections.\",\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"$comment\": \"This validates the fields in Item Assets, - but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n },\n {\n \"$comment\": \"This - is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": - [\n {\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n {\n \"$ref\": - \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n - \ {\n \"$comment\": \"This is the schema for the top-level - fields in a Collection. Remove this if this extension does not define top-level - fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": - \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": - \"#/definitions/fields\"\n }\n ]\n },\n {\n - \ \"$comment\": \"This validates the fields in Collection Assets, - but does not require them.\",\n \"required\": [\n \"assets\"\n - \ ],\n \"properties\": {\n \"assets\": {\n \"type\": - \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Item Asset Definitions. It doesn't - require any fields.\",\n \"required\": [\n \"item_assets\"\n - \ ],\n \"properties\": {\n \"item_assets\": {\n - \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Summaries. By default, only checks - the existance of the properties, but not the schema of the summaries.\",\n - \ \"required\": [\n \"summaries\"\n ],\n \"properties\": - {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n - \ }\n }\n }\n ]\n }\n ],\n \"definitions\": - {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": - [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": - {\n \"type\": \"array\",\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n - \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": - \"Please list all fields here so that we can force the existance of one of - them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": - [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": - [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": - {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": - \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": - {\n \"type\": \"string\", \n \"title\": \"Proposed Data - Citation\"\n },\n \"sci:publications\": {\n \"type\": - \"array\",\n \"title\": \"Publications\",\n \"items\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"doi\": - {\n \"type\": \"string\",\n \"title\": \"Publication - DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n - \ }, \n \"citation\": { \n \"type\": - \"string\", \n \"title\": \"Publication Citation\"\n }\n - \ }\n }\n }\n },\n \"patternProperties\": - {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n - \ }\n }\n}" + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -350,73 +137,4 @@ interactions: status: code: 200 message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": - \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension - for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Required fields here for item properties.\",\n \"required\": - [\n \"rd:type\",\n \"rd:product_level\",\n - \ \"rd:sat_id\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$comment\": \"Remove this - and the following object if this is not an extension to a Collection.\",\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n - \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": - \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n - \ \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": - {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": - [\n \"public\",\n \"protected\",\n \"private\"\n - \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": - {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": - [\n \"scene\"\n ]\n },\n \"rd:product_level\": - {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n - \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n - \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": - \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n - \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": - {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": - {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n - \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": - {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": - {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": - {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields - with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n - \ }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path5].yaml b/tests/cassettes/test_examples/test_example_from_dict[path5].yaml deleted file mode 100644 index b543e00c0..000000000 --- a/tests/cassettes/test_examples/test_example_from_dict[path5].yaml +++ /dev/null @@ -1,263 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/view/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": - \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension - for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": - [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n - \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": - [\"view:sun_elevation\"]}\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": - {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": - {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": - {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": - {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": - {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n - \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n - \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": - false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_examples/test_example_from_dict[path10].yaml b/tests/cassettes/test_examples/test_example_from_dict[path6].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_from_dict[path10].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path6].yaml diff --git a/tests/cassettes/test_examples/test_example_from_dict[path19].yaml b/tests/cassettes/test_examples/test_example_from_dict[path8].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_from_dict[path19].yaml rename to tests/cassettes/test_examples/test_example_from_dict[path8].yaml diff --git a/tests/cassettes/test_examples/test_example_read_file[path11].yaml b/tests/cassettes/test_examples/test_example_read_file[path11].yaml index 92b20ee3b..5562e8a96 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path11].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path11].yaml @@ -7,70 +7,121 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" headers: {} status: code: 200 @@ -83,70 +134,58 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the - schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n \ \"assets\"\n ],\n \"properties\": {\n \"type\": {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Require fields here for item properties.\",\n \"required\": - [\n \"proj:epsg\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n - \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n - \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" headers: {} status: code: 200 diff --git a/tests/cassettes/test_examples/test_example_read_file[path12].yaml b/tests/cassettes/test_examples/test_example_read_file[path12].yaml index 5562e8a96..b543e00c0 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path12].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path12].yaml @@ -134,58 +134,66 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": - \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC - Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema - for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n - \ ]\n },\n {\n \"required\": - [\n \"sat:orbit_state\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:absolute_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:relative_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:anx_datetime\"\n ]\n - \ }\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": - {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": - {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n - \ \"enum\": [\n \"ascending\",\n \"descending\",\n - \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": - {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n - \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": - 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n - \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": - {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified - fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": - false\n }\n }\n}" + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" headers: {} status: code: 200 diff --git a/tests/cassettes/test_examples/test_example_read_file[path14].yaml b/tests/cassettes/test_examples/test_example_read_file[path14].yaml new file mode 100644 index 000000000..c6a6f459c --- /dev/null +++ b/tests/cassettes/test_examples/test_example_read_file[path14].yaml @@ -0,0 +1,64 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/view/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": + \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension + for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": + [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n + \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": + [\"view:sun_elevation\"]}\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": + {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": + {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": + {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": + {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n + \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": + {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n + \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n + \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path16].yaml b/tests/cassettes/test_examples/test_example_read_file[path16].yaml index a0a42e384..7f15ee3df 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path16].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path16].yaml @@ -7,70 +7,193 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n - \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and + STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", + \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n + \ }\n ],\n \"anyOf\": [\n {\n \"required\": + [\"properties\"],\n \"properties\": {\n \"properties\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n + \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n + \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n + \ {\n \"properties\": {\n \"item_assets\": + {\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/validate_properties\"\n }\n }\n + \ }\n },\n {\n \"properties\": {\n + \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": + \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n + \ \"allOf\": [\n {\n \"$ref\": + \"#/definitions/validate_bands\"\n },\n {\n + \ \"properties\": {\n \"eo:cloud_cover\": + {\n \"type\": [\"array\", \"object\"],\n \"items\": + {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ }\n }\n }\n + \ },\n {\n \"properties\": + {\n \"eo:snow_cover\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:snow_cover\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:common_name\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:common_name\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:center_wavelength\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:center_wavelength\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:full_width_half_max\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:full_width_half_max\"\n }\n }\n + \ }\n },\n {\n \"properties\": + {\n \"eo:solar_illumination\": {\n \"type\": + [\"array\", \"object\"],\n \"items\": {\n \"$ref\": + \"#/definitions/eo:solar_illumination\"\n }\n }\n + \ }\n }\n ]\n }\n + \ }\n }\n ],\n \"anyOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n + \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n + \ \"required\": [\"item_assets\"],\n \"properties\": + {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n + \ }\n }\n },\n {\n \"required\": + [\"summaries\"],\n \"properties\": {\n \"summaries\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n ]\n }\n }\n ],\n \"definitions\": + {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": + \"#/definitions/require_fields\"\n },\n {\n \"$ref\": + \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": + {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": + {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n + \ }\n ]\n },\n \"require_assets\": {\n \"required\": + [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": + \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": + {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n + \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n + \ \"not\": {\n \"additionalProperties\": {\n \"not\": + {\n \"$ref\": \"#/definitions/require_properties\"\n }\n + \ }\n }\n },\n \"require_in_bands\": {\n \"required\": + [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": + \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n + \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": + [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": + [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n + \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": + [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] + }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": + {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n + \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n + \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n + \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n + \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n + \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n + \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": + {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": + \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n + \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n + \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n + \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n + \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": + \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width + Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": + 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n + \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields + are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n + \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n \"assets\": {\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n }\n ]\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\",\n \"properties\",\n \"assets\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n },\n \"properties\": {\n \"$ref\": - \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": - \"The if-then-else below checks whether the eo:bands is given in assets or - not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands - in properties (then).\",\n \"if\": {\n \"required\": [\n - \ \"assets\"\n ],\n \"properties\": {\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"properties\": {\n \"eo:bands\": - false\n }\n }\n }\n }\n - \ },\n \"then\": {\n \"properties\": {\n \"properties\": - {\n \"properties\": {\n \"eo:bands\": false\n - \ }\n }\n }\n },\n \"else\": - {\n \"properties\": {\n \"properties\": {\n \"properties\": - {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n - \ }\n }\n }\n }\n }\n - \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": + [\n \"type\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Collection\"\n },\n \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": - \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": - \"#/definitions/bands\"\n }\n },\n \"patternProperties\": - {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n - \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n - \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n - \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": - {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": - \"string\"\n },\n \"common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"red\",\n \"rededge\",\n \"yellow\",\n - \ \"pan\",\n \"nir\",\n \"nir08\",\n - \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n - \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"center_wavelength\": - {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n - \ },\n \"full_width_half_max\": {\n \"title\": - \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n - \ }\n }\n }\n }\n}" + \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection + code\",\n \"type\":[\n \"string\",\n \"null\"\n + \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" headers: {} status: code: 200 @@ -83,58 +206,84 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": - \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC - Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema - for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n - \ ]\n },\n {\n \"required\": - [\n \"sat:orbit_state\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:absolute_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:relative_orbit\"\n ]\n - \ },\n {\n \"required\": - [\n \"sat:anx_datetime\"\n ]\n - \ }\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": + \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation + Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n + \ \"$comment\": \"This is the schema for STAC Items. Remove this object + if this extension only applies to Collections.\",\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"type\": \"object\",\n \"required\": [\n \"type\",\n + \ \"properties\",\n \"assets\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Feature\"\n },\n + \ \"properties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"$comment\": \"This validates the fields in Item Assets, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": - {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": - {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n - \ \"enum\": [\n \"ascending\",\n \"descending\",\n - \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": - {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n - \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": - 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n - \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": - {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified - fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": - false\n }\n }\n}" + \ }\n }\n ]\n },\n {\n \"$comment\": \"This + is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": + [\n {\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n }\n }\n },\n {\n \"$ref\": + \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n + \ {\n \"$comment\": \"This is the schema for the top-level + fields in a Collection. Remove this if this extension does not define top-level + fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": + \"#/definitions/fields\"\n }\n ]\n },\n {\n + \ \"$comment\": \"This validates the fields in Collection Assets, + but does not require them.\",\n \"required\": [\n \"assets\"\n + \ ],\n \"properties\": {\n \"assets\": {\n \"type\": + \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Item Asset Definitions. It doesn't + require any fields.\",\n \"required\": [\n \"item_assets\"\n + \ ],\n \"properties\": {\n \"item_assets\": {\n + \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": + {\n \"not\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/require_any_field\"\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n }\n }\n + \ }\n }\n }\n },\n {\n \"$comment\": + \"This is the schema for the fields in Summaries. By default, only checks + the existance of the properties, but not the schema of the summaries.\",\n + \ \"required\": [\n \"summaries\"\n ],\n \"properties\": + {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n + \ }\n }\n }\n ]\n }\n ],\n \"definitions\": + {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": + [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": + \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n + \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": + \"Please list all fields here so that we can force the existance of one of + them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": + [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": + [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": + {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": + \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": + {\n \"type\": \"string\", \n \"title\": \"Proposed Data + Citation\"\n },\n \"sci:publications\": {\n \"type\": + \"array\",\n \"title\": \"Publications\",\n \"items\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"doi\": + {\n \"type\": \"string\",\n \"title\": \"Publication + DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n + \ }, \n \"citation\": { \n \"type\": + \"string\", \n \"title\": \"Publication Citation\"\n }\n + \ }\n }\n }\n },\n \"patternProperties\": + {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n + \ }\n }\n}" headers: {} status: code: 200 @@ -201,4 +350,73 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": + \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension + for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": + \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Required fields here for item properties.\",\n \"required\": + [\n \"rd:type\",\n \"rd:product_level\",\n + \ \"rd:sat_id\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$comment\": \"Remove this + and the following object if this is not an extension to a Collection.\",\n + \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n + \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n + \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": + \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n + \ \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": + {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": + [\n \"public\",\n \"protected\",\n \"private\"\n + \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": + {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": + [\n \"scene\"\n ]\n },\n \"rd:product_level\": + {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n + \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n + \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": + \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n + \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": + {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": + {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n + \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": + {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": + {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": + {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields + with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n + \ }\n }\n}\n" + headers: {} + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path15].yaml b/tests/cassettes/test_examples/test_example_read_file[path18].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_read_file[path15].yaml rename to tests/cassettes/test_examples/test_example_read_file[path18].yaml diff --git a/tests/cassettes/test_examples/test_example_read_file[path1].yaml b/tests/cassettes/test_examples/test_example_read_file[path1].yaml index c6a6f459c..a0a42e384 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path1].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path1].yaml @@ -1,4 +1,144 @@ interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n + \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/sat/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\",\n \"title\": + \"Satellite Extension\",\n \"description\": \"STAC Sat Extension to a STAC + Item.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the schema + for STAC Items.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"anyOf\": + [\n {\n \"required\": [\n \"sat:platform_international_designator\"\n + \ ]\n },\n {\n \"required\": + [\n \"sat:orbit_state\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:absolute_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:relative_orbit\"\n ]\n + \ },\n {\n \"required\": + [\n \"sat:anx_datetime\"\n ]\n + \ }\n ]\n },\n {\n + \ \"$ref\": \"#/definitions/fields\"\n }\n + \ ]\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n }\n },\n + \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n + \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC + Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n + \ \"required\": [\n \"type\"\n ],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n + \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/sat/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"type\": + \"object\",\n \"properties\": {\n \"sat:platform_international_designator\": + {\n \"type\": \"string\"\n },\n \"sat:orbit_state\": + {\n \"title\": \"Orbit State\",\n \"type\": \"string\",\n + \ \"enum\": [\n \"ascending\",\n \"descending\",\n + \ \"geostationary\"\n ]\n },\n \"sat:absolute_orbit\": + {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n + \ \"sat:relative_orbit\": {\n \"type\": \"integer\",\n \"minimum\": + 1\n },\n \"sat:anx_datetime\": {\n \"type\": \"string\",\n + \ \"format\": \"date-time\"\n }\n },\n \"patternProperties\": + {\n \"^(?!sat:)\": {\n \"$comment\": \"Do not allow unspecified + fields prefixed with sat:\"\n }\n },\n \"additionalProperties\": + false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_from_dict[path7].yaml b/tests/cassettes/test_examples/test_example_read_file[path2].yaml similarity index 63% rename from tests/cassettes/test_examples/test_example_from_dict[path7].yaml rename to tests/cassettes/test_examples/test_example_read_file[path2].yaml index 8a8c2e0e6..92b20ee3b 100644 --- a/tests/cassettes/test_examples/test_example_from_dict[path7].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path2].yaml @@ -75,6 +75,82 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_read_file[path7].yaml b/tests/cassettes/test_examples/test_example_read_file[path3].yaml similarity index 63% rename from tests/cassettes/test_examples/test_example_read_file[path7].yaml rename to tests/cassettes/test_examples/test_example_read_file[path3].yaml index 8a8c2e0e6..92b20ee3b 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path7].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path3].yaml @@ -75,6 +75,82 @@ interactions: status: code: 200 message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + method: GET + uri: https://stac-extensions.github.io/projection/v1.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\",\n \"title\": + \"Projection Extension\",\n \"description\": \"STAC Projection Extension + for STAC Items.\",\n \"oneOf\": [\n {\n \"$comment\": \"This is the + schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n + \ \"assets\"\n ],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": + {\n \"allOf\": [\n {\n \"$comment\": + \"Require fields here for item properties.\",\n \"required\": + [\n \"proj:epsg\"\n ]\n },\n + \ {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n ]\n },\n \"assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": + {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": + {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n + \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v1.0.0/schema.json\"\n + \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": + \"Add your new fields here. Don't require them here, do that above in the + item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"proj:epsg\":{\n + \ \"title\":\"EPSG code\",\n \"type\":[\n \"integer\",\n + \ \"null\"\n ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate + Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n + \ \"null\"\n ]\n },\n \"proj:projjson\": + {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n + \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.2/projjson.schema.json\"\n + \ },\n {\n \"type\": \"null\"\n }\n + \ ]\n },\n \"proj:geometry\":{\n \"$ref\": + \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n + \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": + [\n {\n \"minItems\":4,\n \"maxItems\":4\n + \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n + \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n + \ \"lon\"\n ],\n \"properties\": {\n \"lat\": + {\n \"type\": \"number\",\n \"minimum\": -90,\n + \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": + \"number\",\n \"minimum\": -180,\n \"maximum\": + 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n + \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n + \ \"items\":{\n \"type\":\"integer\"\n }\n },\n + \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n + \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n + \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n + \ }\n ],\n \"items\":{\n \"type\":\"number\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": + {}\n },\n \"additionalProperties\": false\n }\n }\n}" + headers: {} + status: + code: 200 + message: OK - request: body: null headers: diff --git a/tests/cassettes/test_examples/test_example_read_file[path4].yaml b/tests/cassettes/test_examples/test_example_read_file[path4].yaml index 7f15ee3df..8a8c2e0e6 100644 --- a/tests/cassettes/test_examples/test_example_read_file[path4].yaml +++ b/tests/cassettes/test_examples/test_example_read_file[path4].yaml @@ -7,283 +7,70 @@ interactions: Host: - stac-extensions.github.io method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json + uri: https://stac-extensions.github.io/eo/v1.0.0/schema.json response: body: string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \"https://stac-extensions.github.io/eo/v1.0.0/schema.json#\",\n \"title\": + \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items.\",\n + \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": + [\n \"type\",\n \"properties\",\n \"assets\"\n + \ ],\n \"properties\": {\n \"type\": {\n \"const\": + \"Feature\"\n },\n \"properties\": {\n \"$ref\": + \"#/definitions/fields\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n }\n },\n \"$comment\": + \"The if-then-else below checks whether the eo:bands is given in assets or + not. If yes, allows eo:bands in properties (else), otherwise, disallows eo:bands + in properties (then).\",\n \"if\": {\n \"required\": [\n + \ \"assets\"\n ],\n \"properties\": {\n + \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"properties\": {\n \"eo:bands\": + false\n }\n }\n }\n }\n + \ },\n \"then\": {\n \"properties\": {\n \"properties\": + {\n \"properties\": {\n \"eo:bands\": false\n + \ }\n }\n }\n },\n \"else\": + {\n \"properties\": {\n \"properties\": {\n \"properties\": + {\n \"eo:bands\": {\n \"$ref\": \"#/definitions/bands\"\n + \ }\n }\n }\n }\n }\n + \ },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n + \ }\n ]\n },\n {\n \"$comment\": \"This is the schema + for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": + \"object\",\n \"required\": [\n \"type\"\n ],\n + \ \"properties\": {\n \"type\": {\n \"const\": + \"Collection\"\n },\n \"assets\": {\n \"type\": + \"object\",\n \"additionalProperties\": {\n \"$ref\": + \"#/definitions/fields\"\n }\n },\n \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n + \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v1.0.0/schema.json\"\n \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/scientific/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json#\",\n \"title\": - \"Scientific Citation Extension\",\n \"description\": \"Scientific Citation - Extension for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items. Remove this object - if this extension only applies to Collections.\",\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"$comment\": \"This validates the fields in Item Assets, - but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n }\n ]\n },\n {\n \"$comment\": \"This - is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\": - [\n {\n \"required\": [\n \"type\"\n ],\n - \ \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n {\n \"$ref\": - \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n - \ {\n \"$comment\": \"This is the schema for the top-level - fields in a Collection. Remove this if this extension does not define top-level - fields for Collections.\",\n \"allOf\": [\n {\n \"$ref\": - \"#/definitions/require_any_field\"\n },\n {\n \"$ref\": - \"#/definitions/fields\"\n }\n ]\n },\n {\n - \ \"$comment\": \"This validates the fields in Collection Assets, - but does not require them.\",\n \"required\": [\n \"assets\"\n - \ ],\n \"properties\": {\n \"assets\": {\n \"type\": - \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Item Asset Definitions. It doesn't - require any fields.\",\n \"required\": [\n \"item_assets\"\n - \ ],\n \"properties\": {\n \"item_assets\": {\n - \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\": - {\n \"not\": {\n \"allOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_any_field\"\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n }\n }\n - \ }\n }\n }\n },\n {\n \"$comment\": - \"This is the schema for the fields in Summaries. By default, only checks - the existance of the properties, but not the schema of the summaries.\",\n - \ \"required\": [\n \"summaries\"\n ],\n \"properties\": - {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n - \ }\n }\n }\n ]\n }\n ],\n \"definitions\": - {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\": - [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\": - {\n \"type\": \"array\",\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/scientific/v1.0.0/schema.json\"\n }\n - \ }\n }\n },\n \"require_any_field\": {\n \"$comment\": - \"Please list all fields here so that we can force the existance of one of - them in other parts of the schemas.\",\n \"anyOf\": [\n {\"required\": - [\"sci:doi\"]},\n {\"required\": [\"sci:citation\"]},\n {\"required\": - [\"sci:publications\"]}\n ]\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"sci:doi\": - {\n \"type\": \"string\",\n \"title\": \"Data DOI\",\n \"pattern\": - \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n }, \n \"sci:citation\": - {\n \"type\": \"string\", \n \"title\": \"Proposed Data - Citation\"\n },\n \"sci:publications\": {\n \"type\": - \"array\",\n \"title\": \"Publications\",\n \"items\": {\n - \ \"type\": \"object\",\n \"properties\": {\n \"doi\": - {\n \"type\": \"string\",\n \"title\": \"Publication - DOI\",\n \"pattern\": \"^10\\\\.[0-9a-zA-Z]{4,}/[^\\\\s]+$\"\n - \ }, \n \"citation\": { \n \"type\": - \"string\", \n \"title\": \"Publication Citation\"\n }\n - \ }\n }\n }\n },\n \"patternProperties\": - {\n \"^(?!sci:)\": {}\n },\n \"additionalProperties\": false\n - \ }\n }\n}" + \"object\",\n \"properties\": {\n \"eo:cloud_cover\": {\n \"title\": + \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"eo:bands\": {\n \"$ref\": + \"#/definitions/bands\"\n }\n },\n \"patternProperties\": + {\n \"^(?!eo:)\": {}\n },\n \"additionalProperties\": false\n + \ },\n \"bands\": {\n \"title\": \"Bands\",\n \"type\": \"array\",\n + \ \"minItems\": 1,\n \"items\": {\n \"title\": \"Band\",\n + \ \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": + {\n \"name\": {\n \"title\": \"Name of the band\",\n \"type\": + \"string\"\n },\n \"common_name\": {\n \"title\": + \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": + [\n \"coastal\",\n \"blue\",\n \"green\",\n + \ \"red\",\n \"rededge\",\n \"yellow\",\n + \ \"pan\",\n \"nir\",\n \"nir08\",\n + \ \"nir09\",\n \"cirrus\",\n \"swir16\",\n + \ \"swir22\",\n \"lwir\",\n \"lwir11\",\n + \ \"lwir12\"\n ]\n },\n \"center_wavelength\": + {\n \"title\": \"Center Wavelength\",\n \"type\": \"number\"\n + \ },\n \"full_width_half_max\": {\n \"title\": + \"Full Width Half Max (FWHM)\",\n \"type\": \"number\"\n }\n + \ }\n }\n }\n }\n}" headers: {} status: code: 200 @@ -350,73 +137,4 @@ interactions: status: code: 200 message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/remote-data/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\",\n \"title\": - \"Remote Data (Example) Extension\",\n \"description\": \"STAC Example Extension - for fictional vendor Remote Data\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"$comment\": - \"Required fields here for item properties.\",\n \"required\": - [\n \"rd:type\",\n \"rd:product_level\",\n - \ \"rd:sat_id\"\n ]\n },\n - \ {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n ]\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n },\n {\n \"$comment\": \"This is the schema - for STAC Collections.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"rd:visibility\"\n - \ ],\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n },\n \"item_assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$comment\": \"Remove this - and the following object if this is not an extension to a Collection.\",\n - \ \"$ref\": \"#/definitions/stac_extensions\"\n },\n {\n - \ \"$ref\": \"#/definitions/collection_fields\"\n }\n ]\n - \ }\n ],\n \"definitions\": {\n \"stac_extensions\": {\n \"type\": - \"object\",\n \"required\": [\n \"stac_extensions\"\n ],\n - \ \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"collection_fields\": {\n \"properties\": - {\n \"rd:visibility\": {\n \"type\": \"string\",\n \"enum\": - [\n \"public\",\n \"protected\",\n \"private\"\n - \ ]\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Remote Data fictional fields.\",\n \"type\": \"object\",\n \"properties\": - {\n \"rd:type\": {\n \"type\": \"string\",\n \"enum\": - [\n \"scene\"\n ]\n },\n \"rd:product_level\": - {\n \"type\": \"string\",\n \"enum\": [\n \"LV1A\",\n - \ \"LV1B\",\n \"LV2A\",\n \"LV2B\",\n \"LV3A\",\n - \ \"LV3B\"\n ]\n }, \n \"rd:runs\": {\n \"type\": - \"array\", \n \"items\": {\n \"type\": \"string\"\n }\n - \ },\n \"rd:parsecs\": {\n \"type\": \"array\", \n \"items\": - {\n \"type\": \"number\"\n }\n },\n \"rd:anomalous_pixels\": - {\n \"type\": \"number\"\n },\n \"rd:sat_id\": {\n - \ \"type\": \"string\"\n },\n \"rd:earth_sun_distance\": - {\n \"type\": \"number\"\n },\n \"rd:flux_capacitor\": - {\n \"type\": \"boolean\"\n }\n },\n \"patternProperties\": - {\n \"^(?!rd:)\": {\n \"$comment\": \"Disallow other fields - with rd: prefix\"\n }\n },\n \"additionalProperties\": false\n - \ }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path5].yaml b/tests/cassettes/test_examples/test_example_read_file[path5].yaml deleted file mode 100644 index b543e00c0..000000000 --- a/tests/cassettes/test_examples/test_example_read_file[path5].yaml +++ /dev/null @@ -1,263 +0,0 @@ -interactions: -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/eo/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/eo/v2.0.0/schema.json#\",\n \"title\": - \"EO Extension\",\n \"description\": \"STAC EO Extension for STAC Items and - STAC Collections.\",\n \"type\": \"object\",\n \"required\": [\"stac_extensions\", - \"type\"],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/eo/v2.0.0/schema.json\"\n - \ }\n }\n },\n \"allOf\": [\n {\n \"$comment\": \"Items\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Feature\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/validate_assets\"\n - \ }\n ],\n \"anyOf\": [\n {\n \"required\": - [\"properties\"],\n \"properties\": {\n \"properties\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/require_assets\"\n - \ }\n ]\n }\n },\n {\n \"$comment\": \"Collections\",\n - \ \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": - \"Collection\"\n }\n }\n },\n \"then\": {\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/validate_bands\"\n },\n - \ {\n \"$ref\": \"#/definitions/validate_assets\"\n },\n - \ {\n \"properties\": {\n \"item_assets\": - {\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/validate_properties\"\n }\n }\n - \ }\n },\n {\n \"properties\": {\n - \ \"summaries\": {\n \"type\": \"object\",\n \"$comment\": - \"We can't properly validate summary objects types (min/max or schemas) yet.\",\n - \ \"allOf\": [\n {\n \"$ref\": - \"#/definitions/validate_bands\"\n },\n {\n - \ \"properties\": {\n \"eo:cloud_cover\": - {\n \"type\": [\"array\", \"object\"],\n \"items\": - {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ }\n }\n }\n - \ },\n {\n \"properties\": - {\n \"eo:snow_cover\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:snow_cover\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:common_name\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:common_name\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:center_wavelength\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:center_wavelength\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:full_width_half_max\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:full_width_half_max\"\n }\n }\n - \ }\n },\n {\n \"properties\": - {\n \"eo:solar_illumination\": {\n \"type\": - [\"array\", \"object\"],\n \"items\": {\n \"$ref\": - \"#/definitions/eo:solar_illumination\"\n }\n }\n - \ }\n }\n ]\n }\n - \ }\n }\n ],\n \"anyOf\": [\n {\n - \ \"$ref\": \"#/definitions/require_in_bands\"\n },\n {\n - \ \"$ref\": \"#/definitions/require_assets\"\n },\n {\n - \ \"required\": [\"item_assets\"],\n \"properties\": - {\n \"item_assets\": {\n \"$ref\": \"#/definitions/asset_contains\"\n - \ }\n }\n },\n {\n \"required\": - [\"summaries\"],\n \"properties\": {\n \"summaries\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n ]\n }\n }\n ],\n \"definitions\": - {\n \"require_properties\": {\n \"anyOf\": [\n {\n \"$ref\": - \"#/definitions/require_fields\"\n },\n {\n \"$ref\": - \"#/definitions/require_in_bands\"\n }\n ]\n },\n \"validate_bands\": - {\n \"type\": \"object\",\n \"properties\": {\n \"bands\": - {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n \"validate_properties\": - {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n {\n \"$ref\": \"#/definitions/validate_bands\"\n - \ }\n ]\n },\n \"require_assets\": {\n \"required\": - [\"assets\"],\n \"properties\": {\n \"assets\": {\n \"$ref\": - \"#/definitions/asset_contains\"\n }\n }\n },\n \"validate_assets\": - {\n \"properties\": {\n \"assets\": {\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/validate_properties\"\n }\n - \ }\n }\n },\n \"asset_contains\": {\n \"type\": \"object\",\n - \ \"not\": {\n \"additionalProperties\": {\n \"not\": - {\n \"$ref\": \"#/definitions/require_properties\"\n }\n - \ }\n }\n },\n \"require_in_bands\": {\n \"required\": - [\"bands\"],\n \"properties\": {\n \"bands\": {\n \"type\": - \"array\",\n \"contains\": {\n \"$ref\": \"#/definitions/require_fields\"\n - \ }\n }\n }\n },\n \"require_fields\": {\n \"anyOf\": - [\n { \"required\": [\"eo:cloud_cover\"] },\n { \"required\": - [\"eo:snow_cover\"] },\n { \"required\": [\"eo:common_name\"] },\n - \ { \"required\": [\"eo:center_wavelength\"] },\n { \"required\": - [\"eo:full_width_half_max\"] },\n { \"required\": [\"eo:solar_illumination\"] - }\n ]\n },\n \"fields\": {\n \"type\": \"object\",\n \"properties\": - {\n \"eo:cloud_cover\": {\n \"$ref\": \"#/definitions/eo:cloud_cover\"\n - \ },\n \"eo:snow_cover\": {\n \"$ref\": \"#/definitions/eo:snow_cover\"\n - \ },\n \"eo:common_name\": {\n \"$ref\": \"#/definitions/eo:common_name\"\n - \ },\n \"eo:center_wavelength\": {\n \"$ref\": \"#/definitions/eo:center_wavelength\"\n - \ },\n \"eo:full_width_half_max\": {\n \"$ref\": \"#/definitions/eo:full_width_half_max\"\n - \ },\n \"eo:solar_illumination\": {\n \"$ref\": \"#/definitions/eo:solar_illumination\"\n - \ }\n },\n \"patternProperties\": {\n \"^(?!eo:)\": - {}\n },\n \"additionalProperties\": false\n },\n \"eo:cloud_cover\": - {\n \"title\": \"Cloud Cover\",\n \"type\": \"number\",\n \"minimum\": - 0,\n \"maximum\": 100\n },\n \"eo:snow_cover\": {\n \"title\": - \"Snow and Ice Cover\",\n \"type\": \"number\",\n \"minimum\": 0,\n - \ \"maximum\": 100\n },\n \"eo:common_name\": {\n \"title\": - \"Common Name of the band\",\n \"type\": \"string\",\n \"enum\": - [\n \"pan\",\n \"coastal\",\n \"blue\",\n \"green\",\n - \ \"green05\",\n \"yellow\",\n \"red\",\n \"rededge\",\n - \ \"rededge071\",\n \"rededge075\",\n \"rededge078\",\n - \ \"nir\",\n \"nir08\",\n \"nir09\",\n \"cirrus\",\n - \ \"swir16\",\n \"swir22\",\n \"lwir\",\n \"lwir11\",\n - \ \"lwir12\"\n ]\n },\n \"eo:center_wavelength\": {\n \"title\": - \"Center Wavelength\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:full_width_half_max\": {\n \"title\": \"Full Width - Half Max (FWHM)\",\n \"type\": \"number\",\n \"minimumExclusive\": - 0\n },\n \"eo:solar_illumination\": {\n \"title\": \"Solar Illumination\",\n - \ \"type\": \"number\",\n \"minimum\": 0\n }\n }\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/projection/v2.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\",\n \"title\": - \"Projection Extension\",\n \"description\": \"STAC Projection Extension - for STAC Items.\",\n \"$comment\": \"This schema succeeds if the proj: fields - are not used at all, please keep this in mind.\",\n \"oneOf\": [\n {\n - \ \"$comment\": \"This is the schema for STAC Items.\",\n \"allOf\": - [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n },\n - \ {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n - \ \"properties\",\n \"assets\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Feature\"\n },\n - \ \"properties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ },\n \"assets\": {\n \"type\": \"object\",\n - \ \"additionalProperties\": {\n \"$ref\": \"#/definitions/fields\"\n - \ }\n }\n }\n }\n ]\n },\n - \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n - \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\": - [\n \"type\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Collection\"\n },\n \"assets\": - {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/projection/v2.0.0/schema.json\"\n - \ }\n }\n }\n },\n \"fields\": {\n \"type\": - \"object\",\n \"properties\": {\n \"proj:code\":{\n \"title\":\"Projection - code\",\n \"type\":[\n \"string\",\n \"null\"\n - \ ]\n },\n \"proj:wkt2\":{\n \"title\":\"Coordinate - Reference System in WKT2 format\",\n \"type\":[\n \"string\",\n - \ \"null\"\n ]\n },\n \"proj:projjson\": - {\n \"title\":\"Coordinate Reference System in PROJJSON format\",\n - \ \"oneOf\": [\n {\n \"$ref\": \"https://proj.org/schemas/v0.7/projjson.schema.json\"\n - \ },\n {\n \"type\": \"null\"\n }\n - \ ]\n },\n \"proj:geometry\":{\n \"$ref\": - \"https://geojson.org/schema/Geometry.json\"\n },\n \"proj:bbox\":{\n - \ \"title\":\"Extent\",\n \"type\":\"array\",\n \"oneOf\": - [\n {\n \"minItems\":4,\n \"maxItems\":4\n - \ },\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n },\n \"proj:centroid\":{\n \"title\":\"Centroid\",\n - \ \"type\":\"object\",\n \"required\": [\n \"lat\",\n - \ \"lon\"\n ],\n \"properties\": {\n \"lat\": - {\n \"type\": \"number\",\n \"minimum\": -90,\n - \ \"maximum\": 90\n },\n \"lon\": {\n \"type\": - \"number\",\n \"minimum\": -180,\n \"maximum\": - 180\n }\n }\n },\n \"proj:shape\":{\n \"title\":\"Shape\",\n - \ \"type\":\"array\",\n \"minItems\":2,\n \"maxItems\":2,\n - \ \"items\":{\n \"type\":\"integer\"\n }\n },\n - \ \"proj:transform\":{\n \"title\":\"Transform\",\n \"type\":\"array\",\n - \ \"oneOf\": [\n {\n \"minItems\":6,\n \"maxItems\":6\n - \ },\n {\n \"minItems\":9,\n \"maxItems\":9\n - \ }\n ],\n \"items\":{\n \"type\":\"number\"\n - \ }\n }\n },\n \"patternProperties\": {\n \"^(?!proj:)\": - {}\n },\n \"additionalProperties\": false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: - Connection: - - close - Host: - - stac-extensions.github.io - method: GET - uri: https://stac-extensions.github.io/view/v1.0.0/schema.json - response: - body: - string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json#\",\n \"title\": - \"View Geometry Extension\",\n \"description\": \"STAC View Geometry Extension - for STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": - \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"type\": - \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n - \ \"assets\"\n ],\n \"properties\": {\n \"type\": - {\n \"const\": \"Feature\"\n },\n \"properties\": - {\n \"allOf\": [\n {\n \"anyOf\": - [\n {\"required\": [\"view:off_nadir\"]},\n {\"required\": - [\"view:incidence_angle\"]},\n {\"required\": [\"view:azimuth\"]},\n - \ {\"required\": [\"view:sun_azimuth\"]},\n {\"required\": - [\"view:sun_elevation\"]}\n ]\n },\n {\n - \ \"$ref\": \"#/definitions/fields\"\n }\n - \ ]\n },\n \"assets\": {\n \"type\": - \"object\",\n \"additionalProperties\": {\n \"$ref\": - \"#/definitions/fields\"\n }\n }\n }\n },\n - \ {\n \"$ref\": \"#/definitions/stac_extensions\"\n }\n - \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC - Collections.\",\n \"allOf\": [\n {\n \"type\": \"object\",\n - \ \"required\": [\n \"type\"\n ],\n \"properties\": - {\n \"type\": {\n \"const\": \"Collection\"\n },\n - \ \"assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n },\n - \ \"item_assets\": {\n \"type\": \"object\",\n \"additionalProperties\": - {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n - \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n - \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\": - {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n - \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\": - \"array\",\n \"contains\": {\n \"contains\": {\n \"const\": - \"https://stac-extensions.github.io/view/v1.0.0/schema.json\"\n }\n - \ }\n }\n }\n },\n \"fields\": {\n \"$comment\": - \"Add your new fields here. Don't require them here, do that above in the - item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"view:off_nadir\": - {\n \"title\": \"Off Nadir\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:incidence_angle\": - {\n \"title\": \"Center incidence angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 90\n },\n \"view:azimuth\": - {\n \"title\": \"Azimuth angle\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_azimuth\": - {\n \"title\": \"Sun Azimuth\",\n \"type\": \"number\",\n - \ \"minimum\": 0,\n \"maximum\": 360\n },\n \"view:sun_elevation\": - {\n \"title\": \"Sun Elevation\",\n \"type\": \"number\",\n - \ \"minimum\": -90,\n \"maximum\": 90\n }\n },\n - \ \"patternProperties\": {\n \"^(?!view:)\": {}\n },\n \"additionalProperties\": - false\n }\n }\n}" - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_examples/test_example_read_file[path10].yaml b/tests/cassettes/test_examples/test_example_read_file[path6].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_read_file[path10].yaml rename to tests/cassettes/test_examples/test_example_read_file[path6].yaml diff --git a/tests/cassettes/test_examples/test_example_read_file[path19].yaml b/tests/cassettes/test_examples/test_example_read_file[path8].yaml similarity index 100% rename from tests/cassettes/test_examples/test_example_read_file[path19].yaml rename to tests/cassettes/test_examples/test_example_read_file[path8].yaml diff --git a/tests/test_examples.py b/tests/test_examples.py index 96f7ab040..0c6b858ce 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -6,8 +6,10 @@ import pystac from pystac.reader import StandardLibraryReader -EXAMPLE_FILES = list( - (Path(__file__).parent / "data-files" / "examples").glob("**/*.json") +EXAMPLE_FILES = sorted( + list( + (Path(__file__).parent / "data-files" / "examples").glob("**/*.json"), + ) ) From 5aa4f2727c7785b1eea5673c1aee2fce7e46cac7 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Fri, 6 Mar 2026 09:06:49 -0500 Subject: [PATCH 50/60] feat: V2 Bands (#1635) * Add bands as a top-level constuct * Add repr for bands --- src/pystac/__init__.py | 2 + src/pystac/asset.py | 16 ++++++- src/pystac/band.py | 62 ++++++++++++++++++++++++++ src/pystac/collection.py | 8 ++++ src/pystac/item.py | 18 ++++++-- tests/test_band.py | 94 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 src/pystac/band.py create mode 100644 tests/test_band.py diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py index 0ef340657..a19849bb4 100644 --- a/src/pystac/__init__.py +++ b/src/pystac/__init__.py @@ -3,6 +3,7 @@ from typing_extensions import deprecated from .asset import Asset +from .band import Band from .catalog import Catalog from .collection import ( Collection, @@ -61,6 +62,7 @@ def get_stac_version() -> str: __all__ = [ "Asset", + "Band", "Catalog", "Collection", "CommonMetadata", diff --git a/src/pystac/asset.py b/src/pystac/asset.py index d12f858d7..d974c876f 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -5,6 +5,7 @@ from typing_extensions import deprecated +from .band import Band from .media_type import MediaType from .utils import is_absolute_href, make_absolute_href, make_relative_href from .writer import Writer @@ -20,12 +21,17 @@ def __init__( description: str | None = None, type: str | None = None, roles: list[str] | None = None, + bands: list[Band] | list[dict[str, Any]] | None = None, **kwargs: Any, ): self.title: str | None = title self.description: str | None = description self.type: str | None = type self.roles: list[str] | None = roles + if bands is not None: + self.bands: list[Band] | None = [Band.try_from(band) for band in bands] + else: + self.bands = None self.extra_fields: dict[str, Any] = kwargs @classmethod @@ -54,6 +60,8 @@ def to_dict(self) -> dict[str, Any]: data["type"] = self.type if self.roles is not None: data["roles"] = self.roles + if self.bands is not None: + data["bands"] = [band.to_dict() for band in self.bands] return data @@ -65,10 +73,16 @@ def __init__( description: str | None = None, type: str | None = None, roles: list[str] | None = None, + bands: list[Band] | list[dict[str, Any]] | None = None, **kwargs: Any, ): super().__init__( - title=title, description=description, type=type, roles=roles, **kwargs + title=title, + description=description, + type=type, + roles=roles, + bands=bands, + **kwargs, ) self.href: str = href diff --git a/src/pystac/band.py b/src/pystac/band.py new file mode 100644 index 000000000..837161600 --- /dev/null +++ b/src/pystac/band.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import copy +from typing import Any, override + + +class Band: + def __init__( + self, + name: str | None = None, + **kwargs: Any, + ): + self.name: str | None = name + self.extra_fields: dict[str, Any] = kwargs + + def __getitem__(self, key: str) -> Any: + if hasattr(self, key): + return getattr(self, key) + else: + return self.extra_fields[key] + + def __setitem__(self, key: str, value: Any) -> None: + if hasattr(self, key): + setattr(self, key, value) + else: + self.extra_fields[key] = value + + def __contains__(self, key: str) -> bool: + return key == "name" or key in self.extra_fields + + def __deepcopy__(self, memo: dict[int, Any]) -> Band: + return Band( + name=self.name, + **copy.deepcopy(self.extra_fields, memo), + ) + + @override + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Band): + return NotImplemented + return self.to_dict() == other.to_dict() + + @override + def __repr__(self) -> str: + return f"Band(name={self.name!r})" + + @classmethod + def try_from(cls, data: dict[str, Any] | Band) -> Band: + if isinstance(data, Band): + return data + else: + return cls(**data) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Band: + return cls(**data) + + def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) + if self.name: + data["name"] = self.name + return data diff --git a/src/pystac/collection.py b/src/pystac/collection.py index c06b68882..df5578ebb 100644 --- a/src/pystac/collection.py +++ b/src/pystac/collection.py @@ -7,6 +7,7 @@ from typing_extensions import deprecated from .asset import Asset, Assets, ItemAsset +from .band import Band from .constants import DEFAULT_LICENSE, DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE from .container import Container from .link import Link @@ -46,6 +47,7 @@ def __init__( stac_version: str = DEFAULT_STAC_VERSION, stac_extensions: list[str] | None = None, links: list[Link] | list[dict[str, Any]] | None = None, + bands: list[Band] | list[dict[str, Any]] | None = None, **kwargs: Any, ) -> None: super().__init__(type, id, stac_version, stac_extensions, links, **kwargs) @@ -58,6 +60,10 @@ def __init__( if providers is not None else None ) + if bands is not None: + self.bands: list[Band] | None = [Band.try_from(band) for band in bands] + else: + self.bands = None self.extent: Extent = Extent.try_from(extent) self.summaries: dict[str, Any] | None = summaries self.assets: dict[str, Asset] = ( @@ -108,6 +114,8 @@ def to_dict( data["extent"] = self.extent.to_dict() if self.summaries: data["summaries"] = self.summaries + if self.bands is not None: + data["bands"] = [band.to_dict() for band in self.bands] if self.assets: data["assets"] = { key: asset.to_dict() for key, asset in self.assets.items() diff --git a/src/pystac/item.py b/src/pystac/item.py index 3b38aed1c..19118db5b 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -10,6 +10,7 @@ from pystac.rel_type import RelType from .asset import Asset, Assets +from .band import Band from .constants import DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE from .container import Container from .geo_interface import GeoInterface @@ -218,7 +219,12 @@ def __geo_interface__(self) -> dict[str, Any]: class Properties: - def __init__(self, datetime: dt.datetime | str | None = None, **kwargs: Any): + def __init__( + self, + datetime: dt.datetime | str | None = None, + bands: list[Band] | list[dict[str, Any]] | None = None, + **kwargs: Any, + ): if isinstance(datetime, str): self.datetime: dt.datetime | None = str_to_datetime(datetime) elif isinstance(datetime, dt.datetime): @@ -226,7 +232,10 @@ def __init__(self, datetime: dt.datetime | str | None = None, **kwargs: Any): else: # TODO check for start and end datetime self.datetime = dt.datetime.now(tz=dt.UTC) - + if bands is not None: + self.bands: list[Band] | None = [Band.try_from(band) for band in bands] + else: + self.bands = None self.extra_fields: dict[str, Any] = kwargs def __getitem__(self, key: str) -> Any: @@ -236,11 +245,12 @@ def __setitem__(self, name: str, value: Any, /) -> None: self.extra_fields[name] = value def __contains__(self, key: str) -> bool: - return key == "datetime" or key in self.extra_fields + return key in ("datetime", "bands") or key in self.extra_fields def __deepcopy__(self, memo: dict[int, Any]) -> Properties: return Properties( datetime=self.datetime, + bands=self.bands, **copy.deepcopy(self.extra_fields, memo), ) @@ -263,4 +273,6 @@ def from_dict(cls, data: dict[str, Any]) -> Properties: def to_dict(self) -> dict[str, Any]: data = copy.deepcopy(self.extra_fields) data["datetime"] = datetime_to_str(self.datetime) if self.datetime else None + if self.bands is not None: + data["bands"] = [band.to_dict() for band in self.bands] return data diff --git a/tests/test_band.py b/tests/test_band.py new file mode 100644 index 000000000..e2f25b8b9 --- /dev/null +++ b/tests/test_band.py @@ -0,0 +1,94 @@ +import pytest + +from pystac import Asset, Band, Collection, Item + + +@pytest.mark.parametrize( + "data", [{"name": "B01"}, {}, {"name": "B01", "eo:common_name": "blue"}] +) +def test_roundtrip_to_dict(data: dict[str, str]) -> None: + band = Band(**data) + assert band.to_dict() == data + + +def test_get_band_fields() -> None: + band = Band(name="foo", field="one") + assert band["name"] == "foo" + assert band["field"] == "one" + assert "field" in band + assert band.extra_fields["field"] == "one" + + +def test_set_band_fields() -> None: + band = Band() + band["field"] = "two" + assert "field" in band + assert band["field"] == "two" + assert band.extra_fields["field"] == "two" + + +def test_set_band_name() -> None: + band = Band() + band.name = "B01" + assert band.name == "B01" + assert band["name"] == "B01" + + band["name"] = "B02" + assert band.name == "B02" + assert band["name"] == "B02" + + +def test_band_repr() -> None: + band = Band(name="foo", field="one") + assert str(band) == "Band(name='foo')" + + +def test_set_bands_on_asset() -> None: + asset = Asset(href="foo", bands=[Band(name="B01")]) + assert asset.bands == [Band(name="B01")] + + +def test_set_bands_on_asset_as_dict() -> None: + asset = Asset(href="foo", bands=[{"name": "B01"}]) + assert asset.bands == [Band(name="B01")] + + +def test_asset_to_dict_includes_bands() -> None: + asset = Asset(href="foo", bands=[Band("B01")]) + asset_dict = asset.to_dict() + assert "bands" in asset_dict + assert asset_dict["bands"] == [{"name": "B01"}] + + +def test_set_bands_on_item() -> None: + item = Item(id="foo", properties={"bands": [Band("B01")]}) + assert item.properties.bands == [Band("B01")] + + +def test_set_bands_on_item_as_dict() -> None: + item = Item(id="foo", properties={"bands": [{"name": "B01"}]}) + assert item.properties.bands == [Band("B01")] + + +def test_item_to_dict_includes_bands() -> None: + item = Item(id="foo", properties={"bands": [Band("B01")]}) + properties_dict = item.to_dict()["properties"] + assert "bands" in properties_dict + assert properties_dict["bands"] == [{"name": "B01"}] + + +def test_set_bands_on_collection() -> None: + collection = Collection(id="foo", description="foo", bands=[Band("B01")]) + assert collection.bands == [Band("B01")] + + +def test_set_bands_on_collection_as_dict() -> None: + collection = Collection(id="foo", description="foo", bands=[{"name": "B01"}]) + assert collection.bands == [Band("B01")] + + +def test_collection_to_dict_includes_bands() -> None: + collection = Collection(id="foo", description="foo", bands=[Band("B01")]) + collection_dict = collection.to_dict() + assert "bands" in collection_dict + assert collection_dict["bands"] == [{"name": "B01"}] From a5465761a39d81a03cee5c43640bd52d26fae29d Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 11 Mar 2026 10:00:38 -0400 Subject: [PATCH 51/60] feat: V2 common metadata (#1636) * New implementation of CommonMetadata * Add common metadata protocols to Item and Asset --- src/pystac/asset.py | 23 +- src/pystac/common_metadata.py | 498 ++++++++++++++++++++----------- src/pystac/item.py | 45 +-- src/pystac/provider.py | 8 +- tests/test_common_metadata.py | 69 +++++ tests/v1/test_common_metadata.py | 6 + 6 files changed, 446 insertions(+), 203 deletions(-) create mode 100644 tests/test_common_metadata.py diff --git a/src/pystac/asset.py b/src/pystac/asset.py index d974c876f..9b2511425 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -6,6 +6,7 @@ from typing_extensions import deprecated from .band import Band +from .common_metadata import DataValue, Instrument from .media_type import MediaType from .utils import is_absolute_href, make_absolute_href, make_relative_href from .writer import Writer @@ -14,7 +15,7 @@ from .stac_object import STACObject -class ItemAsset: +class ItemAsset(DataValue, Instrument): def __init__( self, title: str | None = None, @@ -51,18 +52,15 @@ def clone[T: ItemAsset](self: T) -> T: return self.from_dict(self.to_dict()) def to_dict(self) -> dict[str, Any]: - data = copy.deepcopy(self.extra_fields) - if self.title is not None: - data["title"] = self.title - if self.description is not None: - data["description"] = self.description - if self.type is not None: - data["type"] = self.type - if self.roles is not None: - data["roles"] = self.roles + data: dict[str, Any] = copy.deepcopy(self.extra_fields) + data["title"] = self.title + data["description"] = self.description + data["type"] = self.type + data["roles"] = self.roles + data["statistics"] = self.statistics.to_dict() or None if self.bands is not None: data["bands"] = [band.to_dict() for band in self.bands] - return data + return {k: v for k, v in data.items() if v is not None} class Asset(ItemAsset): @@ -117,8 +115,7 @@ def get_absolute_href(self) -> str | None: @override def to_dict(self) -> dict[str, Any]: data = super().to_dict() - data["href"] = self.href - return data + return {"href": self.href, **data} class Assets(Protocol): diff --git a/src/pystac/common_metadata.py b/src/pystac/common_metadata.py index b3046db27..134b36d96 100644 --- a/src/pystac/common_metadata.py +++ b/src/pystac/common_metadata.py @@ -1,251 +1,407 @@ from __future__ import annotations -from datetime import datetime -from typing import TYPE_CHECKING, Any, TypeVar, cast +import datetime as dt +from dataclasses import dataclass +from enum import StrEnum +from typing import TYPE_CHECKING, Any, Protocol, override -import pystac -from pystac import utils -from pystac.errors import STACError +from .provider import Provider +from .utils import datetime_to_str, str_to_datetime if TYPE_CHECKING: - from pystac.asset import Asset - from pystac.item import Item - from pystac.provider import Provider + from .asset import Asset + from .item import Item -P = TypeVar("P") +class Basics(Protocol): + """Protocol for basic descriptive fields.""" + extra_fields: dict[str, Any] -class CommonMetadata: - """Object containing fields that are not included in core item schema but - are still commonly used. All attributes are defined within the properties of - this item and are optional - - Args: - properties : Dictionary of attributes that is the Item's properties - """ - - object: Asset | Item - """The object from which common metadata is obtained.""" - - def __init__(self, object: Asset | Item): - self.object = object - - def _set_field(self, prop_name: str, v: Any | None) -> None: - if hasattr(self.object, prop_name): - setattr(self.object, prop_name, v) - elif hasattr(self.object, "properties"): - item = cast(pystac.Item, self.object) - if v is None: - item.properties.pop(prop_name, None) - else: - item.properties[prop_name] = v - elif hasattr(self.object, "extra_fields") and isinstance( - self.object.extra_fields, dict - ): - if v is None: - self.object.extra_fields.pop(prop_name, None) - else: - self.object.extra_fields[prop_name] = v - else: - raise pystac.STACError(f"Cannot set field {prop_name} on {self}.") - - def _get_field(self, prop_name: str, _typ: type[P]) -> P | None: - if hasattr(self.object, prop_name): - return cast(P | None, getattr(self.object, prop_name)) - elif hasattr(self.object, "properties"): - item = cast(pystac.Item, self.object) - return item.properties.get(prop_name) - elif hasattr(self.object, "extra_fields") and isinstance( - self.object.extra_fields, dict - ): - return self.object.extra_fields.get(prop_name) - else: - raise STACError(f"Cannot get field {prop_name} from {self}.") - - # Basics @property def title(self) -> str | None: - """Gets or set the object's title.""" - return self._get_field("title", str) + """A human readable title describing the STAC entity.""" + return self.extra_fields.get("title") @title.setter - def title(self, v: str | None) -> None: - self._set_field("title", v) + def title(self, value: str | None) -> None: + self.extra_fields["title"] = value @property def description(self) -> str | None: - """Gets or set the object's description.""" - return self._get_field("description", str) + """ + Detailed multi-line description to fully explain the STAC entity. + CommonMark 0.29 syntax MAY be used for rich text representation. + """ + return self.extra_fields.get("description") @description.setter - def description(self, v: str | None) -> None: - if v == "": + def description(self, value: str | None) -> None: + if value == "": raise ValueError("description cannot be an empty string") - self._set_field("description", v) + self.extra_fields["description"] = value - # Date and Time Range @property - def start_datetime(self) -> datetime | None: - """Get or set the object's start_datetime. + def keywords(self) -> list[str] | None: + """List of keywords describing the STAC entity.""" + return self.extra_fields.get("keywords") + + @keywords.setter + def keywords(self, value: list[str] | None) -> None: + self.extra_fields["keywords"] = value - Note: - ``start_datetime`` is an inclusive datetime. + @property + def roles(self) -> list[str] | None: """ - return utils.map_opt( - utils.str_to_datetime, self._get_field("start_datetime", str) - ) + The semantic roles of the entity, e.g. for assets, + links, providers, bands, etc. + """ + return self.extra_fields.get("roles") - @start_datetime.setter - def start_datetime(self, v: datetime | None) -> None: - self._set_field("start_datetime", utils.map_opt(utils.datetime_to_str, v)) + @roles.setter + def roles(self, value: list[str] | None) -> None: + self.extra_fields["roles"] = value + + +class DateTime(Protocol): + """Protocol for date and time fields.""" + + extra_fields: dict[str, Any] + + @property + def datetime(self) -> dt.datetime | None: + """See the Item Specification Fields for more information.""" + value: str | None = self.extra_fields.get("datetime") + datetime: dt.datetime | None = None + if value is not None: + datetime = str_to_datetime(value) + return datetime + + @datetime.setter + def datetime(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] + datetime: str | None = None + if isinstance(value, str): + datetime = value + elif isinstance(value, dt.datetime): + datetime = datetime_to_str(value) + self.extra_fields["datetime"] = datetime @property - def end_datetime(self) -> datetime | None: - """Get or set the item's end_datetime. + def created(self) -> dt.datetime | None: + """Creation date and time of the corresponding STAC entity or Asset, in UTC.""" + value: str | None = self.extra_fields.get("created") + datetime: dt.datetime | None = None + if value is not None: + datetime = str_to_datetime(value) + return datetime + + @created.setter + def created(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] + datetime: str | None = None + if isinstance(value, str): + datetime = value + elif isinstance(value, dt.datetime): + datetime = datetime_to_str(value) + self.extra_fields["created"] = datetime - Note: - ``end_datetime`` is an inclusive datetime. + @property + def updated(self) -> dt.datetime | None: """ - return utils.map_opt( - utils.str_to_datetime, self._get_field("end_datetime", str) - ) + Date and time the corresponding STAC entity or Asset + was updated last, in UTC. + """ + value: str | None = self.extra_fields.get("updated") + datetime: dt.datetime | None = None + if value is not None: + datetime = str_to_datetime(value) + return datetime + + @updated.setter + def updated(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] + datetime: str | None = None + if isinstance(value, str): + datetime = value + elif isinstance(value, dt.datetime): + datetime = datetime_to_str(value) + self.extra_fields["updated"] = datetime + + @property + def start_datetime(self) -> dt.datetime | None: + """The first or start date and time for the resource, in UTC.""" + value: str | None = self.extra_fields.get("start_datetime") + datetime: dt.datetime | None = None + if value is not None: + datetime = str_to_datetime(value) + return datetime + + @start_datetime.setter + def start_datetime(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] + datetime: str | None = None + if isinstance(value, str): + datetime = value + elif isinstance(value, dt.datetime): + datetime = datetime_to_str(value) + self.extra_fields["start_datetime"] = datetime + + @property + def end_datetime(self) -> dt.datetime | None: + """The last or end date and time for the resource, in UTC.""" + value: str | None = self.extra_fields.get("end_datetime") + datetime: dt.datetime | None = None + if value is not None: + datetime = str_to_datetime(value) + return datetime @end_datetime.setter - def end_datetime(self, v: datetime | None) -> None: - self._set_field("end_datetime", utils.map_opt(utils.datetime_to_str, v)) + def end_datetime(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] + datetime: str | None = None + if isinstance(value, str): + datetime = value + elif isinstance(value, dt.datetime): + datetime = datetime_to_str(value) + self.extra_fields["end_datetime"] = datetime + + +class Licensing(Protocol): + """Protocol for information about the license(s) of the data""" + + extra_fields: dict[str, Any] - # License @property def license(self) -> str | None: - """Get or set the current license. License should be provided - as a `SPDX License identifier `_, - or `other`. If object includes data with multiple - different licenses, use `other` and add a link for - each. - - Note: - The licenses `various` and `proprietary` are deprecated. - """ - return self._get_field("license", str) + """License(s) of the data that the STAC entity provides.""" + return self.extra_fields.get("license") @license.setter - def license(self, v: str | None) -> None: - self._set_field("license", v) + def license(self, value: str | None) -> None: + self.extra_fields["license"] = value + + +class Providers(Protocol): + """Protocol for nformation about the organizations capturing, producing, + processing, hosting or publishing this data.""" + + extra_fields: dict[str, Any] - # Providers @property def providers(self) -> list[Provider] | None: - """Get or set a list of the object's providers.""" - return utils.map_opt( - lambda providers: [pystac.Provider.from_dict(d) for d in providers], - self._get_field("providers", list[dict[str, Any]]), - ) + """Provider(s) of the data that the STAC entity provides.""" + return [ + Provider.try_from(p) for p in self.extra_fields.get("providers", []) + ] or None @providers.setter - def providers(self, v: list[Provider] | None) -> None: - self._set_field( - "providers", - utils.map_opt(lambda providers: [p.to_dict() for p in providers], v), - ) + def providers(self, value: list[Provider] | None) -> None: + providers: list[dict[str, Any]] | None = None + if isinstance(value, list): + providers = [Provider.try_from(p).to_dict() for p in value] + self.extra_fields["providers"] = providers + + +class Instrument(Protocol): + """Protocol for instrument metadata fields.""" + + extra_fields: dict[str, Any] - # Instrument @property def platform(self) -> str | None: - """Gets or set the object's platform attribute.""" - return self._get_field("platform", str) + """Unique name of the specific platform to which the instrument is attached.""" + return self.extra_fields.get("platform") @platform.setter - def platform(self, v: str | None) -> None: - self._set_field("platform", v) + def platform(self, value: str | None) -> None: + self.extra_fields["platform"] = value @property def instruments(self) -> list[str] | None: - """Gets or sets the names of the instruments used.""" - return self._get_field("instruments", list[str]) + """Name of instrument or sensor used (e.g., MODIS, ASTER, OLI, Canon F-1).""" + return self.extra_fields.get("instruments") @instruments.setter - def instruments(self, v: list[str] | None) -> None: - self._set_field("instruments", v) + def instruments(self, value: list[str] | None) -> None: + self.extra_fields["instruments"] = value @property def constellation(self) -> str | None: - """Gets or set the name of the constellation associate with an object.""" - return self._get_field("constellation", str) + """Name of the constellation to which the platform belongs.""" + return self.extra_fields.get("constellation") @constellation.setter - def constellation(self, v: str | None) -> None: - self._set_field("constellation", v) + def constellation(self, value: str | None) -> None: + self.extra_fields["constellation"] = value @property def mission(self) -> str | None: - """Gets or set the name of the mission associated with an object.""" - return self._get_field("mission", str) + """Name of the mission for which data is collected.""" + return self.extra_fields.get("mission") @mission.setter - def mission(self, v: str | None) -> None: - self._set_field("mission", v) + def mission(self, value: str | None) -> None: + self.extra_fields["mission"] = value @property - def gsd(self) -> float | None: - """Gets or sets the Ground Sample Distance at the sensor.""" - return self._get_field("gsd", float) + def gsd(self) -> int | None: + """ + Ground Sample Distance at the sensor, in meters (m), + must be greater than 0. + """ + return self.extra_fields.get("gsd") @gsd.setter - def gsd(self, v: float | None) -> None: - self._set_field("gsd", v) + def gsd(self, value: int | None) -> None: + self.extra_fields["gsd"] = value + + +class DataType(StrEnum): + int8 = "int8" + int16 = "int16" + int32 = "int32" + int64 = "int64" + uint8 = "uint8" + uint16 = "uint16" + uint32 = "uint32" + uint64 = "uint64" + float16 = "float16" + float32 = "float32" + float64 = "float64" + cint16 = "cint16" + cint32 = "cint32" + cfloat32 = "cfloat32" + cfloat64 = "cfloat64" + other = "other" + + @override + def __repr__(self): + return self.value + + +@dataclass +class Statistics: + statistics: dict[str, Any] + + @property + def minimum(self) -> float | None: + return self.statistics.get("minimum") + + @minimum.setter + def minimum(self, value: float | None) -> None: + self.statistics["minimum"] = value + + @property + def maximum(self) -> float | None: + return self.statistics.get("maximum") + + @maximum.setter + def maximum(self, value: float | None) -> None: + self.statistics["maximum"] = value + + @property + def mean(self) -> float | None: + return self.statistics.get("mean") + + @mean.setter + def mean(self, value: float | None) -> None: + self.statistics["mean"] = value - # Metadata @property - def created(self) -> datetime | None: - """Get or set the metadata file's creation date. All datetime attributes have - setters that can take either a string or a datetime, but always stores - the attribute as a string. + def stddev(self) -> float | None: + return self.statistics.get("stddev") + + @stddev.setter + def stddev(self, value: float | None) -> None: + self.statistics["stddev"] = value + + @property + def count(self) -> int | None: + return self.statistics.get("count") + + @count.setter + def count(self, value: int | None) -> None: + self.statistics["count"] = value + + @property + def valid_percent(self) -> float | None: + return self.statistics.get("valid_percent") + + @valid_percent.setter + def valid_percent(self, value: float | None) -> None: + self.statistics["valid_percent"] = value - Note: - ``created`` has a different meaning depending on the type of STAC object. - On an :class:`~pystac.Item`, it refers to the creation time of the - metadata. On an :class:`~pystac.Asset`, it refers to the creation time of - the actual data linked to in :attr:`~pystac.Asset.href`. + def to_dict(self) -> dict[str, Any]: + return {k: v for k, v in self.statistics.items() if v is not None} + + +class DataValue(Protocol): + extra_fields: dict[str, Any] + + @property + def nodata(self) -> float | str | None: """ - return utils.map_opt(utils.str_to_datetime, self._get_field("created", str)) + The no-data value must be provided either as: - @created.setter - def created(self, v: datetime | None) -> None: - self._set_field("created", utils.map_opt(utils.datetime_to_str, v)) + - a number + - a string: + nan - NaN (not a number) as defined in IEEE-754 + inf - Positive Infinity + -inf - Negative Infinity + """ + return self.extra_fields.get("nodata") + + @nodata.setter + def nodata(self, value: float | str | None) -> None: + self.extra_fields["nodata"] = value @property - def updated(self) -> datetime | None: - """Get or set the metadata file's update date. All datetime attributes have - setters that can take either a string or a datetime, but always stores - the attribute as a string + def unit(self) -> str | None: + """ + It is STRONGLY RECOMMENDED to provide units in one of the following two formats: - Note: - ``updated`` has a different meaning depending on the type of STAC object. - On an :class:`~pystac.Item`, it refers to the update time of the - metadata. On an :class:`~pystac.Asset`, it refers to the update time of - the actual data linked to in :attr:`~pystac.Asset.href`. + UCUM: The unit code that is compliant to the UCUM specification. + UDUNITS-2: The unit symbol if available, otherwise the singular unit name. """ - return utils.map_opt(utils.str_to_datetime, self._get_field("updated", str)) + return self.extra_fields.get("unit") - @updated.setter - def updated(self, v: datetime | None) -> None: - self._set_field("updated", utils.map_opt(utils.datetime_to_str, v)) + @unit.setter + def unit(self, value: str | None) -> None: + self.extra_fields["unit"] = value @property - def keywords(self) -> list[str] | None: - """Get or set the keywords describing the STAC entity.""" - return self._get_field("keywords", list[str]) + def data_type(self) -> DataType | str | None: + return self.extra_fields.get("data_type") - @keywords.setter - def keywords(self, v: list[str] | None) -> None: - self._set_field("keywords", v) + @data_type.setter + def data_type(self, value: DataType | str | None) -> None: + self.extra_fields["data_type"] = DataType(value) @property - def roles(self) -> list[str] | None: - """Get or set the semantic roles of the entity.""" - return self._get_field("roles", list[str]) + def statistics(self) -> Statistics: + if not self.extra_fields.get("statistics"): + statistics: dict[str, Any] = {} + self.extra_fields["statistics"] = statistics + return Statistics(self.extra_fields["statistics"]) - @roles.setter - def roles(self, v: list[str] | None) -> None: - self._set_field("roles", v) + +class CommonMetadata(Basics, DateTime, Licensing, Providers, Instrument, DataValue): + object: Asset | Item + """The object from which common metadata is obtained.""" + + def __init__(self, /, object: Asset | Item): + self.object = object + + @property + @override + def extra_fields(self) -> dict[str, Any]: # pyright: ignore[reportIncompatibleVariableOverride] + from pystac import Item + + if isinstance(self.object, Item): + return self.object.properties.extra_fields + return self.object.extra_fields + + @override + def __repr__(self): + fields: str = ", ".join( + f"{k}={v}" for k, v in self.extra_fields.items() if v is not None + ) + if len(fields) > 100: + fields = f"{fields[:50]}...{fields[-50:]}" + return f"" diff --git a/src/pystac/item.py b/src/pystac/item.py index 19118db5b..8abd85d54 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -3,7 +3,7 @@ import copy import datetime as dt import warnings -from typing import TYPE_CHECKING, Any, ClassVar, override +from typing import TYPE_CHECKING, Any, ClassVar, final, override from typing_extensions import deprecated @@ -11,6 +11,14 @@ from .asset import Asset, Assets from .band import Band +from .common_metadata import ( + Basics, + CommonMetadata, + DateTime, + Instrument, + Licensing, + Providers, +) from .constants import DEFAULT_STAC_VERSION, STAC_OBJECT_TYPE from .container import Container from .geo_interface import GeoInterface @@ -67,10 +75,8 @@ def __init__( self.bbox: list[float | int] | None = list(bbox) if bbox else None self.properties: Properties = Properties.try_from(properties) - if isinstance(datetime, dt.datetime): + if datetime: self.properties.datetime = datetime - elif isinstance(datetime, str): - self.properties.datetime = str_to_datetime(datetime) if assets: self.assets: dict[str, Asset] = {} @@ -117,7 +123,7 @@ def get_datetime(self, asset: Asset | None = None) -> dt.datetime | None: if asset and (datetime := asset.extra_fields.get("datetime")): return str_to_datetime(datetime) else: - return self.datetime + return self.properties.datetime @deprecated("Set the datetime on the asset directly") def set_datetime(self, datetime: dt.datetime, asset: Asset | None = None) -> None: @@ -131,6 +137,11 @@ def set_self_href(self, href: str | None) -> None: Asset.update_hrefs(self.assets, self.get_self_href(), href) return super().set_self_href(href) + @property + @deprecated("Access common metadata fields directly on properties") + def common_metadata(self): + return CommonMetadata(self) + def get_collection(self) -> Collection | None: from .collection import Collection @@ -218,25 +229,25 @@ def __geo_interface__(self) -> dict[str, Any]: return self.to_dict(include_self_link=False) -class Properties: +@final +class Properties(Basics, DateTime, Licensing, Providers, Instrument): def __init__( self, + *, datetime: dt.datetime | str | None = None, + start_datetime: dt.datetime | str | None = None, + end_datetime: dt.datetime | str | None = None, bands: list[Band] | list[dict[str, Any]] | None = None, **kwargs: Any, ): - if isinstance(datetime, str): - self.datetime: dt.datetime | None = str_to_datetime(datetime) - elif isinstance(datetime, dt.datetime): - self.datetime = datetime - else: - # TODO check for start and end datetime - self.datetime = dt.datetime.now(tz=dt.UTC) + self.extra_fields: dict[str, Any] = kwargs + self.datetime = datetime or dt.datetime.now(tz=dt.UTC) + self.start_datetime = start_datetime + self.end_datetime = end_datetime if bands is not None: self.bands: list[Band] | None = [Band.try_from(band) for band in bands] else: self.bands = None - self.extra_fields: dict[str, Any] = kwargs def __getitem__(self, key: str) -> Any: return self.extra_fields[key] @@ -245,11 +256,10 @@ def __setitem__(self, name: str, value: Any, /) -> None: self.extra_fields[name] = value def __contains__(self, key: str) -> bool: - return key in ("datetime", "bands") or key in self.extra_fields + return key == "bands" or key in self.extra_fields def __deepcopy__(self, memo: dict[int, Any]) -> Properties: return Properties( - datetime=self.datetime, bands=self.bands, **copy.deepcopy(self.extra_fields, memo), ) @@ -272,7 +282,6 @@ def from_dict(cls, data: dict[str, Any]) -> Properties: def to_dict(self) -> dict[str, Any]: data = copy.deepcopy(self.extra_fields) - data["datetime"] = datetime_to_str(self.datetime) if self.datetime else None if self.bands is not None: data["bands"] = [band.to_dict() for band in self.bands] - return data + return {k: v for k, v in data.items() if v is not None} diff --git a/src/pystac/provider.py b/src/pystac/provider.py index b1e679192..e520ca67b 100644 --- a/src/pystac/provider.py +++ b/src/pystac/provider.py @@ -2,7 +2,7 @@ import copy from enum import StrEnum -from typing import Any +from typing import Any, override class Provider: @@ -20,6 +20,12 @@ def __init__( self.url: str | None = url self.extra_fields: dict[str, Any] = kwargs + @override + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Provider): + return NotImplemented + return self.to_dict() == other.to_dict() + @classmethod def try_from(cls, data: Provider | dict[str, Any]) -> Provider: if isinstance(data, Provider): diff --git a/tests/test_common_metadata.py b/tests/test_common_metadata.py new file mode 100644 index 000000000..c4a9a40ca --- /dev/null +++ b/tests/test_common_metadata.py @@ -0,0 +1,69 @@ +from typing import Any, final + +import pytest + +from pystac import Asset +from pystac.common_metadata import DataValue + + +@final +class Common(DataValue): + def __init__(self, **kwargs: Any): + self.extra_fields = kwargs + + +def test_data_type_set_to_invalid_value() -> None: + common = Common() + with pytest.raises(ValueError, match="'bad' is not a valid DataType"): + common.data_type = "bad" + + +def test_data_type_repr() -> None: + common = Common() + common.data_type = "int8" + assert common.data_type.__repr__() == "int8" + + +def test_setting_statistics_directly_raises() -> None: + common = Common() + with pytest.raises(AttributeError): + common.statistics = {} # pyright: ignore [reportAttributeAccessIssue] + + +def test_get_data_value_common_fields_on_asset() -> None: + asset = Asset(href="foo", data_type="int8", statistics=dict(minimum=3, maximum=5)) + assert asset.data_type == "int8" + assert asset.statistics.minimum == 3 + assert asset.statistics.maximum == 5 + assert asset.extra_fields == { + "data_type": "int8", + "statistics": { + "minimum": 3, + "maximum": 5, + }, + } + + +def test_set_data_value_common_fields_on_asset() -> None: + asset = Asset(href="foo") + asset.data_type = "int8" + asset.nodata = None + asset.statistics.minimum = 3 + asset.statistics.maximum = 5 + asset.statistics.count = None + + asset_dict = asset.to_dict() + assert asset_dict == { + "href": "foo", + "data_type": "int8", + "statistics": { + "minimum": 3, + "maximum": 5, + }, + } + + +def test_get_instrument_common_fields_on_asset() -> None: + asset = Asset(href="foo", gsd=60) + assert asset.gsd == 60 + assert asset.extra_fields == {"gsd": 60} diff --git a/tests/v1/test_common_metadata.py b/tests/v1/test_common_metadata.py index 579d6c1ab..c2c0edfb5 100644 --- a/tests/v1/test_common_metadata.py +++ b/tests/v1/test_common_metadata.py @@ -8,6 +8,9 @@ from .utils import TestCases +pytestmark = pytest.mark.passing_v2 + + @pytest.fixture def date_time_range_item() -> Item: return Item.from_file( @@ -235,6 +238,7 @@ def item(self) -> Item: TestCases.get_path("data-files/item/sample-item-asset-properties.json") ) + @pytest.mark.xfail(reason="title not part of common metadata for assets in pystac v2") def test_title(self, item: Item) -> None: cm = item.common_metadata analytic = item.assets["analytic"] @@ -256,6 +260,7 @@ def test_title(self, item: Item) -> None: assert analytic_cm.title == set_value assert analytic.to_dict()["title"] == set_value + @pytest.mark.xfail(reason="description not part of common metadata for assets in pystac v2") def test_description(self, item: Item) -> None: cm = item.common_metadata analytic = item.assets["analytic"] @@ -541,6 +546,7 @@ def test_keywords(self, item: Item) -> None: assert analytic_cm.keywords == set_value assert analytic.to_dict()["keywords"] == set_value + @pytest.mark.xfail(reason="roles not part of common metadata for assets in pystac v2") def test_roles(self, item: Item) -> None: cm = item.common_metadata analytic = item.assets["analytic"] From 7590b71e8dd2e44dff4b451c758dfc5d87a88f84 Mon Sep 17 00:00:00 2001 From: Ian Cooke Date: Mon, 16 Mar 2026 12:11:33 -0400 Subject: [PATCH 52/60] tests(v2): enable posix paths tests (#1658) --- tests/v1/posix_paths/test_posix_paths.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/v1/posix_paths/test_posix_paths.py b/tests/v1/posix_paths/test_posix_paths.py index fe0e0e36d..1a03d4a2e 100644 --- a/tests/v1/posix_paths/test_posix_paths.py +++ b/tests/v1/posix_paths/test_posix_paths.py @@ -10,10 +10,12 @@ from ..conftest import get_data_file from ..utils import ARBITRARY_BBOX, ARBITRARY_EXTENT, ARBITRARY_GEOM +pytestmark = pytest.mark.passing_v2 + def check_link(link: pystac.Link | None) -> None: assert link is not None - href = link.get_target_str() + href = link.get_href() assert href is not None assert "\\" not in href From 118c6ab7498efa3d4b8258f634583e76ed9e642d Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 18 Mar 2026 15:04:34 -0400 Subject: [PATCH 53/60] feat: V2 asset and item_assets (#1646) Getting `test_asset.py` passing and `test_item_assets` passing with the old `ItemAssetDefinition`. Adds a new `test_item_assets.py` for the new `ItemAsset`. I think we should deprecate `ItemAssetDefiniton` it is unlikely people are really using it since it only came in a year or so ago to replace the old `item_assets` extension. --- src/pystac/__init__.py | 3 +- src/pystac/asset.py | 56 +++- src/pystac/utils.py | 21 +- .../collections/with-item-assets.json | 311 ++++++++++++++++++ tests/test_item_assets.py | 91 +++++ tests/v1/test_asset.py | 13 +- tests/v1/test_item_assets.py | 10 + 7 files changed, 478 insertions(+), 27 deletions(-) create mode 100644 tests/data-files/collections/with-item-assets.json create mode 100644 tests/test_item_assets.py diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py index a19849bb4..151adb641 100644 --- a/src/pystac/__init__.py +++ b/src/pystac/__init__.py @@ -2,7 +2,7 @@ from typing_extensions import deprecated -from .asset import Asset +from .asset import Asset, ItemAsset from .band import Band from .catalog import Catalog from .collection import ( @@ -73,6 +73,7 @@ def get_stac_version() -> str: "RelType", "DEFAULT_STAC_VERSION", "Item", + "ItemAsset", "ItemAssetDefinition", "ItemCollection", "Link", diff --git a/src/pystac/asset.py b/src/pystac/asset.py index 9b2511425..fdf1f07fa 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -8,7 +8,12 @@ from .band import Band from .common_metadata import DataValue, Instrument from .media_type import MediaType -from .utils import is_absolute_href, make_absolute_href, make_relative_href +from .utils import ( + get_absolute_href, + is_absolute_href, + make_absolute_href, + make_relative_href, +) from .writer import Writer if TYPE_CHECKING: @@ -18,6 +23,7 @@ class ItemAsset(DataValue, Instrument): def __init__( self, + *, title: str | None = None, description: str | None = None, type: str | None = None, @@ -35,6 +41,15 @@ def __init__( self.bands = None self.extra_fields: dict[str, Any] = kwargs + @override + def __eq__(self, other: Any) -> bool: + return self.to_dict() == other.to_dict() + + @classmethod + @deprecated("Instantiate the class directly instead") + def create[T: ItemAsset](cls: type[T], **kwargs: Any) -> T: + return cls(**kwargs) + @classmethod def try_from[T: ItemAsset](cls: type[T], data: T | dict[str, Any]) -> T: if isinstance(data, cls): @@ -67,6 +82,7 @@ class Asset(ItemAsset): def __init__( self, href: str, + *, title: str | None = None, description: str | None = None, type: str | None = None, @@ -84,7 +100,7 @@ def __init__( ) self.href: str = href - self._owner: STACObject | None = None + self.owner: STACObject | None = None @staticmethod def update_hrefs( @@ -97,20 +113,12 @@ def update_hrefs( make_absolute_href(asset.href, start_href), end_href ) - @property - def owner(self) -> STACObject | None: - return self._owner - def set_owner(self, owner: STACObject | None) -> None: - self._owner = owner + self.owner = owner def get_absolute_href(self) -> str | None: - if is_absolute_href(self.href): - return self.href - elif self._owner and (owner_href := self._owner.get_self_href()): - return make_absolute_href(self.href, owner_href, False) - else: - return None + owner_href = self.owner.get_self_href() if self.owner else None + return get_absolute_href(self.href, owner_href) @override def to_dict(self) -> dict[str, Any]: @@ -141,15 +149,29 @@ def add_asset(self, key: str, asset: Asset) -> None: self.assets[key] = asset def make_asset_hrefs_relative(self) -> None: - if self.get_self_href(): - # TODO actually implement - pass + if owner_href := self.get_self_href(): + for asset in self.assets.values(): + if is_absolute_href(asset.href, owner_href): + asset.href = make_relative_href(asset.href, owner_href) else: raise ValueError( "Cannot make asset hrefs relative, item does not have a self href" ) - # TODO do we want to deprecate this? I think so... + def make_asset_hrefs_absolute(self) -> None: + if owner_href := self.get_self_href(): + for asset in self.assets.values(): + if not is_absolute_href(asset.href, owner_href): + asset.href = make_absolute_href(asset.href, owner_href) + else: + raise ValueError( + "Cannot make asset hrefs absolute, item does not have a self href" + ) + + @deprecated( + "delete_asset is deprecated, just delete the asset directly from the " + "assets dictionary" + ) def delete_asset(self, key: str) -> None: asset = self.assets[key] try: diff --git a/src/pystac/utils.py b/src/pystac/utils.py index e39eb5736..e2f9e6f9e 100644 --- a/src/pystac/utils.py +++ b/src/pystac/utils.py @@ -8,7 +8,6 @@ from typing import ( Any, Literal, - TypeAlias, TypeVar, cast, ) @@ -21,7 +20,7 @@ from .version import __version__ #: HREF string or path-like object. -HREF: TypeAlias = str | os.PathLike[str] +HREF = str | os.PathLike[str] def get_stac_type(type: STAC_OBJECT_TYPE) -> Literal["Catalog", "Collection", "Item"]: @@ -70,12 +69,9 @@ def safe_urlparse(href: str) -> URLParseResult: return URLParseResult( scheme="", netloc="", - path="{}:{}".format( - # We use this more complicated formulation because parsed.scheme - # converts to lower-case - href[: len(parsed.scheme)], - parsed.path, - ), + # We use this more complicated formulation because parsed.scheme + # converts to lower-case + path=f"{href[: len(parsed.scheme)]}:{parsed.path}", params=parsed.params, query=parsed.query, fragment=parsed.fragment, @@ -415,6 +411,15 @@ def is_absolute_href(href: str, start_href: str | None = None) -> bool: return parsed_start_scheme in ["", "file"] and os.path.isabs(parsed.path) +def get_absolute_href(href: str, start_href: str | None) -> str | None: + if is_absolute_href(href, start_href): + return href + elif start_href: + return make_absolute_href(href, start_href, False) + else: + return None + + def datetime_to_str(dt: datetime, timespec: str = "auto") -> str: """Converts a :class:`datetime.datetime` instance to an ISO8601 string in the `RFC 3339, section 5.6 diff --git a/tests/data-files/collections/with-item-assets.json b/tests/data-files/collections/with-item-assets.json new file mode 100644 index 000000000..237f3bb92 --- /dev/null +++ b/tests/data-files/collections/with-item-assets.json @@ -0,0 +1,311 @@ +{ + "stac_version": "1.1.0", + "stac_extensions": [ + "eo" + ], + "type": "Collection", + "id": "landsat-8-l1", + "title": "Landsat 8 L1", + "description": "Landsat 8 imagery radiometrically calibrated and orthorectified using ground points and Digital Elevation Model (DEM) data to correct relief displacement.", + "keywords": [ + "landsat", + "earth observation", + "usgs" + ], + "extent": { + "spatial": { + "bbox": [ + [ + -180, + -90, + 180, + 90 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2013-06-01T00:00:00Z", + null + ] + ] + } + }, + "providers": [ + { + "name": "USGS", + "roles": [ + "producer" + ], + "url": "https://landsat.usgs.gov/" + }, + { + "name": "Planet Labs", + "roles": [ + "processor" + ], + "url": "https://github.com/landsat-pds/landsat_ingestor" + }, + { + "name": "AWS", + "roles": [ + "host" + ], + "url": "https://landsatonaws.com/" + }, + { + "name": "Development Seed", + "roles": [ + "processor" + ], + "url": "https://github.com/sat-utils/sat-api" + } + ], + "license": "PDDL-1.0", + "summaries": { + "gsd": [ + 15 + ], + "platform": [ + "landsat-8" + ], + "instruments": [ + "oli", + "tirs" + ], + "view:off_nadir": [ + 0 + ], + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + }, + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 0.48, + "full_width_half_max": 0.06 + }, + { + "name": "B3", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.06 + }, + { + "name": "B4", + "common_name": "red", + "center_wavelength": 0.65, + "full_width_half_max": 0.04 + }, + { + "name": "B5", + "common_name": "nir", + "center_wavelength": 0.86, + "full_width_half_max": 0.03 + }, + { + "name": "B6", + "common_name": "swir16", + "center_wavelength": 1.6, + "full_width_half_max": 0.08 + }, + { + "name": "B7", + "common_name": "swir22", + "center_wavelength": 2.2, + "full_width_half_max": 0.2 + }, + { + "name": "B8", + "common_name": "pan", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + }, + { + "name": "B9", + "common_name": "cirrus", + "center_wavelength": 1.37, + "full_width_half_max": 0.02 + }, + { + "name": "B10", + "common_name": "lwir11", + "center_wavelength": 10.9, + "full_width_half_max": 0.8 + }, + { + "name": "B11", + "common_name": "lwir12", + "center_wavelength": 12, + "full_width_half_max": 1 + } + ] + }, + "item_assets": { + "thumbnail": { + "type": "image/jpeg", + "title": "Thumbnail", + "description": "A medium sized thumbnail", + "roles": [ + "thumbnail" + ] + }, + "metadata": { + "type": "mtl", + "roles": [ + "metadata" + ], + "title": "Original Metadata", + "description": "The original MTL metadata file provided for each Landsat scene" + }, + "B1": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02 + } + ], + "title": "Coastal Band (B1)", + "description": "Coastal Band Top Of the Atmosphere" + }, + "B2": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B2", + "common_name": "blue", + "center_wavelength": 0.48, + "full_width_half_max": 0.06 + } + ], + "title": "Blue Band (B2)", + "description": "Blue Band Top Of the Atmosphere" + }, + "B3": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B3", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.06 + } + ], + "title": "Green Band (B3)", + "description": "Green Band (B3) Top Of the Atmosphere" + }, + "B4": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B4", + "common_name": "red", + "center_wavelength": 0.65, + "full_width_half_max": 0.04 + } + ], + "title": "Red Band (B4)", + "description": "Red Band (B4) Top Of the Atmosphere" + }, + "B5": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B5", + "common_name": "nir", + "center_wavelength": 0.86, + "full_width_half_max": 0.03 + } + ], + "title": "NIR Band (B5)", + "description": "NIR Band (B5) Top Of the Atmosphere" + }, + "B6": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B6", + "common_name": "swir16", + "center_wavelength": 1.6, + "full_width_half_max": 0.08 + } + ], + "title": "SWIR Band (B6)", + "description": "SWIR Band at 1.6um (B6) Top Of the Atmosphere" + }, + "B7": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B7", + "common_name": "swir22", + "center_wavelength": 2.2, + "full_width_half_max": 0.2 + } + ], + "title": "SWIR Band (B7)", + "description": "SWIR Band at 2.2um (B7) Top Of the Atmosphere" + }, + "B8": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B8", + "common_name": "pan", + "center_wavelength": 0.59, + "full_width_half_max": 0.18 + } + ], + "title": "Panchromatic Band (B8)", + "description": "Panchromatic Band (B8) Top Of the Atmosphere" + }, + "B9": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B9", + "common_name": "cirrus", + "center_wavelength": 1.37, + "full_width_half_max": 0.02 + } + ], + "title": "Cirrus Band (B9)", + "description": "Cirrus Band (B9) Top Of the Atmosphere - for cirrus cloud detection" + }, + "B10": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B10", + "common_name": "lwir11", + "center_wavelength": 10.9, + "full_width_half_max": 0.8 + } + ], + "title": "LWIR Band (B10)", + "description": "Long-wave IR Band at 11um (B10) Top Of the Atmosphere" + }, + "B11": { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B11", + "common_name": "lwir12", + "center_wavelength": 12, + "full_width_half_max": 1 + } + ], + "title": "LWIR Band (B11)", + "description": "Long-wave IR Band at 12um (B11) Top Of the Atmosphere" + } + }, + "links": [] +} \ No newline at end of file diff --git a/tests/test_item_assets.py b/tests/test_item_assets.py new file mode 100644 index 000000000..3cb31a884 --- /dev/null +++ b/tests/test_item_assets.py @@ -0,0 +1,91 @@ +from pathlib import Path + +import pytest + +from pystac import Collection +from pystac.asset import ItemAsset + + +@pytest.fixture +def landsat8_collection(data_files_path: Path) -> Collection: + return Collection.from_file( + data_files_path / "collections" / "with-item-assets.json" + ) + + +def test_example(landsat8_collection: Collection) -> None: + assert landsat8_collection.item_assets + assert len(landsat8_collection.item_assets) == 13 + + assert landsat8_collection.item_assets["B1"] == ItemAsset.from_dict( + { + "type": "image/tiff; application=geotiff", + "eo:bands": [ + { + "name": "B1", + "common_name": "coastal", + "center_wavelength": 0.44, + "full_width_half_max": 0.02, + } + ], + "title": "Coastal Band (B1)", + "description": "Coastal Band Top Of the Atmosphere", + } + ) + + +class TestItemAsset: + def test_eq(self, landsat8_collection: Collection) -> None: + assert landsat8_collection.item_assets + assert landsat8_collection.item_assets["B1"].to_dict() != { + "title": "Coastal Band (B1)" + } + + def test_create(self) -> None: + title = "Coastal Band (B1)" + description = "Coastal Band Top Of the Atmosphere" + media_type = "image/tiff; application=geotiff" + roles = ["data"] + asset_defn = ItemAsset( + title=title, description=description, type=media_type, roles=roles + ) + assert ( + asset_defn.title, + asset_defn.description, + asset_defn.type, + asset_defn.roles, + ) == (title, description, media_type, roles) + + def test_title(self) -> None: + asset_defn = ItemAsset() + title = "Very Important Asset" + + asset_defn.title = title + + assert asset_defn.title == asset_defn.to_dict()["title"] == title + + def test_description(self) -> None: + asset_defn = ItemAsset() + description = "What an incredibly important asset this is!" + + asset_defn.description = description + + assert ( + asset_defn.description == asset_defn.to_dict()["description"] == description + ) + + def test_media_type(self) -> None: + asset_defn = ItemAsset() + media_type = "application/json" + + asset_defn.type = media_type + + assert asset_defn.type == asset_defn.to_dict()["type"] == media_type + + def test_roles(self) -> None: + asset_defn = ItemAsset() + roles = ["thumbnail"] + + asset_defn.roles = roles + + assert asset_defn.roles == asset_defn.to_dict()["roles"] == roles diff --git a/tests/v1/test_asset.py b/tests/v1/test_asset.py index b38cdb36f..9c38d6bfa 100644 --- a/tests/v1/test_asset.py +++ b/tests/v1/test_asset.py @@ -6,6 +6,10 @@ import pystac +pytestmark = pytest.mark.passing_v2 + + +@pytest.mark.xfail(reason="Asset copy/move/delete are removed in pystac v2") @pytest.mark.parametrize("action", ["copy", "move"]) def test_alter_asset_absolute_path( action: str, tmp_asset: pystac.Asset, tmp_path: Path @@ -26,6 +30,7 @@ def test_alter_asset_absolute_path( assert os.path.exists(old_href) +@pytest.mark.xfail(reason="Asset copy/move/delete are removed in pystac v2") @pytest.mark.parametrize("action", ["copy", "move"]) def test_alter_asset_relative_path(action: str, tmp_asset: pystac.Asset) -> None: asset = tmp_asset @@ -45,6 +50,7 @@ def test_alter_asset_relative_path(action: str, tmp_asset: pystac.Asset) -> None assert os.path.exists(old_href) +@pytest.mark.xfail(reason="Asset copy/move/delete are removed in pystac v2") @pytest.mark.parametrize("action", ["copy", "move"]) def test_alter_asset_relative_src_no_owner_fails( action: str, tmp_asset: pystac.Asset @@ -55,10 +61,12 @@ def test_alter_asset_relative_src_no_owner_fails( with pytest.raises(ValueError, match=f"Cannot {action} file") as e: getattr(asset, action)(new_href) - assert new_href not in str(e.value) + assert asset.href in str(e.value) + assert new_href in str(e.value) assert asset.href != new_href +@pytest.mark.xfail(reason="Asset copy/move/delete are removed in pystac v2") @pytest.mark.parametrize("action", ["copy", "move"]) def test_alter_asset_relative_dst_no_owner_fails( action: str, tmp_asset: pystac.Asset @@ -74,10 +82,12 @@ def test_alter_asset_relative_dst_no_owner_fails( with pytest.raises(ValueError, match=f"Cannot {action} file") as e: getattr(asset, action)(new_href) + assert asset.href in str(e.value) assert new_href in str(e.value) assert asset.href != new_href +@pytest.mark.xfail(reason="Asset copy/move/delete are removed in pystac v2") def test_delete_asset(tmp_asset: pystac.Asset) -> None: asset = tmp_asset href = asset.get_absolute_href() @@ -89,6 +99,7 @@ def test_delete_asset(tmp_asset: pystac.Asset) -> None: assert not os.path.exists(href) +@pytest.mark.xfail(reason="Asset copy/move/delete are removed in pystac v2") def test_delete_asset_relative_no_owner_fails(tmp_asset: pystac.Asset) -> None: asset = tmp_asset href = asset.get_absolute_href() diff --git a/tests/v1/test_item_assets.py b/tests/v1/test_item_assets.py index 8f2528bb3..04b0d20b3 100644 --- a/tests/v1/test_item_assets.py +++ b/tests/v1/test_item_assets.py @@ -7,6 +7,10 @@ from .utils import TestCases + +pytestmark = pytest.mark.passing_v2 + + CLASSIFICATION_COLLECTION_RASTER_URI = TestCases.get_path( "data-files/classification/collection-item-assets-raster-bands.json" ) @@ -39,6 +43,7 @@ def test_example(landsat8_collection: Collection) -> None: ) +@pytest.mark.xfail(reason="setting item_assets using a dict is no longer supported") def test_set_using_dict(landsat8_collection: Collection) -> None: assert len(landsat8_collection.item_assets) == 13 @@ -62,6 +67,7 @@ def test_set_using_dict(landsat8_collection: Collection) -> None: class TestAssetDefinition: + @pytest.mark.xfail(reason="Item assets don't pretend to be dict in pystac v2") def test_eq(self, landsat8_collection: Collection) -> None: assert landsat8_collection.item_assets["B1"] != {"title": "Coastal Band (B1)"} @@ -134,6 +140,7 @@ def test_set_owner(self, landsat8_collection: Collection) -> None: assert asset_definition.owner == landsat8_collection +@pytest.mark.xfail(reason="ItemAssets have no owner in pystac v2") def test_extra_fields(collection: Collection) -> None: asset_definition = ItemAssetDefinition.create( title=None, @@ -159,6 +166,7 @@ def test_extra_fields(collection: Collection) -> None: assert collection.ext.has("raster") +@pytest.mark.xfail(reason="ItemAssets have no owner in pystac v2") def test_set_item_asset(collection: Collection) -> None: asset_definition = ItemAssetDefinition.create( title=None, @@ -172,6 +180,7 @@ def test_set_item_asset(collection: Collection) -> None: assert collection.item_assets["data"].owner == collection +@pytest.mark.xfail(reason="ItemAssets Extension deprecation cycle is complete") def test_item_assets_extension_is_deprecated() -> None: collection = Collection.from_file(CLASSIFICATION_COLLECTION_RASTER_URI) @@ -197,6 +206,7 @@ def test_item_assets_extension_is_deprecated() -> None: item_asset_ext.item_assets["thumbnail"] = asset_definition +@pytest.mark.xfail(reason="ItemAssets Extension deprecation cycle is complete") def test_item_assets_extension_asset_definition_is_deprecated() -> None: with pytest.warns( DeprecationWarning, match="Please use ``pystac.ItemAssetDefinition``" From 00c26562afa0493a2663a75da1241283947361d8 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 7 Apr 2026 10:28:55 -0400 Subject: [PATCH 54/60] Fix asset `__eq__` (#1660) --- src/pystac/asset.py | 2 ++ tests/v1/test_item_assets.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pystac/asset.py b/src/pystac/asset.py index fdf1f07fa..3c6d4adf5 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -43,6 +43,8 @@ def __init__( @override def __eq__(self, other: Any) -> bool: + if not isinstance(other, ItemAsset): + return NotImplemented return self.to_dict() == other.to_dict() @classmethod diff --git a/tests/v1/test_item_assets.py b/tests/v1/test_item_assets.py index 04b0d20b3..425302b53 100644 --- a/tests/v1/test_item_assets.py +++ b/tests/v1/test_item_assets.py @@ -26,7 +26,7 @@ def landsat8_collection() -> Collection: def test_example(landsat8_collection: Collection) -> None: assert len(landsat8_collection.item_assets) == 13 - assert landsat8_collection.item_assets["B1"] == ItemAssetDefinition( + assert landsat8_collection.item_assets["B1"].to_dict() == ItemAssetDefinition( { "type": "image/tiff; application=geotiff", "eo:bands": [ @@ -40,7 +40,7 @@ def test_example(landsat8_collection: Collection) -> None: "title": "Coastal Band (B1)", "description": "Coastal Band Top Of the Atmosphere", } - ) + ).to_dict() @pytest.mark.xfail(reason="setting item_assets using a dict is no longer supported") From e1538ee49654664acf06832d28db22dc468dfd71 Mon Sep 17 00:00:00 2001 From: Ian Cooke Date: Tue, 7 Apr 2026 11:22:23 -0400 Subject: [PATCH 55/60] refactor: self href and self link handling (#1659) * refactor(test_posix_path): uses self-href instead of self-link * make hrefs to be posix-style on load on load of stac item, initial call to set_self_href has start_href=None, so make asset hrefs posix style instead of trying to update location * refactor: self._href must be absolute --- src/pystac/asset.py | 9 ++++-- src/pystac/io.py | 4 ++- src/pystac/item.py | 11 +++++-- src/pystac/link.py | 6 +++- src/pystac/stac_object.py | 13 +++++---- tests/v1/posix_paths/test_posix_paths.py | 37 +++++++++++++----------- tests/v1/test_item.py | 10 +++++-- 7 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/pystac/asset.py b/src/pystac/asset.py index 3c6d4adf5..c6c6f63d5 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -12,6 +12,7 @@ get_absolute_href, is_absolute_href, make_absolute_href, + make_posix_style, make_relative_href, ) from .writer import Writer @@ -108,12 +109,14 @@ def __init__( def update_hrefs( assets: dict[str, Asset], start_href: str | None, end_href: str | None ) -> None: - if start_href and end_href: - for asset in assets.values(): - if not is_absolute_href(asset.href): + for asset in assets.values(): + if not is_absolute_href(asset.href): + if start_href and end_href: asset.href = make_relative_href( make_absolute_href(asset.href, start_href), end_href ) + else: + asset.href = make_posix_style(asset.href) def set_owner(self, owner: STACObject | None) -> None: self.owner = owner diff --git a/src/pystac/io.py b/src/pystac/io.py index ae9f79ace..9c35382b7 100644 --- a/src/pystac/io.py +++ b/src/pystac/io.py @@ -7,4 +7,6 @@ def read_file(href: str | Path, reader: Reader = DEFAULT_READER) -> STACObject: data = reader.get_json(href) - return from_dict(data) + obj = from_dict(data) + obj.set_self_href(str(href)) + return obj diff --git a/src/pystac/item.py b/src/pystac/item.py index 8abd85d54..53db877b7 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -24,7 +24,7 @@ from .geo_interface import GeoInterface from .link import Link from .stac_object import STACObject -from .utils import datetime_to_str, str_to_datetime +from .utils import datetime_to_str, make_posix_style, str_to_datetime if TYPE_CHECKING: from .collection import Collection @@ -134,8 +134,13 @@ def set_datetime(self, datetime: dt.datetime, asset: Asset | None = None) -> Non @override def set_self_href(self, href: str | None) -> None: - Asset.update_hrefs(self.assets, self.get_self_href(), href) - return super().set_self_href(href) + new_href = None if href is None else make_posix_style(href) + Asset.update_hrefs( + self.assets, + self.get_self_href(), + new_href, + ) + return super().set_self_href(new_href) @property @deprecated("Access common metadata fields directly on properties") diff --git a/src/pystac/link.py b/src/pystac/link.py index 594e2fd89..23eabcb4f 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -9,7 +9,7 @@ from pystac.errors import STACError from pystac.media_type import MediaType from pystac.rel_type import RelType -from pystac.utils import make_absolute_href +from pystac.utils import make_absolute_href, make_posix_style from .reader import Reader @@ -58,6 +58,10 @@ def __init__( else: self._href = href or target self._target = None + # backwards compat allows 'target' parameter to be str, + # so we delay in posix conversion until here + if self._href is not None: + self._href = make_posix_style(self._href) def __fspath__(self) -> str: if self._target and (href := self._target.get_self_href()): diff --git a/src/pystac/stac_object.py b/src/pystac/stac_object.py index 1f40f2170..bccb33c26 100644 --- a/src/pystac/stac_object.py +++ b/src/pystac/stac_object.py @@ -17,7 +17,11 @@ from .constants import STAC_OBJECT_TYPE from .link import Link from .reader import DEFAULT_READER, Reader -from .utils import is_absolute_href, make_absolute_href, make_relative_href +from .utils import ( + is_absolute_href, + make_absolute_href, + make_relative_href, +) from .validator import Validator from .writer import Writer @@ -106,7 +110,7 @@ def from_file[T: STACObject]( f"Expected {cls.__name__}, got {type(stac_object).__name__}" ) stac_object.reader = reader - stac_object.set_self_href(href) + stac_object.set_self_href(str(href)) return stac_object @classmethod @@ -209,10 +213,7 @@ def get_self_href(self) -> str | None: return self._href def set_self_href(self, href: str | None) -> None: - self._href = href - self.links = list(link for link in self.links if not link.is_self()) - if href: - self.links.append(Link(rel="self", target=href)) + self._href = make_absolute_href(href) if href is not None else None def get_root(self) -> Container | None: from .container import Container diff --git a/tests/v1/posix_paths/test_posix_paths.py b/tests/v1/posix_paths/test_posix_paths.py index 1a03d4a2e..ac34abc1b 100644 --- a/tests/v1/posix_paths/test_posix_paths.py +++ b/tests/v1/posix_paths/test_posix_paths.py @@ -13,13 +13,16 @@ pytestmark = pytest.mark.passing_v2 -def check_link(link: pystac.Link | None) -> None: - assert link is not None - href = link.get_href() +def check_href(href: str | None) -> None: assert href is not None assert "\\" not in href +def check_link(link: pystac.Link | None) -> None: + assert link is not None + check_href(link.get_href()) + + def test_create_item_from_absolute_posix_href() -> None: absolute_href = get_data_file("item/sample-item.json") absolute_posix_href = absolute_href.replace("\\", "/") @@ -75,48 +78,48 @@ def test_posix_self_link_from_absolute_href(tmp_path: Path) -> None: absolute_windows_href = get_data_file("item/sample-item.json") assert "\\" in absolute_windows_href item = pystac.Item.from_file(absolute_windows_href) - check_link(item.get_single_link(rel="self")) + check_href(item.get_self_href()) item2 = pystac.read_file(absolute_windows_href) - check_link(item2.get_single_link(rel="self")) + check_href(item2.get_self_href()) absolute_windows_href = get_data_file("collections/multi-extent.json") assert "\\" in absolute_windows_href collection = pystac.Collection.from_file(absolute_windows_href) - check_link(collection.get_single_link(rel="self")) + check_href(collection.get_self_href()) collection2 = pystac.read_file(absolute_windows_href) - check_link(collection2.get_single_link(rel="self")) + check_href(collection2.get_self_href()) absolute_windows_href = get_data_file("catalogs/test-case-1/catalog.json") assert "\\" in absolute_windows_href catalog = pystac.Catalog.from_file(absolute_windows_href) - check_link(catalog.get_single_link(rel="self")) + check_href(catalog.get_self_href()) catalog2 = pystac.read_file(absolute_windows_href) - check_link(catalog2.get_single_link(rel="self")) + check_href(catalog2.get_self_href()) # check that we retain a posix style (/) absolute href in the self link absolute_windows_href = get_data_file("item/sample-item.json") assert "\\" in absolute_windows_href absolute_posix_href = absolute_windows_href.replace("\\", "/") item = pystac.Item.from_file(absolute_posix_href) - check_link(item.get_single_link(rel="self")) + check_href(item.get_self_href()) item2 = pystac.read_file(absolute_posix_href) - check_link(item2.get_single_link(rel="self")) + check_href(item2.get_self_href()) absolute_windows_href = get_data_file("collections/multi-extent.json") assert "\\" in absolute_windows_href absolute_posix_href = absolute_windows_href.replace("\\", "/") collection = pystac.Collection.from_file(absolute_posix_href) - check_link(collection.get_single_link(rel="self")) + check_href(collection.get_self_href()) collection2 = pystac.read_file(absolute_posix_href) - check_link(collection2.get_single_link(rel="self")) + check_href(collection2.get_self_href()) absolute_windows_href = get_data_file("collections/multi-extent.json") assert "\\" in absolute_windows_href absolute_posix_href = absolute_windows_href.replace("\\", "/") catalog = pystac.Collection.from_file(absolute_posix_href) - check_link(catalog.get_single_link(rel="self")) + check_href(catalog.get_self_href()) catalog2 = pystac.read_file(absolute_posix_href) - check_link(catalog2.get_single_link(rel="self")) + check_href(catalog2.get_self_href()) @pytest.mark.skipif(os.name != "nt", reason="windows only test") @@ -135,9 +138,9 @@ def test_posix_self_link_from_relative_href( monkeypatch.chdir(tmp_path) item = pystac.Item.from_file("subdirectory\\test-item.json") - check_link(item.get_single_link(rel="self")) + check_href(item.get_self_href()) item2 = pystac.read_file("subdirectory\\test-item.json") - check_link(item2.get_single_link(rel="self")) + check_href(item2.get_self_href()) def test_all_generated_links_have_posix_hrefs(tmp_path: Path) -> None: diff --git a/tests/v1/test_item.py b/tests/v1/test_item.py index b8b3b2889..bf48d854b 100644 --- a/tests/v1/test_item.py +++ b/tests/v1/test_item.py @@ -476,6 +476,12 @@ def test_geo_interface() -> None: def test_duplicate_self_links(tmp_path: Path, sample_item: pystac.Item) -> None: # https://github.com/stac-utils/pystac/issues/1102 + sample_item.links.append( + Link( + rel=pystac.RelType.SELF, + href=sample_item.get_self_href(), + ) + ) assert len(sample_item.get_links(rel="self")) == 1 path = tmp_path / "item.json" sample_item.save_object(include_self_link=True, dest_href=str(path)) @@ -513,7 +519,7 @@ def test_get_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: item = next(test_case_1_catalog.get_items(recursive=True)) item.add_derived_from("foo") with pytest.raises( - pystac.STACError # , match="Link failed to resolve. Use get_links instead" + pystac.STACError # , match="Link failed to resolve. Use get_links instead" ): item.get_derived_from() @@ -525,7 +531,7 @@ def test_remove_unresolvable_derived_from(test_case_1_catalog: Catalog) -> None: item = next(test_case_1_catalog.get_items(recursive=True)) item.add_derived_from("foo") with pytest.raises( - pystac.STACError # , match="Link failed to resolve. Use remove_links instead" + pystac.STACError # , match="Link failed to resolve. Use remove_links instead" ): item.remove_derived_from("foo") From 098bb9e1688071e7a5859f16090bf3522247830f Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Mon, 20 Apr 2026 10:02:09 -0400 Subject: [PATCH 56/60] feat: V2 collection tests (#1668) This PR make all the regular datetime fields convert to `dt.datetime` on initialization and when setting. This is not the case for `collection.extent.temporal.interval` yet because it looked like there was some intentional logic there. Also because of rounding you can't guarantee that roundtripping from str -> dt.datetime -> str will give exactly the same string. --------- Co-authored-by: Ian Cooke --- src/pystac/asset.py | 6 +- src/pystac/collection.py | 293 +++++++++++++++++++++++++------ src/pystac/common_metadata.py | 98 +++++------ src/pystac/container.py | 65 +++++-- src/pystac/item.py | 29 +-- src/pystac/item_collection.py | 54 +++++- src/pystac/link.py | 5 + src/pystac/utils.py | 18 ++ tests/v1/test_collection.py | 62 +++++-- tests/v1/test_common_metadata.py | 8 +- tests/v1/utils/test_cases.py | 2 +- 11 files changed, 489 insertions(+), 151 deletions(-) diff --git a/src/pystac/asset.py b/src/pystac/asset.py index c6c6f63d5..fd4557422 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -6,7 +6,7 @@ from typing_extensions import deprecated from .band import Band -from .common_metadata import DataValue, Instrument +from .common_metadata import DataValue, DateTime, Instrument from .media_type import MediaType from .utils import ( get_absolute_href, @@ -21,7 +21,7 @@ from .stac_object import STACObject -class ItemAsset(DataValue, Instrument): +class ItemAsset(DateTime, DataValue, Instrument): def __init__( self, *, @@ -41,6 +41,7 @@ def __init__( else: self.bands = None self.extra_fields: dict[str, Any] = kwargs + self.extra_fields.update(DateTime.from_str(kwargs)) @override def __eq__(self, other: Any) -> bool: @@ -71,6 +72,7 @@ def clone[T: ItemAsset](self: T) -> T: def to_dict(self) -> dict[str, Any]: data: dict[str, Any] = copy.deepcopy(self.extra_fields) + data.update(DateTime.to_str(self.extra_fields)) data["title"] = self.title data["description"] = self.description data["type"] = self.type diff --git a/src/pystac/collection.py b/src/pystac/collection.py index df5578ebb..34b2177ee 100644 --- a/src/pystac/collection.py +++ b/src/pystac/collection.py @@ -2,7 +2,17 @@ import copy import datetime as dt -from typing import Any, ClassVar, TypedDict, cast, override +import warnings +from collections.abc import Iterable +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Self, + TypedDict, + cast, + override, +) from typing_extensions import deprecated @@ -12,7 +22,11 @@ from .container import Container from .link import Link from .provider import Provider -from .utils import datetime_to_str +from .utils import to_datetime_str + +if TYPE_CHECKING: + from .item import Item + from .item_collection import ItemCollection SpatialExtentBboxType = list[float | int] | list[list[float | int]] TemporalExtentIntervalType = ( @@ -64,13 +78,22 @@ def __init__( self.bands: list[Band] | None = [Band.try_from(band) for band in bands] else: self.bands = None + + if extent is None: + warnings.warn( + "Collection is missing extent, setting default spatial and " + "temporal extents" + ) self.extent: Extent = Extent.try_from(extent) self.summaries: dict[str, Any] | None = summaries - self.assets: dict[str, Asset] = ( - {key: Asset.try_from(value) for (key, value) in assets.items()} - if assets is not None - else {} - ) + + self.assets: dict[str, Asset] = {} + if assets is not None: + for key, value in assets.items(): + asset = Asset.try_from(value) + asset.set_owner(self) + self.assets[key] = asset + self.item_assets: dict[str, ItemAsset] | None = ( {key: ItemAsset.try_from(value) for (key, value) in item_assets.items()} if item_assets is not None @@ -79,16 +102,77 @@ def __init__( @override @classmethod - def from_dict( - cls, + def from_dict[T: Collection]( + cls: type[T], data: dict[str, Any], preserve_dict: bool = True, migrate: bool | None = None, root: Container | None = None, - ) -> Collection: + ) -> T: if preserve_dict: data = copy.deepcopy(data) - return Collection(**data) + return cls(**data) + + @classmethod + def from_items[T: Collection]( + cls: type[T], + items: Iterable[Item] | ItemCollection, + *, + id: str | None = None, + ) -> T: + """Create a :class:`Collection` from iterable of items or an + :class:`~pystac.ItemCollection`. + + Will try to pull collection attributes from + :attr:`~pystac.ItemCollection.extra_fields` and items when possible. + + Args: + items : Iterable of :class:`~pystac.Item` instances to include in the + :class:`Collection`. This can be a :class:`~pystac.ItemCollection`. + id : Identifier for the collection. If not set, must be available on the + items and they must all match. + """ + from .item_collection import ItemCollection + + if id is None: + values = {item.collection_id for item in items} + if len(values) == 1: + id = next(iter(values)) + if id is None: + raise ValueError( + "Collection id must be defined. Either by specifying collection_id " + "on every item, or as a keyword argument to this function." + ) + + kwargs: dict[str, Any] = {} + if isinstance(items, ItemCollection): + kwargs = copy.deepcopy(items.extra_fields) + + if "description" not in kwargs: + values = {item.properties.description for item in items} + if len(values) == 1: + kwargs["description"] = next(iter(values)) + + if "title" not in kwargs: + values = {item.properties.title for item in items} + if len(values) == 1: + kwargs["title"] = next(iter(values)) + + collection = cls( + id=id, + description=kwargs.pop("description"), + extent=Extent.from_items(items), + **kwargs, + ) + + _ = collection.add_items(items) + + return collection + + @property + @deprecated("use .type instead") + def STAC_OBJECT_TYPE(self) -> STAC_OBJECT_TYPE: + return self.type @override def set_self_href(self, href: str | None) -> None: @@ -127,15 +211,23 @@ def to_dict( } return data + def update_extent_from_items(self) -> None: + """ + Update datetime and bbox based on all items to a single bbox and time window. + """ + self.extent = Extent.from_items(self.get_items(recursive=True)) + class Extent: def __init__( self, spatial: SpatialExtent | SpatialExtentDict | None = None, temporal: TemporalExtent | TemporalExtentDict | None = None, + **kwargs: Any, ): self.spatial: SpatialExtent = SpatialExtent.try_from(spatial) self.temporal: TemporalExtent = TemporalExtent.try_from(temporal) + self.extra_fields: dict[str, Any] = kwargs @classmethod def try_from(cls, extent: Extent | dict[str, Any] | None) -> Extent: @@ -147,21 +239,120 @@ def try_from(cls, extent: Extent | dict[str, Any] | None) -> Extent: return Extent() @classmethod - def from_dict(cls, data: dict[str, Any]) -> Extent: - return Extent(**data) + def from_dict[T: Extent](cls: type[T], data: dict[str, Any]) -> T: + return cls(**data) + + @classmethod + def from_items(cls: type[Self], items: Iterable[Item]) -> Extent: + """Create an Extent based on the datetimes and bboxes of a list of items. + + Args: + items : A list of items to derive the extent from. + + Returns: + Extent: An Extent that spatially and temporally covers all of the + given items. + """ + bounds_values: list[list[float]] = [ + [float("inf")], + [float("inf")], + [float("-inf")], + [float("-inf")], + ] + datetimes: list[dt.datetime] = [] + starts: list[dt.datetime] = [] + ends: list[dt.datetime] = [] + + for item in items: + if item.bbox is not None: + for i in range(0, 4): + bounds_values[i].append(item.bbox[i]) + if item.datetime is not None: + datetimes.append(item.datetime) + if item.properties.start_datetime is not None: + starts.append(item.properties.start_datetime) + if item.properties.end_datetime is not None: + ends.append(item.properties.end_datetime) + + if not any(datetimes + starts): + start_timestamp = None + else: + start_timestamp = min( + [ + d if d.tzinfo else d.replace(tzinfo=dt.UTC) + for d in datetimes + starts + ] + ) + if not any(datetimes + ends): + end_timestamp = None + else: + end_timestamp = max( + [d if d.tzinfo else d.replace(tzinfo=dt.UTC) for d in datetimes + ends] + ) + + spatial = SpatialExtent( + [ + [ + min(bounds_values[0]), + min(bounds_values[1]), + max(bounds_values[2]), + max(bounds_values[3]), + ] + ] + ) + temporal = TemporalExtent( + [ + [ + to_datetime_str(start_timestamp), + to_datetime_str(end_timestamp), + ] + ] + ) + + return Extent(spatial=spatial, temporal=temporal) + + def clone[T: Extent](self: T) -> T: + return self.from_dict(self.to_dict()) def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) return { "spatial": self.spatial.to_dict(), "temporal": self.temporal.to_dict(), + **data, } class TemporalExtent: - def __init__(self, interval: list[list[str | None]] | None = None) -> None: - self.interval: list[list[str | None]] = interval or [ - [datetime_to_str(dt.datetime.now()), None] - ] + def __init__( + self, + interval: TemporalExtentIntervalType | str | dt.datetime | None = None, + **kwargs: Any, + ) -> None: + if interval is None and "intervals" in kwargs: + warnings.warn( + "intervals is deprecated and will be removed in a future version. " + "Use interval instead", + FutureWarning, + ) + interval = kwargs.pop("intervals") + + self.interval: list[list[str | None]] + if interval is None: + self.interval = [[None, None]] + elif isinstance(interval, (str, dt.datetime)): + self.interval = [to_interval([interval, None])] + elif isinstance(interval[0], list): + self.interval = [ + to_interval(cast(list[dt.datetime | str | None], dates)) + for dates in interval + ] + else: + self.interval = [ + to_interval(cast(list[dt.datetime | str | None], interval)) + ] + + self.extra_fields: dict[str, Any] = kwargs @classmethod def try_from( @@ -172,30 +363,44 @@ def try_from( return data elif not data: return TemporalExtent() - elif isinstance(data["interval"][0], list): - return TemporalExtent( - [ - to_interval(cast(list[dt.datetime | str | None], interval)) - for interval in data["interval"] - ] - ) else: - return TemporalExtent( - [to_interval(cast(list[dt.datetime | str | None], data["interval"]))] - ) + return TemporalExtent(**data) + + @property + @deprecated("Use interval instead") + def intervals(self) -> list[list[str | None]]: + return self.interval @classmethod - @deprecated("Use default initializer instead") + @deprecated("Use `TemporalExtent(dt.datetime.now())` instead") def from_now(cls) -> TemporalExtent: - return TemporalExtent() + return TemporalExtent(interval=dt.datetime.now()) def to_dict(self) -> dict[str, Any]: - return {"interval": self.interval} + data = copy.deepcopy(self.extra_fields) + return {"interval": self.interval, **data} class SpatialExtent: - def __init__(self, bbox: list[list[float | int]] | None = None) -> None: - self.bbox: list[list[float | int]] = bbox or [[-180, -90, 180, 90]] + def __init__( + self, bbox: SpatialExtentBboxType | None = None, **kwargs: Any + ) -> None: + if bbox is None and "bboxes" in kwargs: + warnings.warn( + "bboxes is deprecated and will be removed in a future version. " + "Use bbox instead", + FutureWarning, + ) + bbox = kwargs.pop("bboxes") + + self.bbox: list[list[float | int]] + if bbox is None: + self.bbox = [[-180, -90, 180, 90]] + elif isinstance(bbox[0], list): + self.bbox = cast(list[list[float | int]], bbox) + else: + self.bbox = [cast(list[float | int], bbox)] + self.extra_fields: dict[str, Any] = kwargs @classmethod def try_from( @@ -206,30 +411,20 @@ def try_from( return data elif not data: return SpatialExtent() - elif isinstance(data["bbox"][0], list): - return SpatialExtent(cast(list[list[float | int]], data["bbox"])) else: - return SpatialExtent([cast(list[float | int], data["bbox"])]) + return SpatialExtent(**data) - @classmethod - @deprecated("Use try_from instead") - def from_coordinates(cls, coordinates: Any) -> SpatialExtent: - return SpatialExtent.try_from({"bbox": coordinates}) + @property + @deprecated("Use bbox instead") + def bboxes(self) -> list[list[float | int]]: + return self.bbox def to_dict(self) -> dict[str, Any]: - return {"bbox": self.bbox} + data = copy.deepcopy(self.extra_fields) + return {"bbox": self.bbox, **data} def to_interval(interval: list[dt.datetime | str | None]) -> list[str | None]: if len(interval) != 2: raise ValueError("Interval must have exactly two elements") return [to_datetime_str(interval[0]), to_datetime_str(interval[1])] - - -def to_datetime_str(datetime: dt.datetime | str | None) -> str | None: - if datetime is None: - return None - elif isinstance(datetime, str): - return datetime - else: - return datetime_to_str(datetime) diff --git a/src/pystac/common_metadata.py b/src/pystac/common_metadata.py index 134b36d96..a8bc40afd 100644 --- a/src/pystac/common_metadata.py +++ b/src/pystac/common_metadata.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Protocol, override from .provider import Provider -from .utils import datetime_to_str, str_to_datetime +from .utils import from_datetime_str, to_datetime_str if TYPE_CHECKING: from .asset import Asset @@ -71,38 +71,20 @@ class DateTime(Protocol): @property def datetime(self) -> dt.datetime | None: """See the Item Specification Fields for more information.""" - value: str | None = self.extra_fields.get("datetime") - datetime: dt.datetime | None = None - if value is not None: - datetime = str_to_datetime(value) - return datetime + return self.extra_fields.get("datetime") @datetime.setter def datetime(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] - datetime: str | None = None - if isinstance(value, str): - datetime = value - elif isinstance(value, dt.datetime): - datetime = datetime_to_str(value) - self.extra_fields["datetime"] = datetime + self.extra_fields["datetime"] = from_datetime_str(value) @property def created(self) -> dt.datetime | None: """Creation date and time of the corresponding STAC entity or Asset, in UTC.""" - value: str | None = self.extra_fields.get("created") - datetime: dt.datetime | None = None - if value is not None: - datetime = str_to_datetime(value) - return datetime + return self.extra_fields.get("created") @created.setter def created(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] - datetime: str | None = None - if isinstance(value, str): - datetime = value - elif isinstance(value, dt.datetime): - datetime = datetime_to_str(value) - self.extra_fields["created"] = datetime + self.extra_fields["created"] = from_datetime_str(value) @property def updated(self) -> dt.datetime | None: @@ -110,56 +92,59 @@ def updated(self) -> dt.datetime | None: Date and time the corresponding STAC entity or Asset was updated last, in UTC. """ - value: str | None = self.extra_fields.get("updated") - datetime: dt.datetime | None = None - if value is not None: - datetime = str_to_datetime(value) - return datetime + return self.extra_fields.get("updated") @updated.setter def updated(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] - datetime: str | None = None - if isinstance(value, str): - datetime = value - elif isinstance(value, dt.datetime): - datetime = datetime_to_str(value) - self.extra_fields["updated"] = datetime + self.extra_fields["updated"] = from_datetime_str(value) @property def start_datetime(self) -> dt.datetime | None: """The first or start date and time for the resource, in UTC.""" - value: str | None = self.extra_fields.get("start_datetime") - datetime: dt.datetime | None = None - if value is not None: - datetime = str_to_datetime(value) - return datetime + return self.extra_fields.get("start_datetime") @start_datetime.setter def start_datetime(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] - datetime: str | None = None - if isinstance(value, str): - datetime = value - elif isinstance(value, dt.datetime): - datetime = datetime_to_str(value) - self.extra_fields["start_datetime"] = datetime + self.extra_fields["start_datetime"] = from_datetime_str(value) @property def end_datetime(self) -> dt.datetime | None: """The last or end date and time for the resource, in UTC.""" - value: str | None = self.extra_fields.get("end_datetime") - datetime: dt.datetime | None = None - if value is not None: - datetime = str_to_datetime(value) - return datetime + return self.extra_fields.get("end_datetime") @end_datetime.setter def end_datetime(self, value: str | dt.datetime | None) -> None: # pyright: ignore[reportPropertyTypeMismatch] - datetime: str | None = None - if isinstance(value, str): - datetime = value - elif isinstance(value, dt.datetime): - datetime = datetime_to_str(value) - self.extra_fields["end_datetime"] = datetime + self.extra_fields["end_datetime"] = from_datetime_str(value) + + @staticmethod + def from_str(extra_fields: dict[str, Any]) -> dict[str, dt.datetime | None]: + """Create new dict with iso strings replaced by dt.datetimes""" + data: dict[str, dt.datetime | None] = {} + for field in ( + "datetime", + "created", + "updated", + "start_datetime", + "end_datetime", + ): + if field in extra_fields: + data[field] = from_datetime_str(extra_fields[field]) + return data + + @staticmethod + def to_str(extra_fields: dict[str, Any]) -> dict[str, str | None]: + """Create new dict with dt.datetimes replaced by iso strings""" + data: dict[str, str | None] = {} + for field in ( + "datetime", + "created", + "updated", + "start_datetime", + "end_datetime", + ): + if field in extra_fields: + data[field] = to_datetime_str(extra_fields[field]) + return data class Licensing(Protocol): @@ -387,6 +372,7 @@ class CommonMetadata(Basics, DateTime, Licensing, Providers, Instrument, DataVal def __init__(self, /, object: Asset | Item): self.object = object + self.extra_fields.update(DateTime.from_str(self.extra_fields)) @property @override diff --git a/src/pystac/container.py b/src/pystac/container.py index 8d1ac12e7..674643a33 100644 --- a/src/pystac/container.py +++ b/src/pystac/container.py @@ -4,7 +4,7 @@ import os.path import warnings from abc import ABC -from collections.abc import Callable, Iterator +from collections.abc import Callable, Iterable, Iterator from typing import TYPE_CHECKING, Any from typing_extensions import deprecated @@ -20,6 +20,7 @@ if TYPE_CHECKING: from .catalog import CatalogType # pyright: ignore[reportDeprecated] + from .collection import Collection from .href_generator import HrefGenerator from .item import Item from .stac_io import StacIO @@ -27,6 +28,9 @@ class Container(STACObject, ABC): + def get_item(self, id: str, recursive: bool = False) -> Item | None: + return next(self.get_items(id, recursive=recursive), None) + def get_items(self, *ids: str, recursive: bool = False) -> Iterator[Item]: for link in self.links: if link.is_item(): @@ -61,7 +65,7 @@ def add_item( return item_link - def add_items(self, items: list[Item]) -> list[Link]: + def add_items(self, items: Iterable[Item]) -> list[Link]: links: list[Link] = [] for item in items: links.append(self.add_item(item)) @@ -145,19 +149,54 @@ def remove_item(self, id: str) -> Item | None: def clear_items(self) -> None: self.links: list[Link] = [link for link in self.links if not link.is_item()] - def get_child(self, id: str, recursive: bool = False) -> Container | None: - for child in self.get_children(recursive=recursive): - if child.id == id: - return child + def get_collections( + self, *ids: str, recursive: bool = False, sort_links_by_id: bool = True + ) -> Iterator[Collection]: + from .collection import Collection - def get_children(self, recursive: bool = False) -> Iterator[Container]: - for link in self.links: - if link.is_child(): - stac_object = link.get_target(start_href=self._href, reader=self.reader) - if isinstance(stac_object, Container): + for child in self.get_children( + *ids, recursive=recursive, sort_links_by_id=sort_links_by_id + ): + if isinstance(child, Collection) and (not ids or child.id in ids): + yield child + + @deprecated( + "Get all collections is deprecated, use `get_collections(recursive=True)`" + ) + def get_all_collections(self) -> Iterator[Collection]: + yield from self.get_collections(recursive=True) + + def get_child( + self, id: str, recursive: bool = False, sort_links_by_id: bool = True + ) -> Container | None: + return next( + self.get_children( + id, recursive=recursive, sort_links_by_id=sort_links_by_id + ), + None, + ) + + def get_children( + self, *ids: str, recursive: bool = False, sort_links_by_id: bool = True + ) -> Iterator[Container]: + links = self.get_child_links() + if ids and sort_links_by_id: + links = sorted( + links, + key=lambda x: (href := x.get_href()) is None + or all(id not in href for id in ids), + ) + + for link in links: + stac_object = link.get_target(start_href=self._href, reader=self.reader) + + if isinstance(stac_object, Container): + if not ids or stac_object.id in ids: yield stac_object - if recursive: - yield from stac_object.get_children(recursive=recursive) + if recursive: + yield from stac_object.get_children( + *ids, recursive=recursive, sort_links_by_id=sort_links_by_id + ) def get_child_links(self) -> list[Link]: return [link for link in self.links if link.is_child()] diff --git a/src/pystac/item.py b/src/pystac/item.py index 53db877b7..107556048 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -24,7 +24,7 @@ from .geo_interface import GeoInterface from .link import Link from .stac_object import STACObject -from .utils import datetime_to_str, make_posix_style, str_to_datetime +from .utils import make_posix_style if TYPE_CHECKING: from .collection import Collection @@ -120,15 +120,15 @@ def datetime(self) -> dt.datetime | None: @deprecated("Get the datetime from the asset directly") def get_datetime(self, asset: Asset | None = None) -> dt.datetime | None: - if asset and (datetime := asset.extra_fields.get("datetime")): - return str_to_datetime(datetime) + if asset and (datetime := asset.datetime): + return datetime else: return self.properties.datetime @deprecated("Set the datetime on the asset directly") def set_datetime(self, datetime: dt.datetime, asset: Asset | None = None) -> None: if asset: - asset.extra_fields["datetime"] = datetime_to_str(datetime) + asset.datetime = datetime else: self.properties.datetime = datetime @@ -239,16 +239,15 @@ class Properties(Basics, DateTime, Licensing, Providers, Instrument): def __init__( self, *, - datetime: dt.datetime | str | None = None, - start_datetime: dt.datetime | str | None = None, - end_datetime: dt.datetime | str | None = None, bands: list[Band] | list[dict[str, Any]] | None = None, **kwargs: Any, ): self.extra_fields: dict[str, Any] = kwargs - self.datetime = datetime or dt.datetime.now(tz=dt.UTC) - self.start_datetime = start_datetime - self.end_datetime = end_datetime + self.extra_fields.update(DateTime.from_str(kwargs)) + + if not any([self.datetime, self.start_datetime, self.end_datetime]): + self.datetime = dt.datetime.now(tz=dt.UTC) + if bands is not None: self.bands: list[Band] | None = [Band.try_from(band) for band in bands] else: @@ -269,6 +268,12 @@ def __deepcopy__(self, memo: dict[int, Any]) -> Properties: **copy.deepcopy(self.extra_fields, memo), ) + def get(self, key: str, default: Any = None) -> Any: + try: + return self[key] + except KeyError: + return default + @classmethod def try_from( cls, @@ -287,6 +292,8 @@ def from_dict(cls, data: dict[str, Any]) -> Properties: def to_dict(self) -> dict[str, Any]: data = copy.deepcopy(self.extra_fields) + data.update(DateTime.to_str(self.extra_fields)) + if self.bands is not None: data["bands"] = [band.to_dict() for band in self.bands] - return {k: v for k, v in data.items() if v is not None} + return {k: v for k, v in data.items() if v is not None or k == "datetime"} diff --git a/src/pystac/item_collection.py b/src/pystac/item_collection.py index d922052a6..f47e5c10e 100644 --- a/src/pystac/item_collection.py +++ b/src/pystac/item_collection.py @@ -1,12 +1,24 @@ +from __future__ import annotations + +import copy from collections.abc import Iterator -from typing import override +from pathlib import Path +from typing import Any, Literal, TypedDict, override from .item import Item +from .reader import DEFAULT_READER, Reader +from .utils import make_absolute_href + + +class T_ItemCollection(TypedDict): + type: Literal["FeatureCollection"] + features: list[dict[str, Any]] class ItemCollection: - def __init__(self, items: list[Item]): + def __init__(self, items: list[Item], **kwargs: Any): self.items: list[Item] = items + self.extra_fields: dict[str, Any] = kwargs def __len__(self) -> int: return len(self.items) @@ -20,3 +32,41 @@ def __iter__(self) -> Iterator[Item]: @override def __repr__(self) -> str: return f"ItemCollection({self.items})" + + def to_dict(self) -> dict[str, Any]: + data = copy.deepcopy(self.extra_fields) + return { + "type": "FeatureCollection", + "features": [item.to_dict() for item in self.items], + **data, + } + + @classmethod + def try_from(cls, data: dict[str, Any] | ItemCollection) -> ItemCollection: + if isinstance(data, ItemCollection): + return data + else: + return cls(**data) + + @classmethod + def from_dict( + cls, + data: dict[str, Any], + preserve_dict: bool = True, + ) -> ItemCollection: + if preserve_dict: + data = copy.deepcopy(data) + + items = data.get("features", []) + extra_fields = {k: v for k, v in data.items() if k not in ("features", "type")} + + return cls(items=[Item.from_dict(item) for item in items], **extra_fields) + + @classmethod + def from_file( + cls, path: str | Path, reader: Reader = DEFAULT_READER + ) -> ItemCollection: + href = make_absolute_href(str(path)) + data = reader.get_json(href) + + return ItemCollection.from_dict(data) diff --git a/src/pystac/link.py b/src/pystac/link.py index 23eabcb4f..352151726 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -123,6 +123,11 @@ def get_href(self) -> str | None: def set_href(self, href: str) -> None: self._href = href + @property + @deprecated("href is deprecated, use .get_href()") + def href(self) -> str | None: + return self.get_href() + @property @deprecated("target is deprecated, either use .get_href() or .get_target()") def target(self) -> str | STACObject | None: diff --git a/src/pystac/utils.py b/src/pystac/utils.py index e2f9e6f9e..198d6edb0 100644 --- a/src/pystac/utils.py +++ b/src/pystac/utils.py @@ -464,6 +464,24 @@ def str_to_datetime(s: str) -> datetime: return dateutil.parser.isoparse(s) +def to_datetime_str(value: datetime | str | None) -> str | None: + if value is None: + return None + elif isinstance(value, str): + return value + else: + return datetime_to_str(value) + + +def from_datetime_str(value: datetime | str | None) -> datetime | None: + if value is None: + return None + elif isinstance(value, datetime): + return value + else: + return str_to_datetime(value) + + def now_in_utc() -> datetime: """Returns a datetime value of now with the UTC timezone applied""" return datetime.now(UTC) diff --git a/tests/v1/test_collection.py b/tests/v1/test_collection.py index 28ad7bb2d..de0473a6b 100644 --- a/tests/v1/test_collection.py +++ b/tests/v1/test_collection.py @@ -30,6 +30,10 @@ from .utils import ARBITRARY_BBOX, ARBITRARY_GEOM, TestCases + +pytestmark = pytest.mark.passing_v2 + + TEST_DATETIME = datetime(2020, 3, 14, 16, 32) @@ -62,6 +66,7 @@ def test_provider_to_from_dict() -> None: ) +@pytest.mark.xfail(reason="SpatialExtent.from_coordinates is not supported in pystac v2") def test_spatial_extent_from_coordinates() -> None: extent = SpatialExtent.from_coordinates(ARBITRARY_GEOM["coordinates"]) @@ -79,6 +84,7 @@ def test_read_eo_items_are_heritable() -> None: assert EOExtension.has_extension(item) +@pytest.mark.xfail(reason="Collection does not have a catalog type in pystac v2") def test_save_uses_previous_catalog_type() -> None: collection = TestCases.case_8() assert collection.STAC_OBJECT_TYPE == pystac.STACObjectType.COLLECTION @@ -92,6 +98,7 @@ def test_save_uses_previous_catalog_type() -> None: assert collection2.catalog_type == CatalogType.SELF_CONTAINED +@pytest.mark.xfail(reason="Collection does not have a catalog type in pystac v2") def test_clone_uses_previous_catalog_type() -> None: catalog = TestCases.case_8() assert catalog.catalog_type == CatalogType.SELF_CONTAINED @@ -207,12 +214,13 @@ def test_update_extents() -> None: assert [ [ - item2.common_metadata.start_datetime, + datetime_to_str(item2.common_metadata.start_datetime), base_extent.temporal.intervals[0][1], ] ] == collection.extent.temporal.intervals +@pytest.mark.xfail(reason="Supplying href in init is not supported in pystac v2") def test_supplying_href_in_init_does_not_fail() -> None: test_href = "http://example.com/collection.json" spatial_extent = SpatialExtent(bboxes=[ARBITRARY_BBOX]) @@ -226,6 +234,7 @@ def test_supplying_href_in_init_does_not_fail() -> None: assert collection.get_self_href() == test_href +@pytest.mark.xfail(reason="Caching is not implemented in pystac v2") def test_collection_with_href_caches_by_href() -> None: collection = pystac.Collection.from_file( TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") @@ -270,6 +279,7 @@ def test_get_assets() -> None: assert no_assets == {} +@pytest.mark.xfail(reason="Pystac v2 doesn't support summaries yet") def test_removing_optional_attributes() -> None: path = TestCases.get_path("data-files/collections/with-assets.json") with open(path) as file: @@ -299,7 +309,6 @@ def test_removing_optional_attributes() -> None: collection_as_dict = collection.to_dict() for key in ( "title", - "stac_extensions", "keywords", "providers", "summaries", @@ -318,12 +327,20 @@ def test_from_dict_preserves_dict() -> None: _ = Collection.from_dict(param_dict) assert param_dict == collection_dict + +@pytest.mark.xfail(reason="Dict is never mutated in pystac v2") +def test_from_dict_with_preserve_dict_False_does_not_preserve_dict() -> None: + path = TestCases.get_path("data-files/collections/with-assets.json") + with open(path) as f: + collection_dict = json.load(f) + param_dict = deepcopy(collection_dict) # assert that the parameter is not preserved with # non-default parameter _ = Collection.from_dict(param_dict, preserve_dict=False, migrate=False) assert param_dict != collection_dict +@pytest.mark.xfail(reason="Collection.from_dict does not set self href in pystac v2") def test_from_dict_set_root() -> None: path = TestCases.get_path("data-files/examples/hand-0.8.1/collection.json") with open(path) as f: @@ -333,6 +350,7 @@ def test_from_dict_set_root() -> None: assert collection.get_root() is catalog +@pytest.mark.xfail(reason="Pystac v2 doesn't support summaries yet") def test_schema_summary() -> None: collection = pystac.Collection.from_file( TestCases.get_path( @@ -395,8 +413,9 @@ def test_temporal_extent_init_typing() -> None: _ = TemporalExtent([[start_datetime, end_datetime]]) +@pytest.mark.xfail(reason="pystac v2 uses strings to represent interval") @pytest.mark.block_network() -def test_temporal_extent_allows_single_interval() -> None: +def test_temporal_extent_represents_interval_as_datetimes() -> None: start_datetime = str_to_datetime("2022-01-01T00:00:00Z") end_datetime = str_to_datetime("2022-01-31T23:59:59Z") @@ -406,14 +425,28 @@ def test_temporal_extent_allows_single_interval() -> None: assert temporal_extent.intervals == [interval] +@pytest.mark.block_network() +def test_temporal_extent_allows_single_interval() -> None: + start = "2022-01-01T00:00:00Z" + end = "2022-01-31T23:59:59Z" + start_datetime = str_to_datetime(start) + end_datetime = str_to_datetime(end) + + interval = [start_datetime, end_datetime] + temporal_extent = TemporalExtent(intervals=interval) + + assert temporal_extent.intervals == [[start, end]] + + @pytest.mark.block_network() def test_temporal_extent_allows_single_interval_open_start() -> None: - end_datetime = str_to_datetime("2022-01-31T23:59:59Z") + end = "2022-01-31T23:59:59Z" + end_datetime = str_to_datetime(end) interval = [None, end_datetime] temporal_extent = TemporalExtent(intervals=interval) - assert temporal_extent.intervals == [interval] + assert temporal_extent.intervals == [[None, end]] @pytest.mark.block_network() @@ -494,8 +527,8 @@ def test_extent_from_items() -> None: assert len(extent.temporal.intervals) == 1 interval = extent.temporal.intervals[0] - assert interval[0] == datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) - assert interval[1] == datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC) + assert interval[0] == datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC)) + assert interval[1] == datetime_to_str(datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC)) def test_extent_to_from_dict() -> None: @@ -569,14 +602,14 @@ def test_clone(self) -> None: assert isinstance(cloned_collection, self.BasicCustomCollection) + @pytest.mark.xfail(reason="Pystac v2 requires that custom classes match method signatures") def test_collection_get_item_works(self) -> None: path = TestCases.get_path( "data-files/catalogs/test-case-1/country-1/area-1-1/collection.json" ) custom_collection = self.BasicCustomCollection.from_file(path) collection = custom_collection.clone() - with pytest.warns(DeprecationWarning): - collection.get_item("area-1-1-imagery") + collection.get_item("area-1-1-imagery") def test_collection_get_item_raises_type_error() -> None: @@ -711,8 +744,7 @@ def test_permissive_temporal_extent_deserialization(collection: Collection) -> N collection_dict["extent"]["temporal"]["interval"] = collection_dict["extent"][ "temporal" ]["interval"][0] - with pytest.warns(UserWarning): - Collection.from_dict(collection_dict) + Collection.from_dict(collection_dict) @pytest.mark.parametrize("fixture_name", ("sample_item_collection", "sample_items")) @@ -729,8 +761,8 @@ def test_from_items(fixture_name: str, request: pytest.FixtureRequest) -> None: start = collection.extent.temporal.intervals[0][0] end = collection.extent.temporal.intervals[0][1] - assert start and start <= str_to_datetime(item.properties["start_datetime"]) - assert end and end >= str_to_datetime(item.properties["end_datetime"]) + assert start and str_to_datetime(start) <= item.properties["start_datetime"] + assert end and str_to_datetime(end) >= item.properties["end_datetime"] if isinstance(items, ItemCollection): expected = {(link["rel"], link["href"]) for link in items.extra_fields["links"]} @@ -831,7 +863,7 @@ def test_from_dict_null_extent(collection: Collection) -> None: with pytest.warns(UserWarning): c = Collection.from_dict(d) - assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] + assert c.extent.spatial.to_dict()["bbox"] == [[-180, -90, 180, 90]] assert c.extent.temporal.to_dict()["interval"] == [[None, None]] @@ -841,5 +873,5 @@ def test_from_dict_missing_extent(collection: Collection) -> None: with pytest.warns(UserWarning): c = Collection.from_dict(d) - assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] + assert c.extent.spatial.to_dict()["bbox"] == [[-180, -90, 180, 90]] assert c.extent.temporal.to_dict()["interval"] == [[None, None]] diff --git a/tests/v1/test_common_metadata.py b/tests/v1/test_common_metadata.py index c2c0edfb5..74bb54f70 100644 --- a/tests/v1/test_common_metadata.py +++ b/tests/v1/test_common_metadata.py @@ -33,8 +33,8 @@ def test_datetimes(date_time_range_item: Item) -> None: # save dict of original item to check that `common_metadata` # method doesn't mutate self.item_1 before = date_time_range_item.clone().to_dict() - start_datetime_str = date_time_range_item.properties["start_datetime"] - assert isinstance(start_datetime_str, str) + start_datetime = date_time_range_item.properties["start_datetime"] + assert isinstance(start_datetime, datetime) cm = date_time_range_item.common_metadata assert isinstance(cm, CommonMetadata) @@ -43,6 +43,7 @@ def test_datetimes(date_time_range_item: Item) -> None: assert cm.providers is None +@pytest.mark.xfail(reason="pystac v2 purposefully converts datetimes to datetime objects") def test_common_metadata_start_datetime(date_time_range_item: Item) -> None: x = date_time_range_item.clone() start_datetime_str = "2018-01-01T13:21:30Z" @@ -59,6 +60,7 @@ def test_common_metadata_start_datetime(date_time_range_item: Item) -> None: assert x.properties["start_datetime"] == example_datetime_str +@pytest.mark.xfail(reason="pystac v2 purposefully converts datetimes to datetime objects") def test_common_metadata_end_datetime(date_time_range_item: Item) -> None: x = date_time_range_item.clone() end_datetime_str = "2018-01-01T13:31:30Z" @@ -75,6 +77,7 @@ def test_common_metadata_end_datetime(date_time_range_item: Item) -> None: assert x.properties["end_datetime"] == example_datetime_str +@pytest.mark.xfail(reason="pystac v2 purposefully converts datetimes to datetime objects") def test_common_metadata_created(sample_full_item: Item) -> None: x = sample_full_item.clone() created_str = "2016-05-04T00:00:01Z" @@ -91,6 +94,7 @@ def test_common_metadata_created(sample_full_item: Item) -> None: assert x.properties["created"] == example_datetime_str +@pytest.mark.xfail(reason="pystac v2 purposefully converts datetimes to datetime objects") def test_common_metadata_updated(sample_full_item: Item) -> None: x = sample_full_item.clone() updated_str = "2017-01-01T00:30:55Z" diff --git a/tests/v1/utils/test_cases.py b/tests/v1/utils/test_cases.py index 19324673b..f2bc08ad2 100644 --- a/tests/v1/utils/test_cases.py +++ b/tests/v1/utils/test_cases.py @@ -63,7 +63,7 @@ ] ARBITRARY_EXTENT = Extent( - spatial=SpatialExtent.from_coordinates(ARBITRARY_GEOM["coordinates"]), + spatial=SpatialExtent(ARBITRARY_BBOX), temporal=TemporalExtent.from_now(), ) From 614bae097bf717f7f0f0fdc8c423a2d70d1892cb Mon Sep 17 00:00:00 2001 From: Ian Cooke Date: Mon, 27 Apr 2026 10:20:00 -0400 Subject: [PATCH 57/60] feat: V2 links updates (#1693) * test: verifies self links exist * feat: restore convenience methods for Link clone get_absolute_href (reconsider this) target.setter (deprecated w/ warning) collection, self, child, item, derived_from, and canonical * test: v2 compatibility update of v1/test_link * test: make link check windows compatible * test: re-enable test that relative self hrefs are promoted to absolute * feat: restore Link.resolve_stac_object * cleanup: revert unnecessary changes * doc: comment around link target/href handling subtlety * restore methods with deprecation to minimize friction * restore additional convenience methods * feedback: consume and warn if extra_fields in Link init kwargs * remove unnecessary typing change --- src/pystac/link.py | 136 ++++++++++++++++++++++- tests/v1/conftest.py | 7 ++ tests/v1/posix_paths/test_posix_paths.py | 10 ++ tests/v1/test_link.py | 115 ++++++++++++------- 4 files changed, 228 insertions(+), 40 deletions(-) diff --git a/src/pystac/link.py b/src/pystac/link.py index 352151726..f4ebdac96 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -2,18 +2,20 @@ import copy import warnings -from typing import TYPE_CHECKING, Any, override +from typing import TYPE_CHECKING, Any, Self, override from typing_extensions import deprecated +import pystac from pystac.errors import STACError from pystac.media_type import MediaType from pystac.rel_type import RelType -from pystac.utils import make_absolute_href, make_posix_style +from pystac.utils import is_absolute_href, make_absolute_href, make_posix_style from .reader import Reader if TYPE_CHECKING: + from . import Catalog, Collection, Item from .stac_object import STACObject HIERARCHICAL_LINKS = [ @@ -46,10 +48,18 @@ def __init__( self.method: str | None = method self.headers: dict[str, str | list[str]] | None = headers self.body: Any | None = body + extra_fields = kwargs.pop("extra_fields", None) self.extra_fields: dict[str, Any] = kwargs + if extra_fields: + warnings.warn( + "Pass extra_fields entries as kwargs " + "instead of extra_fields as keyword argument." + ) + self.extra_fields.update(extra_fields) if isinstance(target, STACObject): self._href: str | None = href or target.get_self_href() + self.title = title or getattr(target, "title", None) self._target: STACObject | None = target elif href and target: raise ValueError("Both target and href were provided as strings") @@ -82,6 +92,9 @@ def __getattribute__(self, name: str, /) -> Any: else: return super().__getattribute__(name) + def clone(self: Self) -> Self: + return copy.deepcopy(self) + @classmethod def try_from(cls, data: dict[str, Any] | Link) -> Link: if isinstance(data, Link): @@ -120,6 +133,12 @@ def is_json(self) -> bool: def get_href(self) -> str | None: return self._href or self._target and self._target.get_self_href() + def get_absolute_href(self, start_href: str = "") -> str | None: + href = self.get_href() + if href is None: + return href + return make_absolute_href(href, start_href, start_is_dir=False) + def set_href(self, href: str) -> None: self._href = href @@ -133,6 +152,34 @@ def href(self) -> str | None: def target(self) -> str | STACObject | None: return self._target or self._href + @target.setter + @deprecated("target is deprecated, either use .set_href() or .set_target()") + def target(self, value: str | STACObject) -> None: + if isinstance(value, str): + warnings.warn( + "Setting Link.target to href is no longer supported pystac v2. " + "Assigning value to href instead." + ) + self.set_href(value) + else: + self.set_target(value) + if href := value.get_self_href(): + self.set_href(href) + + @deprecated("use .get_href()") + def get_target_str(self) -> str | None: + """Returns this link's target as a string. + + If a string href was provided, returns that. If not, tries to resolve + the self link of the target object. + """ + return self.get_href() + + @deprecated("use bool(link.get_href())") + def has_target_href(self) -> bool: + """Returns true if this link has a string href in its target information.""" + return bool(self.get_href()) + def get_target(self, start_href: str | None, reader: Reader) -> STACObject: from .stac_object import STACObject @@ -183,6 +230,91 @@ def to_dict(self, transform_href: bool | None = None) -> dict[str, Any]: data["body"] = self.body return data + def resolve_stac_object(self, start_href: str = "") -> Link: + """Resolves a STAC object from the HREF of this link, if the link is not + already resolved. + + Args: + start_href : Optional string to put ahead of the href in this Link. + + NOTE: This uses reader.DEFAULT_READER to read the HREF, if necessary. + """ + if self._target: + return self + elif self._href: + # If it's a relative link, base it off the parent. + target_href = self._href + if not is_absolute_href(target_href): + target_href = make_absolute_href(self._href, start_href=start_href) + try: + obj = pystac.read_file(target_href) + except Exception as e: + raise STACError( + f"HREF: '{target_href}' does not resolve to a STAC object" + ) from e + self._target = obj + else: + raise ValueError("Cannot resolve STAC object without a target") + + return self + @override def __repr__(self) -> str: return f"Link(rel={self.rel}, href={self._href})" + + ##### Convenience methods for Link creation ##### + @classmethod + def root(cls: type[Self], c: Catalog) -> Self: + """Creates a link to a root Catalog or Collection.""" + return cls(RelType.ROOT, c, media_type=MediaType.JSON) + + @classmethod + def parent(cls: type[Self], c: Catalog, title: str | None = None) -> Self: + """Creates a link to a parent Catalog or Collection.""" + return cls(RelType.PARENT, c, title=title, media_type=MediaType.JSON) + + @classmethod + def collection(cls: type[Self], c: Collection) -> Self: + """Creates a link to a Collection.""" + return cls(RelType.COLLECTION, c, media_type=MediaType.JSON) + + @classmethod + def self_href(cls: type[Self], href: str) -> Self: + """Creates a self link to a file's location.""" + return cls(RelType.SELF, href, media_type=MediaType.JSON) + + @classmethod + def child(cls: type[Self], c: Catalog, title: str | None = None) -> Self: + """Creates a link to a child Catalog or Collection.""" + return cls(RelType.CHILD, c, title=title, media_type=MediaType.JSON) + + @classmethod + def item(cls: type[Self], item: Item, title: str | None = None) -> Self: + """Creates a link to an Item.""" + return cls(RelType.ITEM, item, title=title, media_type=MediaType.GEOJSON) + + @classmethod + def derived_from( + cls: type[Self], item: Item | str, title: str | None = None + ) -> Self: + """Creates a link to a derived_from Item.""" + return cls( + RelType.DERIVED_FROM, + item, + title=title, + media_type=MediaType.JSON, + ) + + @classmethod + def canonical( + cls: type[Self], + item_or_collection: Item | Collection, + title: str | None = None, + ) -> Self: + """Creates a canonical link to an Item or Collection.""" + return cls( + RelType.CANONICAL, + item_or_collection, + title=title, + media_type=MediaType.JSON, + ) diff --git a/tests/v1/conftest.py b/tests/v1/conftest.py index 72b0aed00..4d65e6240 100644 --- a/tests/v1/conftest.py +++ b/tests/v1/conftest.py @@ -36,6 +36,13 @@ def collection() -> Catalog: return Collection("test-collection", "A test collection", ARBITRARY_EXTENT) +@pytest.fixture +def self_link_collection() -> Catalog: + c = Collection("test-collection", "A test collection", ARBITRARY_EXTENT) + c.set_self_href("file:///a/real/url.json") + return c + + @pytest.fixture def multi_extent_collection() -> Collection: # TODO this code is repeated many times; refactor to use this fixture diff --git a/tests/v1/posix_paths/test_posix_paths.py b/tests/v1/posix_paths/test_posix_paths.py index ac34abc1b..9cc8b41d0 100644 --- a/tests/v1/posix_paths/test_posix_paths.py +++ b/tests/v1/posix_paths/test_posix_paths.py @@ -71,6 +71,16 @@ def test_create_item_containing_posix_hrefs(tmp_path: Path) -> None: pystac.read_file(collection_href) +def test_posix_self_link_added_from_file(tmp_path: Path) -> None: + href = get_data_file("item/sample-item.json") + item = pystac.Item.from_file(href) + item.links.append(pystac.Link(rel="self", href=href)) + check_link(item.get_single_link(rel="self")) + item2 = pystac.read_file(href) + item2.links.append(pystac.Link(rel="self", href=href)) + check_link(item2.get_single_link(rel="self")) + + @pytest.mark.skipif(os.name != "nt", reason="windows only test") def test_posix_self_link_from_absolute_href(tmp_path: Path) -> None: # Check that we convert to a windows style (\\) absolute href to posix style diff --git a/tests/v1/test_link.py b/tests/v1/test_link.py index 3938d13e1..c735b2763 100644 --- a/tests/v1/test_link.py +++ b/tests/v1/test_link.py @@ -1,9 +1,10 @@ import json import os +import re from datetime import datetime from pathlib import Path from tempfile import TemporaryDirectory -from typing import Any, cast +from typing import Any import pytest @@ -15,6 +16,8 @@ from .utils.test_cases import ARBITRARY_EXTENT +pytestmark = pytest.mark.passing_v2 + TEST_DATETIME: datetime = datetime(2020, 3, 14, 16, 32) @@ -32,7 +35,7 @@ def test_minimal(item: pystac.Item) -> None: assert target == link.get_href() assert target == link.get_absolute_href() - expected_repr = f"" + expected_repr = f"Link(rel={rel}, href={target})" assert expected_repr == link.__repr__() assert not link.is_resolved() @@ -51,20 +54,14 @@ def test_minimal(item: pystac.Item) -> None: assert expected_dict == clone.to_dict() - # Try the modification methods. - assert link.owner is None - link.set_owner(None) - assert link.owner is None - - link.set_owner(item) - assert item == link.owner - def test_relative() -> None: rel = "my rel" target = "../elsewhere" mime_type = "example/stac_thing" - link = pystac.Link(rel, target, mime_type, "a title", extra_fields={"a": "b"}) + link = pystac.Link( + rel, target, media_type=mime_type, title="a title", extra_fields={"a": "b"} + ) expected_dict = { "rel": rel, "href": target, @@ -115,34 +112,45 @@ def test_resolved_self_href() -> None: def test_target_getter_setter(item: pystac.Item) -> None: link = pystac.Link("my rel", target="./foo/bar.json") assert link.target == "./foo/bar.json" - assert link.get_target_str() == "./foo/bar.json" + assert link.get_href() == "./foo/bar.json" + item.set_self_href("file:///bad/beef.json") link.target = item assert link.target == item - assert link.get_target_str() == item.get_self_href() - - link.target = "./bar/foo.json" - assert link.target == "./bar/foo.json" + # get_target_str is gone, so replaced with get_href + assert link.get_href() == item.get_self_href() + + # target property + link.set_href("./dead/d011.json") + assert link.target == item # target remains unchanged + assert link.get_href() == "./dead/d011.json" # href is updated + + expected_pattern_ltsh = r"file://([A-Z]:)?/bad/beef.json" + actual_ltsh = link.target.get_self_href() + # windows compatible absolute url regular expression. + # set_href doesn't update target, but self_href is mutated to be absolute. + assert actual_ltsh and re.match(expected_pattern_ltsh, actual_ltsh), ( + f"{actual_ltsh} does not match {expected_pattern_ltsh}" + ) def test_get_target_str_no_href(item: pystac.Item) -> None: item.remove_links("self") link = pystac.Link("self", target=item) item.add_link(link) - assert link.get_target_str() is None + assert link.get_href() is None def test_relative_self_href(item: pystac.Item) -> None: with TemporaryDirectory() as temporary_directory: - pystac.write_file( - item, + item.save_object( include_self_link=False, dest_href=os.path.join(temporary_directory, "item.json"), ) previous = os.getcwd() try: os.chdir(temporary_directory) - item = cast(pystac.Item, pystac.read_file("item.json")) + item = pystac.read_file("item.json") href = item.get_self_href() assert href assert os.path.isabs(href), f"Not an absolute path: {href}" @@ -183,8 +191,9 @@ def test_auto_title_is_serialized(item: pystac.Item) -> None: extent=extent, title="Collection Title", ) + collection.set_self_href("file:///dev/null") + ## link.to_dict needs to extract href from `target` or have it passed in link = pystac.Link("my rel", target=collection) - assert link.to_dict().get("title") == collection.title @@ -203,7 +212,14 @@ def test_title_as_init_argument(item: pystac.Item) -> None: extent=extent, title="Collection Title", ) - link = pystac.Link("my rel", title=link_title, target=collection) + + ## link.to_dict needs to extract href from `target` or have it passed in + link = pystac.Link( + "my rel", + title=link_title, + target=collection, + href="file:///dev/null", + ) assert link.title == link_title assert link.to_dict().get("title") == link_title @@ -212,7 +228,14 @@ def test_title_as_init_argument(item: pystac.Item) -> None: def test_serialize_link() -> None: href = "https://some-domain/path/to/item.json" title = "A Test Link" - link = pystac.Link(pystac.RelType.SELF, href, pystac.MediaType.JSON, title) + # Positional is lame + # link = Link(RelType.SELF, None, href, MediaType.JSON, title) + link = pystac.Link( + rel=pystac.RelType.SELF, + href=href, + media_type=pystac.MediaType.JSON, + title=title, + ) link_dict = link.to_dict() assert link_dict["rel"] == "self" @@ -223,7 +246,6 @@ def test_serialize_link() -> None: def test_static_from_dict_round_trip() -> None: test_cases: list[dict[str, Any]] = [ - {"rel": "", "href": ""}, # Not valid, but works. {"rel": "r", "href": "t"}, {"rel": "r", "href": "/t"}, {"rel": "r", "href": "t", "type": "a/b", "title": "t", "c": "d", "1": 2}, @@ -232,38 +254,55 @@ def test_static_from_dict_round_trip() -> None: d2 = pystac.Link.from_dict(d).to_dict() assert d == d2 d = {"rel": "self", "href": "t"} - d2 = {"rel": "self", "href": make_posix_style(os.path.join(os.getcwd(), "t"))} - assert pystac.Link.from_dict(d).to_dict() == d2 + d2 = {"rel": "self", "href": "t"} + assert Link.from_dict(d).to_dict() == d2 def test_static_from_dict_failures() -> None: dicts: list[dict[str, Any]] = [{}, {"href": "t"}, {"rel": "r"}] for d in dicts: - with pytest.raises(KeyError): - pystac.Link.from_dict(d) + with pytest.raises((TypeError, ValueError)): + Link.from_dict(d) -def test_static_collection(collection: pystac.Collection) -> None: - link = pystac.Link.collection(collection) - expected = {"rel": "collection", "href": None, "type": "application/json"} +def test_static_collection(self_link_collection: Collection) -> None: + link = pystac.Link.collection(self_link_collection) + expected = { + "rel": "collection", + "href": self_link_collection.get_self_href(), + "type": "application/json", + } assert expected == link.to_dict() -def test_static_child(collection: pystac.Collection) -> None: - link = pystac.Link.child(collection) - expected = {"rel": "child", "href": None, "type": "application/json"} +def test_static_child(self_link_collection: Collection) -> None: + link = pystac.Link.child(self_link_collection) + expected = { + "rel": "child", + "href": self_link_collection.get_self_href(), + "type": "application/json", + } assert expected == link.to_dict() def test_static_canonical_item(item: pystac.Item) -> None: + item.set_self_href("file:///bad/beef.json") link = pystac.Link.canonical(item) - expected = {"rel": "canonical", "href": None, "type": "application/json"} + expected = { + "rel": "canonical", + "href": item.get_self_href(), + "type": "application/json", + } assert expected == link.to_dict() -def test_static_canonical_collection(collection: pystac.Collection) -> None: - link = pystac.Link.canonical(collection) - expected = {"rel": "canonical", "href": None, "type": "application/json"} +def test_static_canonical_collection(self_link_collection: Collection) -> None: + link = pystac.Link.canonical(self_link_collection) + expected = { + "rel": "canonical", + "href": self_link_collection.get_self_href(), + "type": "application/json", + } assert expected == link.to_dict() From 1a37397982f346277373b565db607cc229fb57cd Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 28 Apr 2026 10:52:07 -0400 Subject: [PATCH 58/60] fix: V2 item collection (#1709) --- src/pystac/item.py | 7 +++++ src/pystac/item_collection.py | 50 ++++++++++++++++++++++++++++++-- tests/v1/test_item_collection.py | 25 ++++++++++++---- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/pystac/item.py b/src/pystac/item.py index 107556048..7107de576 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -89,6 +89,13 @@ def __init__( self._collection: Collection | str | None = collection + @classmethod + def try_from(cls, data: dict[str, Any] | Item) -> Item: + if isinstance(data, Item): + return data + else: + return cls(**data) + @override @classmethod def from_dict( diff --git a/src/pystac/item_collection.py b/src/pystac/item_collection.py index f47e5c10e..1ec2e4e8d 100644 --- a/src/pystac/item_collection.py +++ b/src/pystac/item_collection.py @@ -1,10 +1,15 @@ from __future__ import annotations import copy +import warnings from collections.abc import Iterator from pathlib import Path from typing import Any, Literal, TypedDict, override +from typing_extensions import deprecated + +from .container import Container +from .errors import STACTypeError from .item import Item from .reader import DEFAULT_READER, Reader from .utils import make_absolute_href @@ -16,9 +21,24 @@ class T_ItemCollection(TypedDict): class ItemCollection: - def __init__(self, items: list[Item], **kwargs: Any): - self.items: list[Item] = items + def __init__(self, items: list[Item], root: Container | None = None, **kwargs: Any): + self.items: list[Item] = [Item.try_from(item) for item in items] + + if "root" in kwargs: + raise KeyError( + "root is not a valid key word argument to ``ItemCollection()``. " + "If root is required try instantiating the items directly and " + "providing root." + ) + + extra_fields = kwargs.pop("extra_fields", {}) self.extra_fields: dict[str, Any] = kwargs + if extra_fields: + warnings.warn( + "Pass extra_fields entries as kwargs, " + "instead of in extra_fields dictionary." + ) + self.extra_fields.update(extra_fields) def __len__(self) -> int: return len(self.items) @@ -29,6 +49,16 @@ def __getitem__(self, index: int) -> Item: def __iter__(self) -> Iterator[Item]: return iter(self.items) + def __contains__(self, __x: Item) -> bool: + return __x in self.items + + def __add__(self, other: Any) -> ItemCollection: + if not isinstance(other, ItemCollection): + return NotImplemented + + combined = [*self.items, *other.items] + return ItemCollection(items=combined) + @override def __repr__(self) -> str: return f"ItemCollection({self.items})" @@ -41,6 +71,14 @@ def to_dict(self) -> dict[str, Any]: **data, } + def clone(self) -> ItemCollection: + return copy.deepcopy(self) + + @deprecated("Try `ItemCollection.from_dict` and handle any exceptions instead") + @staticmethod + def is_item_collection(data: dict[str, Any]) -> bool: + return data.get("type", "") == "FeatureCollection" + @classmethod def try_from(cls, data: dict[str, Any] | ItemCollection) -> ItemCollection: if isinstance(data, ItemCollection): @@ -53,14 +91,20 @@ def from_dict( cls, data: dict[str, Any], preserve_dict: bool = True, + root: Container | None = None, ) -> ItemCollection: + if data.get("type", "") != "FeatureCollection": + raise STACTypeError(data, cls) + if preserve_dict: data = copy.deepcopy(data) items = data.get("features", []) extra_fields = {k: v for k, v in data.items() if k not in ("features", "type")} - return cls(items=[Item.from_dict(item) for item in items], **extra_fields) + return cls( + items=[Item.from_dict(item, root=root) for item in items], **extra_fields + ) @classmethod def from_file( diff --git a/tests/v1/test_item_collection.py b/tests/v1/test_item_collection.py index 28af1c9e6..4b4c73bcb 100644 --- a/tests/v1/test_item_collection.py +++ b/tests/v1/test_item_collection.py @@ -4,13 +4,17 @@ from typing import Any, cast import pytest +from unittest.mock import Mock import pystac from pystac import Item, StacIO from pystac.item_collection import ItemCollection +from pystac.reader import DEFAULT_READER from .utils import TestCases -from .utils.stac_io_mock import MockDefaultStacIO + + +pytestmark = pytest.mark.passing_v2 SIMPLE_ITEM = TestCases.get_path("data-files/examples/1.0.0-RC1/simple-item.json") CORE_ITEM = TestCases.get_path("data-files/examples/1.0.0-RC1/core-item.json") @@ -20,6 +24,16 @@ "data-files/item-collection/sample-item-collection.json" ) +class MockReader: + mock: Mock + + def __init__(self) -> None: + self.mock = Mock() + + def get_json(self, href: str) -> dict[str, Any]: + self.mock.read_json(href) + return DEFAULT_READER.get_json(href) + @pytest.fixture def item_collection_dict() -> dict[str, Any]: @@ -199,9 +213,8 @@ def test_from_dict_sets_root(item_collection_dict: dict[str, Any]) -> None: def test_to_dict_does_not_read_root_link_of_items() -> None: - with MockDefaultStacIO() as mock_stac_io: - item_collection = pystac.ItemCollection.from_file(ITEM_COLLECTION) - - item_collection.to_dict() + mock_reader = MockReader() + item_collection = ItemCollection.from_file(ITEM_COLLECTION, reader=mock_reader) + item_collection.to_dict() - assert mock_stac_io.mock.read_text.call_count == 1 + assert mock_reader.mock.read_json.call_count == 1 From 75c771e0c8a281d8835bff927bd29b875a2a54a2 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 5 May 2026 11:24:13 -0400 Subject: [PATCH 59/60] fix: V2 summaries (#1710) * Fix static fields file * Make Item Properties a `MutableMapping` * Simplify `Summaries` * Fix RangeSummary coersion --- scripts/pull-static | 2 +- src/pystac/__init__.py | 2 + src/pystac/collection.py | 7 +- src/pystac/extensions/base.py | 4 +- src/pystac/item.py | 32 ++- src/pystac/static/__init__.py | 0 src/pystac/static/fields-normalized.json | 1 + src/pystac/summaries.py | 326 +++++++++-------------- tests/v1/test_collection.py | 6 +- tests/v1/test_summaries.py | 3 + 10 files changed, 166 insertions(+), 217 deletions(-) create mode 100644 src/pystac/static/__init__.py create mode 100644 src/pystac/static/fields-normalized.json diff --git a/scripts/pull-static b/scripts/pull-static index 46b6b27b8..fab104a27 100755 --- a/scripts/pull-static +++ b/scripts/pull-static @@ -5,7 +5,7 @@ set -e VERSION="1.5.0" SRC="https://cdn.jsdelivr.net/npm/@radiantearth/stac-fields@$VERSION/fields-normalized.json" HERE=$(dirname "$0") -DEST=$(dirname "$HERE")/pystac/static/fields-normalized.json +DEST=$(dirname "$HERE")/src/pystac/static/fields-normalized.json echo "Pulling fields-normalized.json from cdn to $DEST" curl "$SRC" | jq -c '{metadata: .metadata}' > "$DEST" diff --git a/src/pystac/__init__.py b/src/pystac/__init__.py index 151adb641..056221244 100644 --- a/src/pystac/__init__.py +++ b/src/pystac/__init__.py @@ -31,6 +31,7 @@ from .provider import Provider, ProviderRole from .rel_type import RelType from .stac_object import STACObject +from .summaries import Summaries from .version import __version__ @@ -87,6 +88,7 @@ def get_stac_version() -> str: "STACTypeError", "STACValidationError", "SpatialExtent", + "Summaries", "TemporalExtent", "read_file", "__version__", diff --git a/src/pystac/collection.py b/src/pystac/collection.py index 34b2177ee..0b98956ab 100644 --- a/src/pystac/collection.py +++ b/src/pystac/collection.py @@ -22,6 +22,7 @@ from .container import Container from .link import Link from .provider import Provider +from .summaries import Summaries from .utils import to_datetime_str if TYPE_CHECKING: @@ -55,7 +56,7 @@ def __init__( keywords: list[str] | None = None, license: str = DEFAULT_LICENSE, providers: list[Provider | dict[str, Any]] | None = None, - summaries: dict[str, Any] | None = None, + summaries: Summaries | dict[str, Any] | None = None, assets: dict[str, Asset | dict[str, Any]] | None = None, item_assets: dict[str, ItemAsset] | None = None, stac_version: str = DEFAULT_STAC_VERSION, @@ -85,7 +86,7 @@ def __init__( "temporal extents" ) self.extent: Extent = Extent.try_from(extent) - self.summaries: dict[str, Any] | None = summaries + self.summaries: Summaries = Summaries.try_from(summaries) self.assets: dict[str, Asset] = {} if assets is not None: @@ -197,7 +198,7 @@ def to_dict( data["providers"] = [provider.to_dict() for provider in self.providers] data["extent"] = self.extent.to_dict() if self.summaries: - data["summaries"] = self.summaries + data["summaries"] = self.summaries.to_dict() if self.bands is not None: data["bands"] = [band.to_dict() for band in self.bands] if self.assets: diff --git a/src/pystac/extensions/base.py b/src/pystac/extensions/base.py index 356851ecb..7abd404fa 100644 --- a/src/pystac/extensions/base.py +++ b/src/pystac/extensions/base.py @@ -37,9 +37,9 @@ def _set_summary( v: list[Any] | pystac.RangeSummary[Any] | dict[str, Any] | None, ) -> None: if v is None: - self.summaries.remove(prop_key) + del self.summaries[prop_key] else: - self.summaries.add(prop_key, v) + self.summaries[prop_key] = v P = TypeVar("P") diff --git a/src/pystac/item.py b/src/pystac/item.py index 7107de576..7a67e02a0 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -3,6 +3,7 @@ import copy import datetime as dt import warnings +from collections.abc import Iterator, MutableMapping from typing import TYPE_CHECKING, Any, ClassVar, final, override from typing_extensions import deprecated @@ -242,7 +243,9 @@ def __geo_interface__(self) -> dict[str, Any]: @final -class Properties(Basics, DateTime, Licensing, Providers, Instrument): +class Properties( + MutableMapping[str, Any], Basics, DateTime, Licensing, Providers, Instrument +): def __init__( self, *, @@ -260,13 +263,32 @@ def __init__( else: self.bands = None + @override def __getitem__(self, key: str) -> Any: return self.extra_fields[key] + @override def __setitem__(self, name: str, value: Any, /) -> None: self.extra_fields[name] = value - def __contains__(self, key: str) -> bool: + @override + def __delitem__(self, key: str) -> None: + del self.extra_fields[key] + + @override + def __iter__(self) -> Iterator[str]: + if self.bands is not None: + return iter({**self.extra_fields, "bands": self.bands}) + return iter(self.extra_fields) + + @override + def __len__(self) -> int: + if self.bands is not None: + return len(self.extra_fields) + 1 + return len(self.extra_fields) + + @override + def __contains__(self, key: object) -> bool: return key == "bands" or key in self.extra_fields def __deepcopy__(self, memo: dict[int, Any]) -> Properties: @@ -275,12 +297,6 @@ def __deepcopy__(self, memo: dict[int, Any]) -> Properties: **copy.deepcopy(self.extra_fields, memo), ) - def get(self, key: str, default: Any = None) -> Any: - try: - return self[key] - except KeyError: - return default - @classmethod def try_from( cls, diff --git a/src/pystac/static/__init__.py b/src/pystac/static/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/pystac/static/fields-normalized.json b/src/pystac/static/fields-normalized.json new file mode 100644 index 000000000..9d5e1dff5 --- /dev/null +++ b/src/pystac/static/fields-normalized.json @@ -0,0 +1 @@ +{"metadata":{"id":{"label":"Identifier"},"keywords":{"label":"Keywords"},"datetime":{"label":"Time of Data","format":"Timestamp","summary":false},"title":{"label":"Title","summary":false},"description":{"label":"Description","format":"CommonMark","summary":false},"roles":{"label":"Purpose"},"start_datetime":{"label":"Time of Data begins","format":"Timestamp","summary":false},"end_datetime":{"label":"Time of Data ends","format":"Timestamp","summary":false},"created":{"label":"Created","format":"Timestamp","summary":"r"},"updated":{"label":"Updated","format":"Timestamp","summary":"r"},"published":{"label":"Published","format":"Timestamp","summary":"r"},"expires":{"label":"Expires","format":"Timestamp","summary":"r"},"unpublished":{"label":"Unpublished","format":"Timestamp","summary":"r"},"license":{"label":"License","format":"License","summary":false},"providers":{"label":"Providers","format":"Providers","summary":false},"platform":{"label":"Platform"},"instruments":{"label":"Instruments","format":"CSV"},"constellation":{"label":"Constellation"},"mission":{"label":"Mission"},"gsd":{"label":"GSD","explain":"Ground Sample Distance","unit":"m"},"bands":{"label":"Bands","items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"description":{"alias":"description","order":2,"label":"Description","format":"CommonMark","summary":false},"gsd":{"alias":"gsd","sortable":true,"label":"GSD","explain":"Ground Sample Distance","unit":"m"},"nodata":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"data_type":{"alias":"data_type","sortable":true,"label":"Data Type of Values","format":"FileDataType"},"statistics":{"alias":"statistics","label":"Statistics","listWithKeys":true,"items":{"mean":{"label":"Average"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stdev":{"label":"Std. Dev.","explain":"Standard Deviation"},"count":{"label":"Count","explain":"Total number of values"},"valid_percent":{"label":"Valid","explain":"Percentage of valid values","unit":"%"}},"itemOrder":["mean","count","maximum","minimum","stdev","valid_percent"]},"eo:common_name":{"alias":"eo:common_name","sortable":true,"order":1,"label":"Common Name"},"eo:center_wavelength":{"alias":"eo:center_wavelength","sortable":true,"label":"Wavelength","explain":"The center wavelength of the band","unit":"μm"},"eo:full_width_half_max":{"alias":"eo:full_width_half_max","sortable":true,"label":"FWHM","explain":"Full Width Half Max","unit":"μm"},"eo:solar_illumination":{"alias":"eo:solar_illumination","sortable":true,"label":"Solar Illumination","unit":"W/m²/μm"},"raster:sampling":{"alias":"raster:sampling","sortable":true,"label":"Sampling","mapping":{"area":"Area","point":"Point (at pixel center)"}},"unit":{"alias":"unit","sortable":true,"label":"Unit of Values"},"raster:bits_per_sample":{"alias":"raster:bits_per_sample","sortable":true,"label":"Bits per Sample"},"raster:spatial_resolution":{"alias":"raster:spatial_resolution","sortable":true,"label":"Resolution","explain":"Average spatial resolution","unit":"m"},"raster:scale":{"alias":"raster:scale","sortable":true,"label":"Scale"},"raster:offset":{"alias":"raster:offset","sortable":true,"label":"Offset"},"raster:histogram":{"alias":"raster:histogram","label":"Histogram","custom":true},"classification:classes":{"alias":"classification:classes","summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"classification:bitfields":{"alias":"classification:bitfields","summary":false,"label":"Bit Mask","items":{"name":{"label":"Name","sortable":true,"order":0},"offset":{"label":"Offset","explain":"Offset to the first bit","order":1},"length":{"label":"Number of bits","order":2},"description":{"label":"Description","order":3,"format":"CommonMark"},"classes":{"alias":"classification:classes","summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"roles":{"label":"Purpose"}},"itemOrder":["classes","name","offset","length","description","roles"]}},"itemOrder":["name","classification:bitfields","raster:bits_per_sample","classification:classes","eo:common_name","data_type","description","eo:full_width_half_max","gsd","raster:histogram","nodata","raster:offset","raster:spatial_resolution","raster:sampling","raster:scale","eo:solar_illumination","statistics","unit","eo:center_wavelength"]},"nodata":{"label":"No-Data Values","format":"CSV","summary":false},"data_type":{"label":"Data Type of Values","format":"FileDataType"},"unit":{"label":"Unit of Values"},"statistics":{"label":"Statistics","listWithKeys":true,"items":{"mean":{"label":"Average"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stdev":{"label":"Std. Dev.","explain":"Standard Deviation"},"count":{"label":"Count","explain":"Total number of values"},"valid_percent":{"label":"Valid","explain":"Percentage of valid values","unit":"%"}},"itemOrder":["mean","count","maximum","minimum","stdev","valid_percent"]},"version":{"label":"Data Version","summary":false},"deprecated":{"label":"Deprecated","summary":false},"experimental":{"label":"Experimental","summary":false},"language":{"label":"Current Language","ext":"language","summary":"v","properties":{"name":{"label":"Name"},"alternate":{"label":"Alternate Name"},"code":{"label":"Code"},"dir":{"label":"Direction","explain":"Reading and writing direction","mapping":{"ltr":"left-to-right","rtl":"right-to-left"},"default":"ltr"}}},"languages":{"label":"Available Languages","ext":"language","summary":false,"items":{"name":{"label":"Name","sortable":true,"order":0},"alternate":{"label":"Alternate Name","sortable":true,"order":1},"code":{"label":"Code","sortable":true,"order":2},"dir":{"label":"Direction","explain":"Reading and writing direction","sortable":true,"order":3,"mapping":{"ltr":"left-to-right","rtl":"right-to-left"},"default":"ltr"}},"itemOrder":["name","alternate","code","dir"]},"contacts":{"label":"Contacts","ext":"contacts","summary":"v","items":{"name":{"label":"Name"},"identifier":{"label":"Identifier"},"position":{"label":"Position"},"organization":{"label":"Organization"},"logo":{"label":"Logo","format":"Image"},"phones":{"label":"Phone","items":{"value":{"label":"Number","format":"Phone","order":0},"roles":{"label":"Used For","order":1,"mapping":{"work":"Work","home":"Personal","fax":"Fax"}}},"itemOrder":["value","roles"]},"emails":{"label":"Email","items":{"value":{"label":"Address","format":"Email","order":0},"roles":{"label":"Used For","order":1,"mapping":{"work":"Work","home":"Personal"}}},"itemOrder":["value","roles"]},"addresses":{"label":"Postal Addresses","format":"Address","items":{"deliveryPoint":{"label":"Street / House","order":0},"city":{"label":"City","order":1},"administrativeArea":{"label":"State / Province","order":2},"postalCode":{"label":"Postal Code","order":3},"country":{"label":"Country","order":4}},"itemOrder":["deliveryPoint","city","administrativeArea","postalCode","country"]},"links":{"label":"Additional Resources","format":"Link"},"contactInstructions":{"label":"Further Instructions"},"roles":{"label":"Types","format":"CSV"}},"itemOrder":["links","emails","contactInstructions","identifier","logo","name","organization","phones","position","addresses","roles"]},"themes":{"label":"Themes","ext":"themes","summary":false,"items":{"scheme":{"label":"Vocabulary","order":0,"format":"Url"},"concepts":{"label":"Terms","order":1,"format":"Concepts","items":{"id":{"label":"Identifier","order":0},"title":{"label":"Title","order":1},"description":{"label":"Description","order":2},"url":{"label":"URL","order":3,"format":"Url"}},"itemOrder":["id","title","description","url"]}},"itemOrder":["scheme","concepts"]},"crs":{"label":"CRS","format":"CRS","explain":"Coordinate Reference System"},"anon:size":{"label":"Uncertainty","unit":"°","explain":"The size of one side of the anonymized bounding box"},"anon:warning":{"label":"Warning","summary":false},"classification:classes":{"summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"classification:bitfields":{"summary":false,"label":"Bit Mask","items":{"name":{"label":"Name","sortable":true,"order":0},"offset":{"label":"Offset","explain":"Offset to the first bit","order":1},"length":{"label":"Number of bits","order":2},"description":{"label":"Description","order":3,"format":"CommonMark"},"classes":{"alias":"classification:classes","summary":false,"label":"Classes","items":{"color_hint":{"label":"Color","order":0,"format":"HexColor"},"value":{"label":"Value","sortable":true,"order":1},"title":{"label":"Title","sortable":true,"order":2},"name":{"label":"Identifier","sortable":true,"order":3},"description":{"label":"Description","order":4,"format":"CommonMark"},"percentage":{"label":"Percentage of samples","sortable":true,"order":5,"unit":"%"},"count":{"label":"Number of samples","sortable":true,"order":6},"nodata":{"label":"No-data value","order":7,"default":false}},"itemOrder":["color_hint","value","title","name","description","percentage","count","nodata"]},"roles":{"label":"Purpose"}},"itemOrder":["classes","name","offset","length","description","roles"]},"cube:dimensions":{"label":"Dimensions","summary":false,"listWithKeys":true,"items":{"type":{"label":"Type","order":0},"axis":{"label":"Axis","order":1},"description":{"label":"Description","format":"CommonMark","order":2},"extent":{"label":"Extent","format":"Extent","order":3},"bbox":{"alias":"proj:bbox","order":3,"label":"Bounding Box","custom":true,"summary":false},"values":{"label":"Values","order":4},"step":{"label":"Step","order":5},"unit":{"alias":"unit","order":5,"label":"Unit of Values"},"geometry_types":{"label":"Geometry Types","order":5},"reference_system":{"label":"Reference System","explain":"Coordinate / Temporal / Other Reference System","order":6}},"itemOrder":["type","axis","description","extent","bbox","values","step","unit","geometry_types","reference_system"]},"cube:variables":{"label":"Variables","summary":false,"listWithKeys":true,"items":{"dimensions":{"label":"Dimensions","order":0},"type":{"label":"Type","order":1,"mapping":{"data":"Measured values","auxiliary":"Coordinate data"}},"description":{"label":"Description","format":"CommonMark","order":2},"extent":{"label":"Extent","format":"Extent","order":3},"values":{"label":"Values","order":4},"step":{"label":"Step","order":5},"unit":{"alias":"unit","order":6,"label":"Unit of Values"}},"itemOrder":["dimensions","type","description","extent","values","step","unit"]},"eo:bands":{"label":"Spectral Bands","items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"common_name":{"alias":"eo:common_name","sortable":true,"order":1,"label":"Common Name"},"center_wavelength":{"alias":"eo:center_wavelength","sortable":true,"order":2,"label":"Wavelength","explain":"The center wavelength of the band","unit":"μm"},"full_width_half_max":{"alias":"eo:full_width_half_max","sortable":true,"order":3,"label":"FWHM","explain":"Full Width Half Max","unit":"μm"},"solar_illumination":{"alias":"eo:solar_illumination","sortable":true,"order":7,"label":"Solar Illumination","unit":"W/m²/μm"},"description":{"alias":"description","label":"Description","format":"CommonMark","summary":false},"gsd":{"alias":"gsd","sortable":true,"label":"GSD","explain":"Ground Sample Distance","unit":"m"},"cloud_cover":{"alias":"eo:cloud_cover","sortable":true,"label":"Cloud Cover","unit":"%"}},"itemOrder":["name","cloud_cover","common_name","description","gsd","center_wavelength","full_width_half_max","solar_illumination"]},"eo:cloud_cover":{"label":"Cloud Cover","unit":"%"},"eo:snow_cover":{"label":"Snow/Ice Cover","unit":"%"},"eo:common_name":{"label":"Common Name"},"eo:center_wavelength":{"label":"Wavelength","explain":"The center wavelength of the band","unit":"μm"},"eo:full_width_half_max":{"label":"FWHM","explain":"Full Width Half Max","unit":"μm"},"eo:solar_illumination":{"label":"Solar Illumination","unit":"W/m²/μm"},"forecast:reference_datetime":{"label":"Reference Time","format":"Timestamp","summary":"r"},"forecast:horizon":{"label":"Forecast Horizon","explain":"The time between the reference time and the forecast time","format":"Duration"},"forecast:duration":{"label":"Forecast Length","format":"Duration"},"file:bits_per_sample":{"alias":"raster:bits_per_sample","label":"Bits per Sample"},"file:byte_order":{"label":"Byte Order"},"file:checksum":{"label":"Checksum","format":"Checksum","summary":false},"file:data_type":{"alias":"data_type","label":"Data Type of Values","format":"FileDataType"},"file:header_size":{"label":"Header Size","format":"FileSize","summary":false},"file:nodata":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"file:size":{"label":"Size","format":"FileSize","summary":false},"file:unit":{"alias":"unit","label":"Unit of Values"},"file:values":{"label":"Map of Values","summary":false,"items":{"values":{"label":"Values","format":"CSV","order":1},"summary":{"label":"Summary","order":0}},"itemOrder":["summary","values"]},"file:local_path":{"label":"Local Path","summary":false},"nodata:values":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"goes:orbital_slot":{"label":"Orbital Slot"},"goes:system_environment":{"label":"System Environment","mapping":{"OR":"Operational system, real-time data","OT":"Operational system, test data","IR":"Test system, real-time data","IT":"Test system, test data","IP":"Test system, playback data","IS":"Test system, simulated data"}},"goes:image_type":{"label":"Area","mapping":{"FULL DISK":"The Americas (full disk)","CONUS":"North America (continental US)","MESOSCALE":"Central/South America (mesoscale)"}},"goes:mesoscale_image_number":{"label":"Area in Central/South America","mapping":{"1":"Region 1","2":"Region 2"}},"goes:mode":{"label":"Capture Mode","mapping":{"3":"3: 1x full disk, 3x continental US, 30x mesoscale region 1, 30x mesoscale region 2 (every 15 minutes)","4":"4: 1x full disk (every 5 minutes)","6":"6: 1x full disk, 2x continental US, 20x mesoscale region 1, 20x mesoscale region 2 (every 10 minutes)"}},"goes:group_time_threshold":{"label":"Time Threshold in a Group","explain":"Lightning group maximum time difference among lightning events in a group","unit":"s"},"goes:flash_time_threshold":{"label":"Time Threshold in a Flash","explain":"Lightning flash maximum time difference among lightning events in a flash","unit":"s"},"goes:lightning_wavelength":{"label":"Central Wavelength","unit":"nm"},"goes:yaw_flip_flag":{"label":"Yaw Flip Configuration","explain":"Flag indicating that the spacecraft is operating in yaw flip configuration.","mapping":{"0":"Upright","1":"Neither","2":"Inverted"}},"goes:event_count":{"label":"Lightning Events"},"goes:group_count":{"label":"Lightning Groups"},"goes:flash_count":{"label":"Lightning Flashes"},"goes:nominal_satellite_subpoint_lat":{"label":"Satellite Subpoint Latitude","unit":"°N"},"goes:nominal_satellite_subpoint_lon":{"label":"Satellite Subpoint Longitude","unit":"°E"},"goes:nominal_satellite_height":{"label":"Satellite Height","explain":"Nominal satellite height above GRS 80 ellipsoid","unit":"km"},"goes:percent_navigated_L1b_events":{"label":"Events navigated by Instrument","format":"Percent0to1","unit":"%"},"goes:percent_uncorrectable_L0_errors":{"label":"Data Lost","format":"Percent0to1","unit":"%"},"grid:code":{"label":"Grid","format":"GridCode"},"raster:bands":{"label":"Layers","items":{"nodata":{"alias":"nodata","label":"No-Data Values","format":"CSV","summary":false},"sampling":{"alias":"raster:sampling","sortable":true,"label":"Sampling","mapping":{"area":"Area","point":"Point (at pixel center)"}},"data_type":{"alias":"data_type","sortable":true,"label":"Data Type of Values","format":"FileDataType"},"bits_per_sample":{"alias":"raster:bits_per_sample","sortable":true,"label":"Bits per Sample"},"spatial_resolution":{"alias":"raster:spatial_resolution","sortable":true,"label":"Resolution","explain":"Average spatial resolution","unit":"m"},"statistics":{"alias":"statistics","label":"Statistics","listWithKeys":true,"items":{"mean":{"label":"Average"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stdev":{"label":"Std. Dev.","explain":"Standard Deviation"},"count":{"label":"Count","explain":"Total number of values"},"valid_percent":{"label":"Valid","explain":"Percentage of valid values","unit":"%"}},"itemOrder":["mean","count","maximum","minimum","stdev","valid_percent"]},"unit":{"alias":"unit","sortable":true,"label":"Unit of Values"},"scale":{"alias":"raster:scale","sortable":true,"label":"Scale"},"offset":{"alias":"raster:offset","sortable":true,"label":"Offset"},"histogram":{"alias":"raster:histogram","label":"Histogram","custom":true}},"itemOrder":["bits_per_sample","data_type","histogram","nodata","offset","spatial_resolution","sampling","scale","statistics","unit"]},"raster:sampling":{"label":"Sampling","mapping":{"area":"Area","point":"Point (at pixel center)"}},"raster:bits_per_sample":{"label":"Bits per Sample"},"raster:spatial_resolution":{"label":"Resolution","explain":"Average spatial resolution","unit":"m"},"raster:scale":{"label":"Scale"},"raster:offset":{"label":"Offset"},"raster:histogram":{"label":"Histogram","custom":true},"label:properties":{"label":"Properties","null":"raster data"},"label:classes":{"label":"Classes","items":{"name":{"label":"Name","null":"raster-formatted","sortable":true,"id":true},"classes":{"label":"Classes"}},"itemOrder":["name","classes"]},"label:description":{"label":"Description","format":"CommonMark","summary":false},"label:type":{"label":"Type"},"label:tasks":{"label":"Tasks"},"label:methods":{"label":"Methods"},"label:overviews":{"label":"Overviews","summary":false,"items":{"property_key":{"label":"Property Key","id":true},"counts":{"label":"Counts","custom":true},"statistics":{"label":"Statistics","custom":true}},"itemOrder":["property_key","counts","statistics"]},"mgrs:latitude_band":{"label":"Latitude Band"},"mgrs:grid_square":{"label":"Grid Square"},"mgrs:utm_zone":{"label":"UTM Zone"},"noaa_mrms_qpe:pass":{"label":"Pass Number","mapping":{"1":"1 (less latency / less gauges)","2":"2 (more latency / more gauges)"}},"noaa_mrms_qpe:period":{"label":"Accumulation Period","unit":"h"},"noaa_mrms_qpe:region":{"label":"Region","mapping":{"CONUS":"Continental US","HAWAII":"Hawaii","GUAM":"Guam","ALASKA":"Alaska","CARIB":"Caribbean Islands"}},"openeo:status":{"label":"Processing Status"},"api_version":{"label":"API Version","ext":"openeo"},"backend_version":{"label":"Back-End Version","ext":"openeo"},"production":{"label":"Production-Ready","ext":"openeo"},"endpoints":{"label":"Supported Endpoints","ext":"openeo","summary":false,"items":{"path":{"label":"Path Template","order":0},"methods":{"label":"HTTP Methods","order":1,"format":"CSV"}},"itemOrder":["path","methods"]},"billing":{"label":"Billing","ext":"openeo","custom":true,"summary":false},"order:status":{"label":"Status","mapping":{"orderable":"Orderable (data can be ordered)","ordered":"Ordered (preparing to deliver data)","pending":"Pending (waiting for activation)","shipping":"Shipping (data is getting processed)","succeeded":"Delivered (data is available)","failed":"Failed (unable to deliver)","canceled":"Canceled (delivery stopped)"}},"order:id":{"label":"Identifier"},"order:date":{"label":"Submitted","format":"Timestamp","summary":"r"},"order:expiration_date":{"alias":"expires","label":"Expires","format":"Timestamp","summary":"r"},"pc:count":{"label":"Points","explain":"Number of Points"},"pc:type":{"label":"Type"},"pc:encoding":{"label":"Format"},"pc:schemas":{"label":"Schemas","summary":false,"items":{"name":{"label":"Name","sortable":true,"id":true},"size":{"label":"Size","unit":"bytes","sortable":true},"type":{"label":"Type","sortable":true}},"itemOrder":["name","size","type"]},"pc:density":{"label":"Density"},"pc:statistics":{"label":"Statistics","summary":"s","items":{"name":{"label":"Name","id":true},"position":{"label":"Position"},"average":{"label":"Average"},"count":{"label":"Count"},"maximum":{"label":"Max.","explain":"Maxmimum value"},"minimum":{"label":"Min.","explain":"Minimum value"},"stddev":{"label":"Std. Dev.","explain":"Standard Deviation"},"variance":{"label":"Variance"}},"itemOrder":["name","average","count","maximum","minimum","position","stddev","variance"]},"processing:expression":{"label":"Processing Instructions","summary":false},"processing:lineage":{"label":"Lineage","format":"CommonMark","summary":false},"processing:level":{"label":"Level"},"processing:facility":{"label":"Facility"},"processing:software":{"label":"Software","format":"Software","summary":false},"processing:version":{"label":"Processor Version"},"processing:datetime":{"label":"Processing Time","format":"Timestamp","summary":"r"},"product:type":{"label":"Product Type"},"product:timeliness":{"label":"Timeliness","format":"Duration"},"product:timeliness_category":{"label":"Timeliness Category"},"product:acquisition_type":{"label":"Acquisition Type","mapping":{"nominal":"Nominal","calibration":"Calibration","other":"Other"}},"proj:epsg":{"label":"EPSG Code","format":"EPSG","summary":"v"},"proj:code":{"label":"Code","format":"CrsCode","summary":"v"},"proj:wkt2":{"label":"WKT2","explain":"Well-Known Text, version 2","format":"WKT2","summary":false},"proj:projjson":{"label":"PROJJSON","explain":"JSON encoding of WKT2","format":"PROJJSON","summary":false},"proj:geometry":{"label":"Footprint","custom":true,"summary":false},"proj:bbox":{"label":"Bounding Box","custom":true,"summary":false},"proj:centroid":{"label":"Centroid","custom":true,"summary":false},"proj:shape":{"label":"Image Dimensions","format":"Shape","summary":false},"proj:transform":{"label":"Transformation Matrix","format":"Transform","summary":false},"sar:instrument_mode":{"label":"Instrument Mode"},"sar:frequency_band":{"label":"Frequency Band"},"sar:center_frequency":{"label":"Center Frequency","unit":"GHz"},"sar:polarizations":{"label":"Polarizations","format":"CSV"},"sar:product_type":{"label":"Product Type"},"sar:resolution_range":{"label":"Range Resolution","unit":"m"},"sar:resolution_azimuth":{"label":"Azimuth Resolution","unit":"m"},"sar:pixel_spacing_range":{"label":"Range Pixel Spacing","unit":"m"},"sar:pixel_spacing_azimuth":{"label":"Azimuth Pixel Spacing","unit":"m"},"sar:looks_range":{"label":"Range Looks"},"sar:looks_azimuth":{"label":"Azimuth Looks"},"sar:looks_equivalent_number":{"label":"ENL","explain":"Equivalent Number of Looks"},"sar:observation_direction":{"label":"Observation Direction"},"sat:platform_international_designator":{"label":"Int. Designator","explain":"International designator for the platform, also known as COSPAR ID and NSSDCA ID."},"sat:orbit_state":{"label":"Orbit State"},"sat:absolute_orbit":{"label":"Abs. Orbit Number","explain":"Absolute Orbit Number"},"sat:relative_orbit":{"label":"Rel. Orbit Number","explain":"Relative Orbit Number"},"sat:anx_datetime":{"label":"ANX Time","format":"Timestamp","explain":"Ascending Node Crossing time","summary":"r"},"sci:doi":{"label":"DOI","format":"DOI"},"sci:citation":{"label":"Citation"},"sci:publications":{"label":"Publications","summary":false,"items":{"citation":{"label":"Publication","sortable":true,"order":0},"doi":{"label":"DOI","format":"DOI","sortable":true,"order":1}},"itemOrder":["citation","doi"]},"ssys:targets":{"label":"Target Body"},"storage:platform":{"label":"Provider","mapping":{"ALIBABA":"Alibaba Cloud","AWS":"Amazon AWS","AZURE":"Microsoft Azure","GCP":"Google Cloud Platform","IBM":"IBM Cloud","ORACLE":"Oracle Cloud"}},"storage:region":{"label":"Region"},"storage:requester_pays":{"label":"Requester Pays"},"storage:tier":{"label":"Tier Type"},"table:columns":{"label":"Columns","items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"type":{"label":"Data Type","sortable":true,"order":1},"description":{"label":"Description","format":"CommonMark","order":2}},"itemOrder":["name","type","description"]},"table:primary_geometry":{"label":"Primary Geometry Column"},"table:row_count":{"label":"Rows"},"table:tables":{"label":"Tables","summary":false,"listWithKeys":true,"items":{"name":{"label":"Name","sortable":true,"id":true,"order":0},"description":{"label":"Description","format":"CommonMark","order":1}},"itemOrder":["name","description"]},"tiles:tile_matrix_sets":{"label":"Tile Matrix Sets","custom":true,"summary":false},"tiles:tile_matrix_set_links":{"label":"Tile Matrix Set Links","custom":true,"summary":false},"view:off_nadir":{"label":"Off-Nadir Angle","unit":"°"},"view:incidence_angle":{"label":"Incidence Angle","unit":"°"},"view:azimuth":{"label":"Viewing Azimuth","unit":"°"},"view:sun_azimuth":{"label":"Sun Azimuth","unit":"°"},"view:sun_elevation":{"label":"Sun Elevation","unit":"°"},"pl:black_fill":{"label":"Unfilled Image Parts","unit":"%"},"pl:clear_percent":{"label":"Clear Sky","unit":"%"},"pl:grid_cell":{"label":"Grid Cell"},"pl:ground_control":{"label":"Positional Accuracy"},"pl:ground_control_ratio":{"label":"Successful Rectification Ratio"},"pl:item_type":{"label":"Type"},"pl:pixel_resolution":{"label":"Spatial Resolution","unit":"m"},"pl:publishing_stage":{"label":"Publishing Stage","mapping":{"preview":"Preview","standard":"Standard","finalized":"Finalized"}},"pl:quality_category":{"label":"Quality Category","mapping":{"standard":"Standard","test":"Test"}},"pl:strip_id":{"label":"Image Strip ID"},"gee:type":{"label":"Type","mapping":{"image":"Single image","image_collection":"Image collection","table":"Table"}},"gee:cadence":{"label":"Cadence"},"gee:schema":{"label":"Variables","items":{"name":{"label":"Name"},"description":{"label":"Description"},"type":{"label":"Data Type"}},"summary":false,"itemOrder":["type","description","name"]},"gee:revisit_interval":{"label":"Revisit Interval"},"gee:terms_of_use":{"label":"Terms of Use","format":"CommonMark","summary":false},"gee:visualizations":{"label":"Visualizations","custom":true,"summary":false},"landsat:scene_id":{"label":"Scene ID"},"landsat:collection_category":{"label":"Collection Category"},"landsat:collection_number":{"label":"Collection Number"},"landsat:wrs_type":{"label":"WRS Type","explain":"Worldwide Reference System Type"},"landsat:wrs_path":{"label":"WRS Path","explain":"Worldwide Reference System Path"},"landsat:wrs_row":{"label":"WRS Row","explain":"Worldwide Reference System Row"},"landsat:cloud_cover_land":{"label":"Land Cloud Cover","unit":"%"},"msft:container":{"label":"Container"},"msft:storage_account":{"label":"Storage Account"},"msft:short_description":{"label":"Summary","summary":false},"sentinel:utm_zone":{"label":"UTM Zone"},"sentinel:latitude_band":{"label":"Latitude Band"},"sentinel:grid_square":{"label":"Grid Square"},"sentinel:sequence":{"label":"Sequence"},"sentinel:product_id":{"label":"Product ID","summary":"s"},"sentinel:data_coverage":{"label":"Data Coverage","unit":"%"},"sentinel:valid_cloud_cover":{"label":"Valid Cloud Cover"},"cbers:data_type":{"label":"Processing Level","explain":"Geolocation precision level","mapping":{"L2":"Geolocation using only satellite telemetry","L3":"Control points used to geolocate image, no terrain correction","L4":"Control points used to geolocate image, orthorectified"},"summary":"v"},"cbers:path":{"label":"Reference Grid Path"},"cbers:row":{"label":"Reference Grid Row"},"card4l:specification":{"label":"Specification","mapping":{"SR":"Surface Reflectance (Optical)","ST":"Surface Temperature (Optical)","NRB":"Normalized Radar Backscatter (SAR)","POL":"Polarimetric Radar (SAR)"}},"card4l:specification_version":{"label":"Specification Version"},"card4l:orbit_mean_altitude":{"label":"Platform Altitude","unit":"m"},"card4l:incidence_angle_near_range":{"label":"Incidence Angle (near)","unit":"°"},"card4l:incidence_angle_far_range":{"label":"Incidence Angle (far)","unit":"°"},"card4l:noise_equivalent_intensity":{"label":"Noise Equivalent Intensity","unit":"dB"},"card4l:mean_faraday_rotation_angle":{"label":"Mean Faraday Rotation","unit":"°"},"card4l:speckle_filtering":{"label":"Speckle Filtering","custom":true,"summary":false,"null":"not applied"},"card4l:relative_rtc_accuracy":{"label":"Rel. RTC Accuracy","explain":"Relative accuracy of the Radiometric Terrain Correction","unit":"dB"},"card4l:absolute_rtc_accuracy":{"label":"Abs. RTC Accuracy","explain":"Absolute accuracy of the Radiometric Terrain Correction","unit":"dB"},"card4l:northern_geometric_accuracy":{"label":"Northern Geometric Accuracy","unit":"m"},"card4l:eastern_geometric_accuracy":{"label":"Eastern Geometric Accuracy","unit":"m"},"card4l:ellipsoidal_height":{"label":"Ellipsoidal Height","unit":"m"},"geoadmin:variant":{"label":"Product Variant","mapping":{"krel":"RGB color with relief","komb":"RGB color without relief","kgrel":"Grayscale with relief","kgrs":"Grayscale without relief"}},"href:servers":{"label":"Servers","ext":"web-map-links"},"pmtiles:layers":{"label":"Layers","ext":"web-map-links"},"wms:layers":{"label":"Layers","ext":"web-map-links"},"wms:styles":{"label":"Styles","ext":"web-map-links"},"wms:dimensions":{"label":"Dimensions","ext":"web-map-links"},"wms:transparent":{"label":"Transparency","ext":"web-map-links"},"wmts:layer":{"label":"Layers","ext":"web-map-links"},"wmts:dimensions":{"label":"Dimensions","ext":"web-map-links"}}} diff --git a/src/pystac/summaries.py b/src/pystac/summaries.py index 8cdf14848..ec0b1a11e 100644 --- a/src/pystac/summaries.py +++ b/src/pystac/summaries.py @@ -1,68 +1,25 @@ from __future__ import annotations -from abc import abstractmethod -from collections.abc import Iterable +import copy +import datetime as dt +from collections.abc import Iterable, Iterator, MutableMapping from enum import Enum from functools import cache -from typing import ( - TYPE_CHECKING, - Any, - Generic, - Protocol, - TypeVar, -) +from typing import TYPE_CHECKING, Any, TypedDict, cast, override -import pystac -from pystac.utils import get_required +from .reader import DEFAULT_READER if TYPE_CHECKING: - from pystac.collection import Collection - from pystac.item import Item + from .collection import Collection + from .item import Item -def __getattr__(name: str) -> Any: - if name == "FIELDS_JSON_URL": - import warnings - - warnings.warn( - "FIELDS_JSON_URL is deprecated and will be removed in v2", - DeprecationWarning, - ) - return ( - "https://cdn.jsdelivr.net/npm/@radiantearth/" - "stac-fields/fields-normalized.json" - ) - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - - -class _Comparable_x(Protocol): - """Protocol for annotating comparable types. - - For matching __lt__ that takes an 'x' parameter - (e.g. float) - """ - - @abstractmethod - def __lt__(self: T, x: T) -> bool: - return NotImplemented - - -class _Comparable_other(Protocol): - """Protocol for annotating comparable types. - - For matching __lt___ that takes an 'other' parameter - (e.g. datetime) - """ - - @abstractmethod - def __lt__(self: T, other: T) -> bool: - return NotImplemented - - -T = TypeVar("T", bound=_Comparable_x | _Comparable_other) +class RangeSummaryDict[T: (int, float, dt.datetime)](TypedDict): + minimum: T + maximum: T -class RangeSummary(Generic[T]): +class RangeSummary[T: (int, float, dt.datetime)]: minimum: T maximum: T @@ -70,7 +27,7 @@ def __init__(self, minimum: T, maximum: T): self.minimum = minimum self.maximum = maximum - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> RangeSummaryDict[T]: return {"minimum": self.minimum, "maximum": self.maximum} def update_with_value(self, v: T) -> None: @@ -78,17 +35,19 @@ def update_with_value(self, v: T) -> None: self.maximum = max(self.maximum, v) @classmethod - def from_dict(cls, d: dict[str, Any]) -> RangeSummary[T]: - minimum: T = get_required(d.get("minimum"), "RangeSummary", "minimum") - maximum: T = get_required(d.get("maximum"), "RangeSummary", "maximum") + def from_dict(cls, d: RangeSummaryDict[T]) -> RangeSummary[T]: + minimum: T = d["minimum"] + maximum: T = d["maximum"] return cls(minimum=minimum, maximum=maximum) - def __eq__(self, o: object) -> bool: - if not isinstance(o, RangeSummary): + @override + def __eq__(self, other: object) -> bool: + if not isinstance(other, RangeSummary): return NotImplemented - return self.to_dict() == o.to_dict() + return self.to_dict() == other.to_dict() + @override def __repr__(self) -> str: return self.to_dict().__repr__() @@ -107,7 +66,7 @@ def _get_fields_json(url: str | None) -> dict[str, Any]: .read_text() ) return jsonfields - return pystac.StacIO.default().read_json(url) + return DEFAULT_READER.get_json(url) class SummaryStrategy(Enum): @@ -117,6 +76,20 @@ class SummaryStrategy(Enum): DONT_SUMMARIZE = False DEFAULT = True + @classmethod + def try_from( + cls, strategy: SummaryStrategy | dict[str, Any] | None + ) -> SummaryStrategy: + if isinstance(strategy, SummaryStrategy): + return strategy + + if isinstance(strategy, dict): + strategy_value = strategy.get("summary", True) + if strategy_value in cls: + return SummaryStrategy(strategy_value) + + return SummaryStrategy.DEFAULT + class Summarizer: """The Summarizer computes summaries from values, following the definition of fields @@ -145,65 +118,62 @@ class Summarizer: summaryfields: dict[str, SummaryStrategy] - def __init__(self, fields: str | dict[str, SummaryStrategy] | None = None): + def __init__( + self, + fields: str | dict[str, SummaryStrategy | dict[str, Any] | None] | None = None, + ): + json_fields: dict[str, SummaryStrategy | dict[str, Any] | None] if isinstance(fields, dict): - self._set_field_definitions(fields) + json_fields = fields else: - jsonfields = _get_fields_json(fields) - self._set_field_definitions(jsonfields["metadata"]) + json_fields = _get_fields_json(fields)["metadata"] - def _set_field_definitions(self, fields: dict[str, Any]) -> None: self.summaryfields = {} - for name, desc in fields.items(): - strategy: SummaryStrategy = SummaryStrategy.DEFAULT - if isinstance(desc, SummaryStrategy): - strategy = desc - elif isinstance(desc, dict): - strategy_value = desc.get("summary", True) - try: - strategy = SummaryStrategy(strategy_value) - except ValueError: - pass - + for name, desc in json_fields.items(): + strategy = SummaryStrategy.try_from(desc) if strategy != SummaryStrategy.DONT_SUMMARIZE: self.summaryfields[name] = strategy def _update_with_item(self, summaries: Summaries, item: Item) -> None: - import numbers - for k, v in item.properties.items(): if k in self.summaryfields: strategy = self.summaryfields[k] - if strategy == SummaryStrategy.RANGE or ( - strategy == SummaryStrategy.DEFAULT - and isinstance(v, numbers.Number) - and not isinstance(v, bool) - ): - rangesummary: RangeSummary[Any] | None = summaries.get_range(k) - if rangesummary is None: - summaries.add(k, RangeSummary(v, v)) + summary = summaries.get(k) + if strategy in [ + SummaryStrategy.RANGE, + SummaryStrategy.DEFAULT, + ] and isinstance(v, (int, float, dt.datetime)): + if isinstance(summary, RangeSummary): + summary.update_with_value(v) # pyright: ignore[reportArgumentType] else: - rangesummary.update_with_value(v) - elif strategy == SummaryStrategy.ARRAY or ( - strategy == SummaryStrategy.DEFAULT and isinstance(v, list) - ): - listsummary: list[Any] = summaries.get_list(k) or [] - if not isinstance(v, list): - v = [v] - for element in v: - if element not in listsummary: - listsummary.append(element) - summaries.add(k, listsummary) + summaries[k] = RangeSummary(minimum=v, maximum=v) # pyright: ignore[reportArgumentType] + elif strategy in [ + SummaryStrategy.ARRAY, + SummaryStrategy.DEFAULT, + ] and isinstance(v, list): + if summary is None: + summaries[k] = v + elif isinstance(summary, list): + summaries[k] = [ + *summary, + [ + item + for item in cast(list[Any], v) + if item not in summary + ], + ] else: - summary: list[Any] = summaries.get_list(k) or [] - if v not in summary: - summary.append(v) - summaries.add(k, summary) + if summary is None: + summaries[k] = [v] + elif v not in summary: + summaries[k] = [*summary, v] def summarize(self, source: Collection | Iterable[Item]) -> Summaries: """Creates summaries from items""" + from .collection import Collection + summaries = Summaries.empty() - if isinstance(source, pystac.Collection): + if isinstance(source, Collection): for item in source.get_items(recursive=True): self._update_with_item(summaries, item) else: @@ -216,119 +186,77 @@ def summarize(self, source: Collection | Iterable[Item]) -> Summaries: DEFAULT_MAXCOUNT = 25 -class Summaries: - _summaries: dict[str, Any] +def _maybe_range(value: Any) -> RangeSummary[Any] | Any: + if isinstance(value, dict) and "minimum" in value: + return RangeSummary[Any](**value) + return cast(Any, value) + - lists: dict[str, list[Any]] - other: dict[str, Any] - ranges: dict[str, RangeSummary[Any]] - schemas: dict[str, dict[str, Any]] +class Summaries(MutableMapping[str, Any]): + _store: dict[str, Any] maxcount: int def __init__( self, summaries: dict[str, Any], maxcount: int = DEFAULT_MAXCOUNT ) -> None: - self._summaries = summaries + self._store = {k: _maybe_range(v) for k, v in summaries.items()} self.maxcount = maxcount - self.lists = {} - self.ranges = {} - self.schemas = {} - self.other = {} + @classmethod + def try_from(cls, summaries: Summaries | dict[str, Any] | None) -> Summaries: + if isinstance(summaries, Summaries): + return summaries + elif isinstance(summaries, dict): + return Summaries(summaries) + else: + return Summaries({}) - for prop_key, summary in summaries.items(): - self.add(prop_key, summary) + @override + def __getitem__(self, key: str) -> Any: + return self._store[key] - def get_list(self, prop: str) -> list[Any] | None: - return self.lists.get(prop) + @override + def __setitem__(self, name: str, value: Any, /) -> None: + self._store[name] = value - def get_range(self, prop: str) -> RangeSummary[Any] | None: - return self.ranges.get(prop) + @override + def __delitem__(self, key: str) -> None: + del self._store[key] - def get_schema(self, prop: str) -> dict[str, Any] | None: - return self.schemas.get(prop) + @override + def __iter__(self) -> Iterator[str]: + return iter(self._store) - def add( - self, - prop_key: str, - summary: list[Any] | RangeSummary[Any] | dict[str, Any], - ) -> None: - if isinstance(summary, list): - self.lists[prop_key] = summary - elif isinstance(summary, dict): - if "minimum" in summary: - self.ranges[prop_key] = RangeSummary[Any].from_dict(summary) - else: - self.schemas[prop_key] = summary - elif isinstance(summary, RangeSummary): - self.ranges[prop_key] = summary - else: - self.other[prop_key] = summary - - def remove(self, prop_key: str) -> None: - self.lists.pop(prop_key, None) - self.ranges.pop(prop_key, None) - self.schemas.pop(prop_key, None) - self.other.pop(prop_key, None) - - def update(self, summaries: Summaries) -> None: - self.lists.update(summaries.lists) - self.ranges.update(summaries.ranges) - self.schemas.update(summaries.schemas) - self.other.update(summaries.other) - - def combine(self, summaries: Summaries) -> None: - for listname, listvalue in summaries.lists.items(): - if listname in self.lists: - self.lists[listname].extend(listvalue) - else: - self.lists[listname] = listvalue - for rangename, rang in summaries.ranges.items(): - if rangename in self.ranges: - self.ranges[rangename].update_with_value(rang.minimum) - self.ranges[rangename].update_with_value(rang.maximum) - else: - self.ranges[rangename] = rang - for schemaname, schema in summaries.schemas.items(): - if schemaname in self.schemas: - self.schemas[schemaname].update(schema) - else: - self.schemas[schemaname] = schema - for k, v in summaries.other.items(): - if k in self.other: - self.other[k].update(v) - else: - self.other[k] = v + @override + def __len__(self) -> int: + return len(self._store) - def is_empty(self) -> bool: - return not ( - any(self.lists) or any(self.ranges) or any(self.schemas) or any(self.other) - ) + def get_list(self, prop: str) -> list[Any] | None: + value = self._store.get(prop) + if value is not None: + assert isinstance(value, list) + return cast(list[Any], value) - def clone(self) -> Summaries: - """Clones this object. - - Returns: - Summaries: The clone of this object - """ - from copy import deepcopy - - cls = self.__class__ - summaries = cls(summaries=deepcopy(self._summaries), maxcount=self.maxcount) - summaries.lists = deepcopy(self.lists) - summaries.other = deepcopy(self.other) - summaries.ranges = deepcopy(self.ranges) - summaries.schemas = deepcopy(self.schemas) - return summaries + def get_range(self, prop: str) -> RangeSummary[Any] | None: + value = self._store.get(prop) + if value is not None: + assert isinstance(value, RangeSummary) + return value def to_dict(self) -> dict[str, Any]: - return { - **{k: v for k, v in self.lists.items() if len(v) < self.maxcount}, - **{k: v.to_dict() for k, v in self.ranges.items()}, - **self.schemas, - **self.other, - } + out: dict[str, Any] = {} + for k, v in self._store.items(): + if isinstance(v, list) and len(cast(list[Any], v)) >= self.maxcount: + continue + out[k] = v.to_dict() if isinstance(v, RangeSummary) else v + return out + + def is_empty(self) -> bool: + return len(self) == 0 @classmethod def empty(cls) -> Summaries: return Summaries({}) + + def clone(self) -> Summaries: + return copy.deepcopy(self) diff --git a/tests/v1/test_collection.py b/tests/v1/test_collection.py index de0473a6b..901631bbd 100644 --- a/tests/v1/test_collection.py +++ b/tests/v1/test_collection.py @@ -279,7 +279,6 @@ def test_get_assets() -> None: assert no_assets == {} -@pytest.mark.xfail(reason="Pystac v2 doesn't support summaries yet") def test_removing_optional_attributes() -> None: path = TestCases.get_path("data-files/collections/with-assets.json") with open(path) as file: @@ -295,7 +294,7 @@ def test_removing_optional_attributes() -> None: assert collection.stac_extensions assert collection.keywords assert collection.providers - assert collection.summaries + assert collection.summaries is not None assert collection.assets # Remove all of the optional stuff @@ -350,7 +349,6 @@ def test_from_dict_set_root() -> None: assert collection.get_root() is catalog -@pytest.mark.xfail(reason="Pystac v2 doesn't support summaries yet") def test_schema_summary() -> None: collection = pystac.Collection.from_file( TestCases.get_path( @@ -358,7 +356,7 @@ def test_schema_summary() -> None: ) ) instruments_schema = get_required( - collection.summaries.get_schema("instruments"), + collection.summaries["instruments"], collection.summaries, "instruments", ) diff --git a/tests/v1/test_summaries.py b/tests/v1/test_summaries.py index fe15afadc..8f57664a3 100644 --- a/tests/v1/test_summaries.py +++ b/tests/v1/test_summaries.py @@ -8,6 +8,9 @@ from .utils import TestCases +pytestmark = pytest.mark.passing_v2 + + def test_summary() -> None: coll = TestCases.case_5() summaries = Summarizer().summarize(coll.get_items(recursive=True)) From 8add2aebd9b4985f1bc5da7df63672805f374f49 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 5 May 2026 11:25:08 -0400 Subject: [PATCH 60/60] Add .ext to stac objects (#1717) --- src/pystac/asset.py | 13 +++++++++++++ src/pystac/catalog.py | 17 ++++++++++++++++- src/pystac/collection.py | 13 +++++++++++++ src/pystac/item.py | 13 +++++++++++++ src/pystac/link.py | 13 +++++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/pystac/asset.py b/src/pystac/asset.py index fd4557422..cf7751d55 100644 --- a/src/pystac/asset.py +++ b/src/pystac/asset.py @@ -18,6 +18,7 @@ from .writer import Writer if TYPE_CHECKING: + from .extensions.ext import AssetExt from .stac_object import STACObject @@ -132,6 +133,18 @@ def to_dict(self) -> dict[str, Any]: data = super().to_dict() return {"href": self.href, **data} + @property + def ext(self) -> AssetExt: + """Accessor for extension classes on this asset + + Example:: + + asset.ext.proj.code = "EPSG:4326" + """ + from pystac.extensions.ext import AssetExt + + return AssetExt(stac_object=self) + class Assets(Protocol): assets: dict[str, Asset] diff --git a/src/pystac/catalog.py b/src/pystac/catalog.py index 2192c01ac..8286e17fc 100644 --- a/src/pystac/catalog.py +++ b/src/pystac/catalog.py @@ -3,7 +3,7 @@ import copy import warnings from enum import StrEnum -from typing import Any, ClassVar, override +from typing import TYPE_CHECKING, Any, ClassVar, override from typing_extensions import deprecated @@ -13,6 +13,9 @@ from .link import Link from .rel_type import RelType +if TYPE_CHECKING: + from .extensions.ext import CatalogExt + class Catalog(Container): type: ClassVar[STAC_OBJECT_TYPE] = "Catalog" @@ -83,6 +86,18 @@ def to_dict( data["description"] = self.description return data + @property + def ext(self) -> CatalogExt: + """Accessor for extension classes on this catalog + + Example:: + + print(collection.ext.version) + """ + from pystac.extensions.ext import CatalogExt + + return CatalogExt(stac_object=self) + @deprecated("CatalogType is deprecated") class CatalogType(StrEnum): diff --git a/src/pystac/collection.py b/src/pystac/collection.py index 0b98956ab..5016769a5 100644 --- a/src/pystac/collection.py +++ b/src/pystac/collection.py @@ -26,6 +26,7 @@ from .utils import to_datetime_str if TYPE_CHECKING: + from .extensions.ext import CollectionExt from .item import Item from .item_collection import ItemCollection @@ -218,6 +219,18 @@ def update_extent_from_items(self) -> None: """ self.extent = Extent.from_items(self.get_items(recursive=True)) + @property + def ext(self) -> CollectionExt: + """Accessor for extension classes on this collection + + Example:: + + print(collection.ext.xarray) + """ + from pystac.extensions.ext import CollectionExt + + return CollectionExt(stac_object=self) + class Extent: def __init__( diff --git a/src/pystac/item.py b/src/pystac/item.py index 7a67e02a0..bf46f678d 100644 --- a/src/pystac/item.py +++ b/src/pystac/item.py @@ -29,6 +29,7 @@ if TYPE_CHECKING: from .collection import Collection + from .extensions.ext import ItemExt class Item(STACObject, Assets): @@ -241,6 +242,18 @@ def to_dict( def __geo_interface__(self) -> dict[str, Any]: return self.to_dict(include_self_link=False) + @property + def ext(self) -> ItemExt: + """Accessor for extension classes on this item + + Example:: + + item.ext.proj.code = "EPSG:4326" + """ + from pystac.extensions.ext import ItemExt + + return ItemExt(stac_object=self) + @final class Properties( diff --git a/src/pystac/link.py b/src/pystac/link.py index f4ebdac96..19e21841a 100644 --- a/src/pystac/link.py +++ b/src/pystac/link.py @@ -16,6 +16,7 @@ if TYPE_CHECKING: from . import Catalog, Collection, Item + from .extensions.ext import LinkExt from .stac_object import STACObject HIERARCHICAL_LINKS = [ @@ -318,3 +319,15 @@ def canonical( title=title, media_type=MediaType.JSON, ) + + @property + def ext(self) -> LinkExt: + """Accessor for extension classes on this link + + Example:: + + link.ext.file.size = 8675309 + """ + from pystac.extensions.ext import LinkExt + + return LinkExt(stac_object=self)