From bf76aade2269de1a91b242229e2353c35eeb91aa Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Thu, 3 Apr 2025 16:21:22 +0100 Subject: [PATCH 1/7] add cli with 5 commands: product, products, order, orders, opportunities --- README.md | 2 +- pyproject.toml | 4 +- pystapi-client/pyproject.toml | 1 + pystapi-client/src/pystapi_client/cli.py | 145 ++++++++++++++++++ pystapi-client/src/pystapi_client/client.py | 2 +- .../src/pystapi_client/stapi_api_io.py | 2 +- .../pystapi_client/{warnings.py => warns.py} | 0 uv.lock | 15 ++ 8 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 pystapi-client/src/pystapi_client/cli.py rename pystapi-client/src/pystapi_client/{warnings.py => warns.py} (100%) diff --git a/README.md b/README.md index 860a052..dd8effd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ uv run pytest Check formatting and other lints: ```shell -uv run pre-commit --all-files +uv run pre-commit run --all ``` If you don't want to type `uv run` all the time: diff --git a/pyproject.toml b/pyproject.toml index da562b8..eeb6565 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,8 @@ dependencies = [ "pystapi-client", "pystapi-validator", "stapi-pydantic", - "stapi-fastapi" + "stapi-fastapi", + "types-click>=7.1.8", ] [dependency-groups] @@ -21,6 +22,7 @@ dev = [ "pre-commit>=4.2.0", "pre-commit-hooks>=5.0.0", "fastapi[standard]>=0.115.12", + "types-click>=7.1.8", ] docs = [ "mkdocs-material>=9.6.11", diff --git a/pystapi-client/pyproject.toml b/pystapi-client/pyproject.toml index 75ded78..688a282 100644 --- a/pystapi-client/pyproject.toml +++ b/pystapi-client/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "httpx>=0.28.1", "stapi-pydantic", "python-dateutil>=2.8.2", + "click>=8.1.8", ] [project.scripts] diff --git a/pystapi-client/src/pystapi_client/cli.py b/pystapi-client/src/pystapi_client/cli.py new file mode 100644 index 0000000..2904bb2 --- /dev/null +++ b/pystapi-client/src/pystapi_client/cli.py @@ -0,0 +1,145 @@ +import itertools +import json + +import click + +from pystapi_client.client import Client +from pystapi_client.exceptions import APIError + +CONTEXT_SETTINGS = dict(default_map={"cli": {"url": "http://localhost:8000"}}) + + +@click.group(context_settings=CONTEXT_SETTINGS) +@click.option("--url", type=str, required=True, help="Base URL for STAPI server") +@click.pass_context +def cli(ctx: click.Context, url: str) -> None: + """Command line interface for STAPI client. Group ensures client is created.""" + + client = Client.open(url) + ctx.obj = {"client": client} + + +@click.command() +@click.pass_context +@click.option("--max-items", "max_items", type=click.IntRange(min=1), help="Max number of products to display") +@click.option("--limit", type=click.IntRange(min=1), help="Limit number of products to request") +def products(ctx: click.Context, limit: int | None, max_items: int | None) -> None: + """List products.""" + + client: Client = ctx.obj["client"] + + products_iter = client.get_products(limit=limit) + + if max_items: + products_iter = itertools.islice(products_iter, max_items) + + products_list = list(products_iter) + if len(products_list) == 0: + click.echo("No products found.") + return + + # FIXME: to get around AnyUrl not being JSON serializable, this does loads(pydantic.model_dump_json()). Should be + # fixed with a custom JSON serializer for AnyUrl. + click.echo(json.dumps([json.loads(p.model_dump_json()) for p in products_list])) + + +@click.command() +@click.pass_context +@click.option("--id", type=str, required=True, help="Product ID to retrieve") +def product(ctx: click.Context, id: str) -> None: + """Get product by ID.""" + + client: Client = ctx.obj["client"] + + product = client.get_product(product_id=id) + if not product: + click.echo("Product not found.") + return + + click.echo(product.model_dump_json()) + + +@click.command() +@click.pass_context +@click.option("--max-items", "max_items", type=click.IntRange(min=1), help="Max number of products to display") +@click.option("--limit", type=click.IntRange(min=1), help="Limit number of products to request") +def orders(ctx: click.Context, max_items: int | None, limit: int | None) -> None: + """List orders.""" + + client: Client = ctx.obj["client"] + + orders_iter = client.get_orders(limit=limit) + + if max_items: + orders_iter = itertools.islice(orders_iter, max_items) + + orders_list = list(orders_iter) + if len(orders_list) == 0: + click.echo("No orders found.") + return + + # FIXME: to get around AnyUrl not being JSON serializable, this does loads(pydantic.model_dump_json()). Should be + # fixed with a custom JSON serializer for AnyUrl. + click.echo(json.dumps([json.loads(o.model_dump_json()) for o in orders_list])) + + +@click.command() +@click.pass_context +@click.option("--id", type=str, required=True, help="Order ID to retrieve") +def order(ctx: click.Context, id: str) -> None: + """Get order by ID.""" + + client: Client = ctx.obj["client"] + + try: + order = client.get_order(order_id=id) + click.echo(order.model_dump_json()) + except APIError as e: + if e.status_code == 404: + click.echo("Order not found.") + else: + raise e + + +@click.command() +@click.pass_context +@click.option("--product-id", "product_id", type=str, required=True, help="Product ID for opportunities") +@click.option("--max-items", "max_items", type=click.IntRange(min=1), help="Max number of opportunities to display") +@click.option("--limit", type=click.IntRange(min=1), default=10, help="Max number of opportunities to display") +def opportunities(ctx: click.Context, product_id: str, limit: int, max_items: None) -> None: + """List opportunities for a product.""" + + client: Client = ctx.obj["client"] + + date_range = ("2025-01-03T15:18:11Z", "2025-04-03T15:18:11Z") + geometry = {"type": "Point", "coordinates": [-122.4194, 37.7749]} + + opportunities_iter = client.get_product_opportunities( + product_id=product_id, geometry=geometry, date_range=date_range, limit=limit + ) + + if max_items: + opportunities_iter = itertools.islice(opportunities_iter, max_items) + + opportunities_list = list(opportunities_iter) + if len(opportunities_list) == 0: + click.echo("No opportunities found.") + return + + # FIXME: to get around AnyUrl not being JSON serializable, this does loads(pydantic.model_dump_json()). Should be + # fixed with a custom JSON serializer for AnyUrl. + click.echo(json.dumps([json.loads(o.model_dump_json()) for o in opportunities_list])) + + +cli.add_command(products) +cli.add_command(product) +cli.add_command(opportunities) +cli.add_command(orders) +cli.add_command(order) + +if __name__ == "__main__": + try: + cli() + except Exception as e: + click.echo(f"Error: {e=}", err=True) + raise e diff --git a/pystapi-client/src/pystapi_client/client.py b/pystapi-client/src/pystapi_client/client.py index 4202591..e1a4c60 100644 --- a/pystapi-client/src/pystapi_client/client.py +++ b/pystapi-client/src/pystapi_client/client.py @@ -25,7 +25,7 @@ from pystapi_client.conformance import ConformanceClasses from pystapi_client.exceptions import APIError from pystapi_client.stapi_api_io import StapiIO -from pystapi_client.warnings import NoConformsTo +from pystapi_client.warns import NoConformsTo DEFAULT_LINKS = [ { diff --git a/pystapi-client/src/pystapi_client/stapi_api_io.py b/pystapi-client/src/pystapi_client/stapi_api_io.py index a0ec157..b9b31ff 100644 --- a/pystapi-client/src/pystapi_client/stapi_api_io.py +++ b/pystapi-client/src/pystapi_client/stapi_api_io.py @@ -140,7 +140,7 @@ def request( resp = self.session.send(modified) except Exception as err: logger.debug(err) - raise APIError.from_response(resp) + raise APIError(f"Error sending request: {err=}") # NOTE what about other successful status codes? if resp.status_code != 200: diff --git a/pystapi-client/src/pystapi_client/warnings.py b/pystapi-client/src/pystapi_client/warns.py similarity index 100% rename from pystapi-client/src/pystapi_client/warnings.py rename to pystapi-client/src/pystapi_client/warns.py diff --git a/uv.lock b/uv.lock index c835e7b..e5aa3ee 100644 --- a/uv.lock +++ b/uv.lock @@ -1352,6 +1352,7 @@ dependencies = [ { name = "pystapi-validator" }, { name = "stapi-fastapi" }, { name = "stapi-pydantic" }, + { name = "types-click" }, ] [package.dev-dependencies] @@ -1363,6 +1364,7 @@ dev = [ { name = "pymarkdownlnt" }, { name = "pytest" }, { name = "ruff" }, + { name = "types-click" }, ] docs = [ { name = "mkdocs-material" }, @@ -1375,6 +1377,7 @@ requires-dist = [ { name = "pystapi-validator", editable = "pystapi-validator" }, { name = "stapi-fastapi", editable = "stapi-fastapi" }, { name = "stapi-pydantic", editable = "stapi-pydantic" }, + { name = "types-click", specifier = ">=7.1.8" }, ] [package.metadata.requires-dev] @@ -1387,6 +1390,7 @@ dev = [ { name = "pytest", specifier = ">=8.1.1" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "ruff", specifier = ">=0.11.2" }, + { name = "types-click", specifier = ">=7.1.8" }, ] docs = [ { name = "mkdocs-material", specifier = ">=9.6.11" }, @@ -1398,6 +1402,7 @@ name = "pystapi-client" version = "0.0.1" source = { editable = "pystapi-client" } dependencies = [ + { name = "click" }, { name = "httpx" }, { name = "python-dateutil" }, { name = "stapi-pydantic" }, @@ -1405,6 +1410,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "click", specifier = ">=8.1.8" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "python-dateutil", specifier = ">=2.8.2" }, { name = "stapi-pydantic", editable = "stapi-pydantic" }, @@ -2071,6 +2077,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, ] +[[package]] +name = "types-click" +version = "7.1.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/ff/0e6a56108d45c80c61cdd4743312d0304d8192482aea4cce96c554aaa90d/types-click-7.1.8.tar.gz", hash = "sha256:b6604968be6401dc516311ca50708a0a28baa7a0cb840efd7412f0dbbff4e092", size = 10015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/ad/607454a5f991c5b3e14693a7113926758f889138371058a5f72f567fa131/types_click-7.1.8-py3-none-any.whl", hash = "sha256:8cb030a669e2e927461be9827375f83c16b8178c365852c060a34e24871e7e81", size = 12929 }, +] + [[package]] name = "types-pyrfc3339" version = "2.0.1.20241107" From d44efe00b6da2861d5985aa9dd0668e6e6dde784 Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Wed, 16 Apr 2025 14:37:23 -0700 Subject: [PATCH 2/7] simpler json output, send errors to err --- pystapi-client/pyproject.toml | 2 +- .../src/pystapi_client/{ => scripts}/cli.py | 22 ++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) rename pystapi-client/src/pystapi_client/{ => scripts}/cli.py (80%) diff --git a/pystapi-client/pyproject.toml b/pystapi-client/pyproject.toml index 0c519c2..bcc1ead 100644 --- a/pystapi-client/pyproject.toml +++ b/pystapi-client/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ ] [project.scripts] -stapi-client = "pystapi_client.cli:cli" +stapi-cli = "pystapi_client.scripts.cli:cli" [tool.uv.sources] stapi-pydantic = { workspace = true } diff --git a/pystapi-client/src/pystapi_client/cli.py b/pystapi-client/src/pystapi_client/scripts/cli.py similarity index 80% rename from pystapi-client/src/pystapi_client/cli.py rename to pystapi-client/src/pystapi_client/scripts/cli.py index 2904bb2..79f5785 100644 --- a/pystapi-client/src/pystapi_client/cli.py +++ b/pystapi-client/src/pystapi_client/scripts/cli.py @@ -38,9 +38,8 @@ def products(ctx: click.Context, limit: int | None, max_items: int | None) -> No click.echo("No products found.") return - # FIXME: to get around AnyUrl not being JSON serializable, this does loads(pydantic.model_dump_json()). Should be - # fixed with a custom JSON serializer for AnyUrl. - click.echo(json.dumps([json.loads(p.model_dump_json()) for p in products_list])) + # Serialize the products list into JSON format and output it + click.echo(json.dumps([p.model_dump(mode="json") for p in products_list])) @click.command() @@ -53,7 +52,7 @@ def product(ctx: click.Context, id: str) -> None: product = client.get_product(product_id=id) if not product: - click.echo("Product not found.") + click.echo(f"Product {id} not found.", err=True) return click.echo(product.model_dump_json()) @@ -75,12 +74,11 @@ def orders(ctx: click.Context, max_items: int | None, limit: int | None) -> None orders_list = list(orders_iter) if len(orders_list) == 0: - click.echo("No orders found.") + click.echo("No orders found.", err=True) return - # FIXME: to get around AnyUrl not being JSON serializable, this does loads(pydantic.model_dump_json()). Should be - # fixed with a custom JSON serializer for AnyUrl. - click.echo(json.dumps([json.loads(o.model_dump_json()) for o in orders_list])) + # Serialize the orders list into JSON format and output it + click.echo(json.dumps([o.model_dump(mode="json") for o in orders_list])) @click.command() @@ -96,7 +94,7 @@ def order(ctx: click.Context, id: str) -> None: click.echo(order.model_dump_json()) except APIError as e: if e.status_code == 404: - click.echo("Order not found.") + click.echo(f"Order {id} not found.", err=True) else: raise e @@ -123,12 +121,10 @@ def opportunities(ctx: click.Context, product_id: str, limit: int, max_items: No opportunities_list = list(opportunities_iter) if len(opportunities_list) == 0: - click.echo("No opportunities found.") + click.echo("No opportunities found.", err=True) return - # FIXME: to get around AnyUrl not being JSON serializable, this does loads(pydantic.model_dump_json()). Should be - # fixed with a custom JSON serializer for AnyUrl. - click.echo(json.dumps([json.loads(o.model_dump_json()) for o in opportunities_list])) + click.echo(json.dumps([o.model_dump(mode="json") for o in opportunities_list])) cli.add_command(products) From a2dbc44368e4086f2dc262746ed98b028621f7eb Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Wed, 16 Apr 2025 14:42:02 -0700 Subject: [PATCH 3/7] reformat for linter --- pystapi-client/src/pystapi_client/scripts/cli.py | 1 - pystapi-client/src/pystapi_client/stapi_api_io.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pystapi-client/src/pystapi_client/scripts/cli.py b/pystapi-client/src/pystapi_client/scripts/cli.py index 79f5785..8d66154 100644 --- a/pystapi-client/src/pystapi_client/scripts/cli.py +++ b/pystapi-client/src/pystapi_client/scripts/cli.py @@ -2,7 +2,6 @@ import json import click - from pystapi_client.client import Client from pystapi_client.exceptions import APIError diff --git a/pystapi-client/src/pystapi_client/stapi_api_io.py b/pystapi-client/src/pystapi_client/stapi_api_io.py index fd2e294..0509b98 100644 --- a/pystapi-client/src/pystapi_client/stapi_api_io.py +++ b/pystapi-client/src/pystapi_client/stapi_api_io.py @@ -149,7 +149,6 @@ def request( logger.debug(err) raise APIError(f"Error sending request: {err=}") - # NOTE what about other successful status codes? if resp.status_code != 200: raise APIError.from_response(resp) From 8481b6c118a04d42c9fd079307cebaf1525a36cc Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Fri, 18 Apr 2025 08:54:49 -0700 Subject: [PATCH 4/7] remove types-click from parent pyproject.toml --- uv.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/uv.lock b/uv.lock index c3c472a..b766493 100644 --- a/uv.lock +++ b/uv.lock @@ -1508,7 +1508,6 @@ dependencies = [ { name = "pystapi-validator" }, { name = "stapi-fastapi" }, { name = "stapi-pydantic" }, - { name = "types-click" }, ] [package.dev-dependencies] @@ -1535,7 +1534,6 @@ requires-dist = [ { name = "pystapi-validator", editable = "pystapi-validator" }, { name = "stapi-fastapi", editable = "stapi-fastapi" }, { name = "stapi-pydantic", editable = "stapi-pydantic" }, - { name = "types-click", specifier = ">=7.1.8" }, ] [package.metadata.requires-dev] From 3b2d167b66dc2e69b74d31a46a73da87e52e5d49 Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Fri, 18 Apr 2025 10:32:42 -0700 Subject: [PATCH 5/7] make DatetimeInterval RFC 3339 compliant --- .../src/stapi_pydantic/datetime_interval.py | 27 +++++++++++-------- .../tests/test_datetime_interval.py | 11 ++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 stapi-pydantic/tests/test_datetime_interval.py diff --git a/stapi-pydantic/src/stapi_pydantic/datetime_interval.py b/stapi-pydantic/src/stapi_pydantic/datetime_interval.py index a581cf7..0139bb2 100644 --- a/stapi-pydantic/src/stapi_pydantic/datetime_interval.py +++ b/stapi-pydantic/src/stapi_pydantic/datetime_interval.py @@ -10,32 +10,37 @@ WrapSerializer, ) +_DatetimeTuple = tuple[datetime | None, datetime | None] -def validate_before( - value: str | tuple[datetime, datetime], -) -> tuple[datetime, datetime]: + +def validate_before(value: str | _DatetimeTuple) -> _DatetimeTuple: if isinstance(value, str): - start, end = value.split("/", 1) - return (datetime.fromisoformat(start), datetime.fromisoformat(end)) + start_str, end_str = value.split("/", 1) + start = None if start_str == ".." else datetime.fromisoformat(start_str) + end = None if end_str == ".." else datetime.fromisoformat(end_str) + value = (start, end) return value -def validate_after(value: tuple[datetime, datetime]) -> tuple[datetime, datetime]: - if value[1] < value[0]: +def validate_after(value: _DatetimeTuple) -> _DatetimeTuple: + # None/date & date/None are always valid + if value[1] and value[0] and value[1] < value[0]: raise ValueError("end before start") return value def serialize( - value: tuple[datetime, datetime], - serializer: Callable[[tuple[datetime, datetime]], tuple[str, str]], + value: _DatetimeTuple, + serializer: Callable[[_DatetimeTuple], tuple[str, str]], ) -> str: del serializer # unused - return f"{value[0].isoformat()}/{value[1].isoformat()}" + start = value[0].isoformat() if value[0] else ".." + end = value[1].isoformat() if value[1] else ".." + return f"{start}/{end}" DatetimeInterval = Annotated[ - tuple[AwareDatetime, AwareDatetime], + tuple[AwareDatetime | None, AwareDatetime | None], BeforeValidator(validate_before), AfterValidator(validate_after), WrapSerializer(serialize, return_type=str), diff --git a/stapi-pydantic/tests/test_datetime_interval.py b/stapi-pydantic/tests/test_datetime_interval.py new file mode 100644 index 0000000..28ff952 --- /dev/null +++ b/stapi-pydantic/tests/test_datetime_interval.py @@ -0,0 +1,11 @@ +from stapi_pydantic import DatetimeInterval + + +def test_datetime_interval() -> None: + """Test the datetime interval validator.""" + dt1 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/2025-04-01T23:59:59Z") + dt2 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/..") + dt3 = DatetimeInterval.__metadata__[0].func("../2025-04-01T23:59:59Z") + _ = DatetimeInterval.__metadata__[1].func(dt1) + _ = DatetimeInterval.__metadata__[1].func(dt2) + _ = DatetimeInterval.__metadata__[1].func(dt3) From a97129d1bf6384692aecca5ba8eaac2a18df83d3 Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Wed, 23 Apr 2025 13:27:49 -0700 Subject: [PATCH 6/7] add a couple more tests to datetime interval; synch pytest versions --- pyproject.toml | 1 - stapi-pydantic/pyproject.toml | 5 +++++ stapi-pydantic/tests/test_datetime_interval.py | 17 ++++++++++++++--- uv.lock | 9 ++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0d227cf..a17a527 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ dependencies = [ dev = [ "pytest>=8.1.1", "mypy>=1.15.0", - "pytest>=8.3.5", "ruff>=0.11.2", "pymarkdownlnt>=0.9.25", "pre-commit>=4.2.0", diff --git a/stapi-pydantic/pyproject.toml b/stapi-pydantic/pyproject.toml index 8c4f1e8..e2f5d17 100644 --- a/stapi-pydantic/pyproject.toml +++ b/stapi-pydantic/pyproject.toml @@ -25,3 +25,8 @@ include = ["src/stapi_pydantic"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[dependency-groups] +dev = [ + "pytest>=8.1.1", +] diff --git a/stapi-pydantic/tests/test_datetime_interval.py b/stapi-pydantic/tests/test_datetime_interval.py index 28ff952..c35c2c7 100644 --- a/stapi-pydantic/tests/test_datetime_interval.py +++ b/stapi-pydantic/tests/test_datetime_interval.py @@ -1,11 +1,22 @@ +import pytest from stapi_pydantic import DatetimeInterval -def test_datetime_interval() -> None: +def test_valid_datetime_intervals() -> None: """Test the datetime interval validator.""" dt1 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/2025-04-01T23:59:59Z") - dt2 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/..") - dt3 = DatetimeInterval.__metadata__[0].func("../2025-04-01T23:59:59Z") _ = DatetimeInterval.__metadata__[1].func(dt1) + dt2 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/..") _ = DatetimeInterval.__metadata__[1].func(dt2) + dt3 = DatetimeInterval.__metadata__[0].func("../2025-04-01T23:59:59Z") _ = DatetimeInterval.__metadata__[1].func(dt3) + dt4 = DatetimeInterval.__metadata__[0].func("../..") + _ = DatetimeInterval.__metadata__[1].func(dt4) + + +def test_invalid_datetime_intervals() -> None: + """Test the datetime interval validator.""" + with pytest.raises(ValueError, match="end before start"): + dt1 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/2025-03-01T23:59:59Z") + _ = DatetimeInterval.__metadata__[1].func(dt1) + diff --git a/uv.lock b/uv.lock index dc69de0..f16c05f 100644 --- a/uv.lock +++ b/uv.lock @@ -1553,7 +1553,6 @@ dev = [ { name = "pygithub", specifier = ">=2.6.1" }, { name = "pymarkdownlnt", specifier = ">=0.9.25" }, { name = "pytest", specifier = ">=8.1.1" }, - { name = "pytest", specifier = ">=8.3.5" }, { name = "respx", specifier = ">=0.22.0" }, { name = "ruff", specifier = ">=0.11.2" }, { name = "types-click", specifier = ">=7.1.8" }, @@ -2152,12 +2151,20 @@ dependencies = [ { name = "geojson-pydantic" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + [package.metadata] requires-dist = [ { name = "cql2", specifier = ">=0.3.6" }, { name = "geojson-pydantic", specifier = ">=1.2.0" }, ] +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.1.1" }] + [[package]] name = "starlette" version = "0.46.2" From 7d245a2675cce5684c6f9b62488fb38a7a72b554 Mon Sep 17 00:00:00 2001 From: Philip Weiss Date: Wed, 23 Apr 2025 13:30:27 -0700 Subject: [PATCH 7/7] reformat --- stapi-pydantic/tests/test_datetime_interval.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stapi-pydantic/tests/test_datetime_interval.py b/stapi-pydantic/tests/test_datetime_interval.py index c35c2c7..01ee6c6 100644 --- a/stapi-pydantic/tests/test_datetime_interval.py +++ b/stapi-pydantic/tests/test_datetime_interval.py @@ -19,4 +19,3 @@ def test_invalid_datetime_intervals() -> None: with pytest.raises(ValueError, match="end before start"): dt1 = DatetimeInterval.__metadata__[0].func("2025-04-01T00:00:00Z/2025-03-01T23:59:59Z") _ = DatetimeInterval.__metadata__[1].func(dt1) -