diff --git a/README.md b/README.md index 57158a4..6281aa2 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ Options: -m, --max-depth INTEGER Maximum depth to traverse when recursing. Omit this argument to get full recursion. Ignored if `recursive == False`. + -i, --item_collection Lint and validate a stac api item collection + response. -r, --recursive Recursively validate all related stac objects. --help Show this message and exit. Show this message and exit. ``` @@ -38,6 +40,12 @@ $ make build $ make shell ``` --- +### Item Collection Example + +``` +$ stac-check https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a/items --item_collection +``` +--- ### Lint JSON ``` @@ -49,9 +57,26 @@ for k, v in linter.create_best_practices_dict().items(): print(k, ":", v) ``` --- -### CLI Examples +### Lint in-memory dictionary + +``` +from stac_check.lint import Linter + +stac_item = { + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", +} + +linter = Linter(stac_item, assets=True) + +for k, v in linter.create_best_practices_dict().items(): + print(k, ":", v) +``` +--- +### CLI Example Output -``` stac-check https://raw.githubusercontent.com/stac-utils/pystac/main/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json --recursive ``` +``` $ stac-check https://raw.githubusercontent.com/stac-utils/pystac/main/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json --recursive ``` ``` ____ ____ __ ___ ___ _ _ ____ ___ __ _ / ___)(_ _)/ _\ / __)___ / __)/ )( \( __)/ __)( / ) @@ -96,7 +121,7 @@ Error Message: Expecting value: line 1 column 1 (char 0) ------------------------- ``` -``` stac-check sample_files/0.9.0/landsat8-sample.json``` +``` $ stac-check sample_files/0.9.0/landsat8-sample.json```
stac-check: STAC spec validaton and linting tool @@ -159,7 +184,7 @@ This object has 4 links -``` stac-check sample_files/1.0.0/core-item-bad-links.json --links --assets``` +``` $ stac-check sample_files/1.0.0/core-item-bad-links.json --links --assets```stac-check: STAC spec validaton and linting tool @@ -201,7 +226,7 @@ LINK request errors: This object has 4 links-``` stac-check sample_files/0.9.0/bad-item.json``` +``` $ stac-check sample_files/0.9.0/bad-item.json```stac-check: STAC spec validaton and linting tool diff --git a/examples/lint_dict.py b/examples/lint_dict.py new file mode 100644 index 0000000..d28ca00 --- /dev/null +++ b/examples/lint_dict.py @@ -0,0 +1,137 @@ +from stac_check.lint import Linter + +file = { + "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": None, + "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" + } + } +} +linter = Linter(file, assets=True) + +print(f"valid: {linter.valid_stac}") +print(f"error_type: {linter.error_type}") +print(f"error_msg: {linter.error_msg}") +print(f"schema: {linter.schema}") +print(f"file_name: {linter.file_name}") +print("------") +for k,v in linter.create_best_practices_dict().items(): + print(k,":",v) diff --git a/sample_files/1.0.0/item_collection.json b/sample_files/1.0.0/item_collection.json new file mode 100644 index 0000000..9d6d9dd --- /dev/null +++ b/sample_files/1.0.0/item_collection.json @@ -0,0 +1,234 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2_A", + "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" + } + } + }, + { + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2_B", + "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" + } + } + } + ], + "links": [ + { + "rel": "next", + "href": "https://stac-api.example.com/search?page=3", + "type": "application/geo+json" + + }, + { + "rel": "prev", + "href": "https://stac-api.example.com/search?page=1", + "type": "application/geo+json" + } + ] +} diff --git a/stac_check/cli.py b/stac_check/cli.py index dee6d90..66168ac 100644 --- a/stac_check/cli.py +++ b/stac_check/cli.py @@ -20,6 +20,27 @@ def link_asset_message(link_list:list, type: str, format: str) -> None: else: click.secho(f"No {type.upper()} {format} errors!", fg="green") +def api_collection(linter): + counter = 0 + data = linter.data + if data["type"] == "FeatureCollection": + for item in data["features"]: + lint = Linter(item=item) + cli_message(lint) + if lint.version == "1.0.0": + click.secho(lint.set_update_message(), fg='green') + else: + click.secho(lint.set_update_message(), fg='red') + click.secho() + counter = counter + 1 + click.secho("----------------------------------") + click.secho(f"item collection: {counter} items analyzed!", fg="blue", bold=True) + click.secho("----------------------------------") + else: + click.secho("-------------------------") + click.secho("The response is not a proper item collection.", fg="red") + click.secho("-------------------------") + def recursive_message(linter: Linter) -> None: """Displays messages related to the recursive validation of assets in a collection or catalog. @@ -48,7 +69,7 @@ def recursive_message(linter: Linter) -> None: click.secho(f"Error Message: {msg['error_message']}", fg='red') click.secho("-------------------------") -def intro_message(linter: Linter) -> None: +def intro_message(linter: Linter, skip_version): """Prints an introduction message for the stac-check tool. The message includes the stac-check logo, the name of the tool, the version @@ -74,10 +95,11 @@ def intro_message(linter: Linter) -> None: click.secho() - if linter.version == "1.0.0": - click.secho(linter.set_update_message(), fg='green') - else: - click.secho(linter.set_update_message(), fg='red') + if not skip_version: + if linter.version == "1.0.0": + click.secho(linter.set_update_message(), fg='green') + else: + click.secho(linter.set_update_message(), fg='red') click.secho() @@ -157,7 +179,13 @@ def cli_message(linter: Linter) -> None: "--recursive", "-r", is_flag=True, - help="Recursively validate all related stac objects.", + help="Recursively lint and validate all related stac objects.", +) +@click.option( + "--item_collection", + "-i", + is_flag=True, + help="Lint and validate a stac api item collection response.", ) @click.option( "--max-depth", @@ -174,10 +202,19 @@ def cli_message(linter: Linter) -> None: @click.command() @click.argument('file') @click.version_option(version=pkg_resources.require("stac-check")[0].version) -def main(file, recursive, max_depth, assets, links): - linter = Linter(file, assets=assets, links=links, recursive=recursive, max_depth=max_depth) - intro_message(linter) +def main(file, recursive, item_collection, max_depth, assets, links): + linter = Linter( + item=file, + assets=assets, + links=links, + recursive=recursive, + item_collection=item_collection, + max_depth=max_depth + ) + intro_message(linter, skip_version=item_collection) if recursive > 0: recursive_message(linter) + elif item_collection > 0: + api_collection(linter) else: cli_message(linter) \ No newline at end of file diff --git a/stac_check/lint.py b/stac_check/lint.py index 30f7c4d..26bcd66 100644 --- a/stac_check/lint.py +++ b/stac_check/lint.py @@ -122,6 +122,7 @@ def check_summaries(self) -> bool: assets: bool = False links: bool = False recursive: bool = False + item_collection: bool = False max_depth: Optional[int] = None def __post_init__(self):