diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 13d03ed..88fc258 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,7 +25,8 @@ "customizations": { "vscode": { "settings": { - "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python" + "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python", + "python.languageServer": "None" }, "extensions": [ "ms-toolsai.jupyter", @@ -35,7 +36,8 @@ "ms-toolsai.vscode-jupyter-slideshow", "ms-python.vscode-pylance", "ms-python.python", - "ms-python.debugpy" + "ms-python.debugpy", + "astral-sh.ty" ] } }, diff --git a/README.md b/README.md index c7cb31d..c426b72 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,15 @@ permalink: /index.html --- +## Workshop Material + +| Name | Description | +| ---- | ----------- | +| [my-schema](my-schema/README.md) | A starting point for modelling your own data with Overture's schema framework | +| [schema-bootstrap](schema-bootstrap/README.md) | Generate a first draft of that model from a data file and the metadata shipped beside it | + +--- + ## Workshop Setup ### Local setup (recommended) diff --git a/examples/bad-depth.yaml b/examples/bad-depth.yaml new file mode 100644 index 0000000..c518934 --- /dev/null +++ b/examples/bad-depth.yaml @@ -0,0 +1,10 @@ +id: 5d40bd6c-db14-4492-a29f-5b6f3e7a1e44 +type: Feature +geometry: + type: Polygon + coordinates: [[[-111.95, 41.05], [-111.90, 41.05], [-111.90, 41.10], [-111.95, 41.10], [-111.95, 41.05]]] +properties: + theme: base + type: bathymetry + version: 0 + depth: -1 diff --git a/examples/bathymetry-example.yaml b/examples/bathymetry-example.yaml new file mode 100644 index 0000000..b84a308 --- /dev/null +++ b/examples/bathymetry-example.yaml @@ -0,0 +1,10 @@ +id: 5d40bd6c-db14-4492-a29f-5b6f3e7a1e44 +type: Feature +geometry: + type: Polygon + coordinates: [[[-111.95, 41.05], [-111.90, 41.05], [-111.90, 41.10], [-111.95, 41.10], [-111.95, 41.05]]] +properties: + theme: base + type: bathymetry + version: 0 + depth: 10 diff --git a/examples/bathymetry.parquet b/examples/bathymetry.parquet new file mode 100644 index 0000000..b10b8fe Binary files /dev/null and b/examples/bathymetry.parquet differ diff --git a/examples/bathymetry.sql b/examples/bathymetry.sql new file mode 100644 index 0000000..a57868d --- /dev/null +++ b/examples/bathymetry.sql @@ -0,0 +1,6 @@ +-- Writes examples/bathymetry.parquet: 100 rows of Overture bathymetry. +-- Run from the repo root: duckdb < examples/bathymetry.sql +INSTALL spatial; LOAD spatial; SET s3_region='us-west-2'; +COPY (SELECT * FROM read_parquet('s3://overturemaps-us-west-2/release/2026-08-19.0/theme=base/type=bathymetry/*.parquet') + LIMIT 100) +TO 'examples/bathymetry.parquet' (FORMAT PARQUET); diff --git a/examples/divisions.sql b/examples/divisions.sql new file mode 100644 index 0000000..26d88d3 --- /dev/null +++ b/examples/divisions.sql @@ -0,0 +1,7 @@ +-- Prints 100 Overture division rows as JSON, one per line, geometry as GeoJSON. +-- duckdb < examples/divisions.sql | overture-schema validate --type division --show-field id - +INSTALL spatial; LOAD spatial; SET s3_region='us-west-2'; +COPY (SELECT ST_AsGeoJSON(geometry) AS geometry, * EXCLUDE geometry + FROM read_parquet('s3://overturemaps-us-west-2/release/2026-08-19.0/theme=divisions/type=division/*.parquet') + LIMIT 100) +TO '/dev/stdout' (FORMAT JSON, ARRAY false); diff --git a/my-schema/README.md b/my-schema/README.md new file mode 100644 index 0000000..33b35a2 --- /dev/null +++ b/my-schema/README.md @@ -0,0 +1,144 @@ +# my-schema + +A starting point for modelling your own data with Overture's schema framework. +Codespaces installs it for you; edit `src/my_schema/models.py` in the editor and +run the commands below in the terminal. + +## Try the example + +```console +overture-schema list-types --tag my_schema +overture-schema validate my-schema/examples/good.json +overture-schema validate my-schema/examples/bad.json +overture-codegen generate --format markdown --tag my_schema --output-dir docs/ +``` + +`--tag my_schema` selects your models. The tag comes from the tag provider in +`src/my_schema/tags.py`, which tags every model this package registers. + +## Exercise: add a second model + +Run these from the repository root. Paths are relative to it. + +**1. Write the model.** Append to `my-schema/src/my_schema/models.py`: + +```python +class RailCrossingRating(Feature): + """A safety star rating for a level crossing, where a road meets a railway.""" + + geometry: Annotated[ + Geometry, + GeometryTypeConstraint(GeometryType.POINT), + Field(description="Where the road crosses the railway."), + ] + stars: StarRating +``` + +Then re-export it from `my-schema/src/my_schema/__init__.py`: + +```python +from my_schema.models import RailCrossingRating, RoadSafetyRating + +__all__ = ["RailCrossingRating", "RoadSafetyRating"] +``` + +**2. Look for it.** The tools can't see it yet: + +```console +$ overture-schema list-types --tag my_schema +road_safety_rating feature my_schema +``` + +They find models through entry points, and this one has none. + +**3. Register it.** In `my-schema/pyproject.toml`, add a line under the existing one: + +```toml +[project.entry-points."overture.models"] +road_safety_rating = "my_schema:RoadSafetyRating" +rail_crossing_rating = "my_schema:RailCrossingRating" +``` + +Run `list-types` again: still one model. The tools read entry points from what was +*installed*, not from `pyproject.toml`, and that record is written only at install +time. Re-sync: + +```console +$ uv sync --all-packages +$ overture-schema list-types --tag my_schema +rail_crossing_rating feature my_schema +road_safety_rating feature my_schema +``` + +Edits to a model's code never need this step; only a new entry point does. + +**4. Validate against the right model.** With two models, name the one you mean: + +```console +$ overture-schema validate --type road_safety_rating my-schema/examples/motorway-without-speed-limit.json +``` + +This fails on the motorway rule. Leave out `--type` and the validator picks a model +for you. Here it picks `RailCrossingRating` and reports the geometry instead. + +**5. Tag by mode, and mark a draft.** Replace `my-schema/src/my_schema/tags.py` with: + +```python +"""Tag this package's models so the tools can select them with `--tag my_schema`.""" + +from collections.abc import Iterable + +from pydantic import BaseModel + +from overture.schema.system.discovery import ModelKey + +# Entry-point name -> mode, and the models still in draft. +MODE = {"road_safety_rating": "road", "rail_crossing_rating": "rail"} +DRAFTS = {"rail_crossing_rating"} + + +def my_schema_provider( + types: Iterable[type[BaseModel]], key: ModelKey, tags: set[str] +) -> set[str]: + """Tag every model from this package `my_schema`, plus its mode and draft status.""" + if key.entry_point.startswith("my_schema:"): + tags.add("my_schema") + tags.add(f"my_schema:mode={MODE[key.name]}") + if key.name in DRAFTS: + tags.add("my_schema:draft") + return tags +``` + +This is ordinary code, so no re-sync: + +```console +$ overture-schema list-types --tag my_schema --group-by my_schema:mode +my_schema:mode=rail (1) +→ rail_crossing_rating feature my_schema my_schema:draft my_schema:mode=rail + +my_schema:mode=road (1) +→ road_safety_rating feature my_schema my_schema:mode=road + +$ overture-schema list-types --tag my_schema --exclude my_schema:draft +road_safety_rating feature my_schema my_schema:mode=road +``` + +**6. Publish only what's ready.** `--exclude` works on the docs too: + +```console +$ overture-codegen generate --format markdown --tag my_schema --exclude my_schema:draft --output-dir docs/ +``` + +`docs/my_schema/` has a page for `road_safety_rating` and none for the draft. + +## Model your data + +1. Write a class in `src/my_schema/models.py` that subclasses `Feature`. +2. Re-export it from `src/my_schema/__init__.py`. +3. Register it in `pyproject.toml` under `[project.entry-points."overture.models"]`. +4. Run `uv sync --all-packages`, then `overture-schema list-types --tag my_schema` to + check it appears. (`--all-packages` keeps the workshop's other packages; a bare + `uv sync` in this directory removes them.) + +Edits to an existing model take effect immediately. Only a new entry point needs +`uv sync --all-packages`, as in the exercise above. diff --git a/my-schema/examples/bad.json b/my-schema/examples/bad.json new file mode 100644 index 0000000..f74c6bc --- /dev/null +++ b/my-schema/examples/bad.json @@ -0,0 +1,10 @@ +{ + "type": "Feature", + "id": "rsr-0002", + "geometry": {"type": "Point", "coordinates": [-122.68, 45.52]}, + "properties": { + "stars": 7, + "road_type": "motorway", + "survey": {"assessor": "irap", "rated_by": "me"} + } +} diff --git a/my-schema/examples/good.json b/my-schema/examples/good.json new file mode 100644 index 0000000..c86281d --- /dev/null +++ b/my-schema/examples/good.json @@ -0,0 +1,10 @@ +{ + "type": "Feature", + "id": "rsr-0001", + "geometry": {"type": "LineString", "coordinates": [[-122.68, 45.52], [-122.67, 45.53]]}, + "properties": { + "stars": 4, + "road_type": "arterial", + "survey": {"assessor": "iRAP", "surveyed_on": "2026-05-01"} + } +} diff --git a/my-schema/examples/motorway-without-speed-limit.json b/my-schema/examples/motorway-without-speed-limit.json new file mode 100644 index 0000000..a8220ca --- /dev/null +++ b/my-schema/examples/motorway-without-speed-limit.json @@ -0,0 +1,9 @@ +{ + "type": "Feature", + "id": "rsr-0003", + "geometry": {"type": "LineString", "coordinates": [[-122.68, 45.52], [-122.67, 45.53]]}, + "properties": { + "stars": 2, + "road_type": "motorway" + } +} diff --git a/my-schema/pyproject.toml b/my-schema/pyproject.toml new file mode 100644 index 0000000..22e8882 --- /dev/null +++ b/my-schema/pyproject.toml @@ -0,0 +1,38 @@ +[project] +name = "my-schema" +version = "0.1.0" +description = "A starting point for modelling your own data with Overture's schema framework" +requires-python = ">=3.12" +dependencies = [ + "overture-schema>=2.0.0", + "overture-schema-codegen>=2.0.0", +] + +[build-system] +requires = ["uv_build>=0.11.32,<0.13"] +build-backend = "uv_build" + +[tool.uv.build-backend] +module-name = "my_schema" + +# The tools find your models through this entry point: one line per model, +# `name = "module:Class"`. After adding a line, run `uv sync` so it registers. +[project.entry-points."overture.models"] +road_safety_rating = "my_schema:RoadSafetyRating" + +# Tags the models above with `my_schema`, so `--tag my_schema` selects them. +[project.entry-points."overture.tag_providers"] +my_schema = "my_schema.tags:my_schema_provider" + +# Example rows appear on each model's generated Markdown page. +[[examples.RoadSafetyRating]] +id = "rsr-0001" +geometry = "LINESTRING (-122.68 45.52, -122.67 45.53)" +stars = 4 +road_type = "arterial" + +[examples.RoadSafetyRating.bbox] +xmin = -122.68 +xmax = -122.67 +ymin = 45.52 +ymax = 45.53 diff --git a/my-schema/src/my_schema/__init__.py b/my-schema/src/my_schema/__init__.py new file mode 100644 index 0000000..63c24f6 --- /dev/null +++ b/my-schema/src/my_schema/__init__.py @@ -0,0 +1,5 @@ +"""Your models. Re-export each one here so its entry point can find it.""" + +from my_schema.models import RoadSafetyRating + +__all__ = ["RoadSafetyRating"] diff --git a/my-schema/src/my_schema/models.py b/my-schema/src/my_schema/models.py new file mode 100644 index 0000000..a3a008c --- /dev/null +++ b/my-schema/src/my_schema/models.py @@ -0,0 +1,58 @@ +"""An example model. Copy it, rename it, and make it describe your data.""" + +from datetime import date +from typing import Annotated, NewType + +from pydantic import BaseModel, Field + +from overture.schema.system.doc import DocumentedEnum +from overture.schema.system.feature import Feature +from overture.schema.system.geometric import Geometry, GeometryType, GeometryTypeConstraint +from overture.schema.system.model_constraint import FieldEqCondition, no_extra_fields, require_if +from overture.schema.system.numeric import uint8, uint16 + +# A named type carries its description and constraints everywhere it is used. +StarRating = NewType( + "StarRating", + Annotated[uint8, Field(ge=1, le=5, description="Star rating from 1 (least safe) to 5 (safest).")], +) + + +# A coded column: list every legal value and what it means. +class RoadType(str, DocumentedEnum): + """Kind of road that was rated.""" + + MOTORWAY = ("motorway", "Divided highway with controlled access.") + ARTERIAL = ("arterial", "Major road connecting districts.") + LOCAL = ("local", "Street serving the properties along it.") + + +# A struct: a group of related fields. +@no_extra_fields +class Survey(BaseModel): + """Who rated the road, and when.""" + + assessor: str + surveyed_on: date | None = None + + +# A rule across fields, written as data so the docs can state it. +# Avoid @field_validator and @model_validator: they run, but no tool can read them. +@no_extra_fields +@require_if(["speed_limit_kph"], FieldEqCondition("road_type", "motorway")) +class RoadSafetyRating(Feature): + """A road-safety star rating for a stretch of road.""" + + geometry: Annotated[ + Geometry, + GeometryTypeConstraint(GeometryType.LINE_STRING), + Field(description="The rated stretch of road."), + ] + stars: StarRating + road_type: Annotated[RoadType | None, Field(description="Kind of road that was rated.")] = None + speed_limit_kph: Annotated[ + uint16 | None, Field(description="Posted speed limit, in kilometres per hour.") + ] = None + survey: Annotated[ + Survey | None, Field(description="The survey this rating came from.") + ] = None diff --git a/my-schema/src/my_schema/tags.py b/my-schema/src/my_schema/tags.py new file mode 100644 index 0000000..ded90d3 --- /dev/null +++ b/my-schema/src/my_schema/tags.py @@ -0,0 +1,16 @@ +"""Tag this package's models so the tools can select them with `--tag my_schema`.""" + +from collections.abc import Iterable + +from pydantic import BaseModel + +from overture.schema.system.discovery import ModelKey + + +def my_schema_provider( + types: Iterable[type[BaseModel]], key: ModelKey, tags: set[str] +) -> set[str]: + """Add `my_schema` to every model registered from this package.""" + if key.entry_point.startswith("my_schema:"): + tags.add("my_schema") + return tags diff --git a/notebooks/5-schema-models.ipynb b/notebooks/5-schema-models.ipynb new file mode 100644 index 0000000..c6d922e --- /dev/null +++ b/notebooks/5-schema-models.ipynb @@ -0,0 +1,590 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "854970c5", + "metadata": {}, + "source": [ + "# Modelling your data: fields, types, and rules\n", + "\n", + "The hands-on half of the schema slides (**How**, **Fields**, **Constraints**). Each\n", + "section runs one concept against the `RoadSafetyRating` model in\n", + "[`my-schema`](../my-schema/src/my_schema/models.py), then leaves you a one-cell\n", + "exercise marked **Try it**.\n", + "\n", + "Some cells are *meant* to fail. A failure prints a `ValidationError`; the last lines\n", + "name the field and the rule it broke." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae9eda48", + "metadata": { + "lines_to_next_cell": 1 + }, + "outputs": [], + "source": [ + "%xmode Minimal\n", + "import json\n", + "\n", + "from pydantic import TypeAdapter\n", + "\n", + "from my_schema.models import RoadSafetyRating, RoadType\n", + "\n", + "LINE = {\"type\": \"LineString\", \"coordinates\": [[-122.68, 45.52], [-122.67, 45.53]]}\n", + "POINT = {\"type\": \"Point\", \"coordinates\": [-122.68, 45.52]}\n", + "\n", + "\n", + "def feature(geometry=LINE, **properties):\n", + " \"\"\"A GeoJSON feature, as a JSON string.\"\"\"\n", + " return json.dumps({\"type\": \"Feature\", \"geometry\": geometry, \"properties\": properties})\n", + "\n", + "\n", + "def check(model=RoadSafetyRating, geometry=LINE, **properties):\n", + " \"\"\"Validate one feature against a model.\"\"\"\n", + " return model.model_validate_json(feature(geometry, **properties))" + ] + }, + { + "cell_type": "markdown", + "id": "2e042838", + "metadata": {}, + "source": [ + "## One model, two shapes\n", + "\n", + "The same model reads a GeoJSON feature (JSON, fields under `properties`) and a flat\n", + "row (a Python dict, fields beside `geometry`). Use `model_validate_json` for the\n", + "first and `model_validate` for the second: `model_validate` on a parsed GeoJSON\n", + "dict treats it as a flat row." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0e937ad4", + "metadata": {}, + "outputs": [], + "source": [ + "check(stars=4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bcb17a56", + "metadata": {}, + "outputs": [], + "source": [ + "RoadSafetyRating.model_validate({\"geometry\": \"LINESTRING (-122.68 45.52, -122.67 45.53)\", \"stars\": 4})" + ] + }, + { + "cell_type": "markdown", + "id": "edd0323b", + "metadata": {}, + "source": [ + "`id` and `bbox` print as `MISSING`: absent from the input, and left out of the\n", + "output rather than written as `null`." + ] + }, + { + "cell_type": "markdown", + "id": "76aed59a", + "metadata": {}, + "source": [ + "## Pick a base class\n", + "\n", + "`Feature` gives you `id`, `bbox`, and `geometry`. `OvertureFeature`, which Overture's\n", + "own themes use, adds more." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25bd8082", + "metadata": {}, + "outputs": [], + "source": [ + "from overture.schema.base import Bathymetry\n", + "from overture.schema.system.feature import Feature\n", + "\n", + "list(Feature.model_fields), list(Bathymetry.model_fields)" + ] + }, + { + "cell_type": "markdown", + "id": "04554487", + "metadata": {}, + "source": [ + "## Required or optional?\n", + "\n", + "`stars` has no default, so it is required. Everything else is `X | None = None`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "546837a5", + "metadata": {}, + "outputs": [], + "source": [ + "check()" + ] + }, + { + "cell_type": "markdown", + "id": "b107f6cb", + "metadata": {}, + "source": [ + "**Try it:** make the cell above pass." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "108f5bc0", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f610bbfb", + "metadata": {}, + "source": [ + "## Field names: aliases\n", + "\n", + "The data says `LSAD`; Python says `lsad`. `Place` is `@no_extra_fields`, so data\n", + "spelled `lsad` is a key it doesn't declare, and fails." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cad27bfe", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated\n", + "\n", + "from pydantic import Field\n", + "\n", + "from overture.schema.system.model_constraint import no_extra_fields\n", + "\n", + "\n", + "@no_extra_fields\n", + "class Place(Feature):\n", + " lsad: Annotated[str | None, Field(alias=\"LSAD\")] = None\n", + "\n", + "\n", + "check(Place, POINT, LSAD=\"25\").lsad" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b1e0a7d", + "metadata": {}, + "outputs": [], + "source": [ + "check(Place, POINT, lsad=\"25\")" + ] + }, + { + "cell_type": "markdown", + "id": "0c293c62", + "metadata": {}, + "source": [ + "**Try it:** add a `class_` field whose name in the data is `class`, and read one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "faac7830", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ca18e6c1", + "metadata": {}, + "source": [ + "## Sized numbers\n", + "\n", + "`speed_limit_kph` is a `uint16`, so its size is a bound too." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0825fbe8", + "metadata": {}, + "outputs": [], + "source": [ + "check(stars=3, speed_limit_kph=70000)" + ] + }, + { + "cell_type": "markdown", + "id": "e28049f3", + "metadata": {}, + "source": [ + "## Geometry\n", + "\n", + "The model allows only `LineString`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c22e9f21", + "metadata": {}, + "outputs": [], + "source": [ + "check(geometry=POINT, stars=3)" + ] + }, + { + "cell_type": "markdown", + "id": "cb453ac2", + "metadata": {}, + "source": [ + "## Structs, and keys nobody declared\n", + "\n", + "`Survey` is marked `@no_extra_fields`, so an undeclared key inside it fails:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6efa7d74", + "metadata": {}, + "outputs": [], + "source": [ + "check(stars=3, survey={\"assessor\": \"iRAP\", \"rated_by\": \"me\"})" + ] + }, + { + "cell_type": "markdown", + "id": "c1db5c22", + "metadata": {}, + "source": [ + "`RoadSafetyRating` is marked `@no_extra_fields` too, so a misspelled top-level field\n", + "fails instead of disappearing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44dcef2f", + "metadata": {}, + "outputs": [], + "source": [ + "check(stars=3, spead_limit_kph=50)" + ] + }, + { + "cell_type": "markdown", + "id": "fa6334ad", + "metadata": {}, + "source": [ + "**Try it:** give `check()` a `survey` with a `surveyed_on` date, and read it back\n", + "as a `date`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb059933", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f416cb1b", + "metadata": {}, + "source": [ + "## NewTypes\n", + "\n", + "A named type brings its description and checks everywhere it's used. Tested on its\n", + "own, the error header names Pydantic's internal validator, not the type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12924a65", + "metadata": {}, + "outputs": [], + "source": [ + "from overture.schema.system.string import CountryCodeAlpha2\n", + "\n", + "country = TypeAdapter(CountryCodeAlpha2)\n", + "country.validate_python(\"US\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e303cbd", + "metadata": {}, + "outputs": [], + "source": [ + "country.validate_python(\"us\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "560137a0", + "metadata": {}, + "outputs": [], + "source": [ + "country.json_schema()" + ] + }, + { + "cell_type": "markdown", + "id": "69ac1f42", + "metadata": {}, + "source": [ + "**Try it:** `country.validate_python(\"XX\")`. What does the check actually test?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20d73d88", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "78c71a97", + "metadata": {}, + "source": [ + "## Enums with meanings\n", + "\n", + "Every legal code, and what it means." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08df800e", + "metadata": {}, + "outputs": [], + "source": [ + "for member in RoadType:\n", + " print(f\"{member.value:10} {member.__doc__}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16ccf48d", + "metadata": {}, + "outputs": [], + "source": [ + "check(stars=3, road_type=\"highway\")" + ] + }, + { + "cell_type": "markdown", + "id": "28097d64", + "metadata": {}, + "source": [ + "**Try it:** write a `DocumentedEnum` for a coded column in your own data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62cc763c", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "from overture.schema.system.doc import DocumentedEnum" + ] + }, + { + "cell_type": "markdown", + "id": "b0ac07dc", + "metadata": {}, + "source": [ + "## Pre-built constraints\n", + "\n", + "On a field:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ca3b495", + "metadata": {}, + "outputs": [], + "source": [ + "from overture.schema.system.string import HexColor, PhoneNumber, WikidataId\n", + "\n", + "TypeAdapter(HexColor).validate_python(\"#ff0000\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53553798", + "metadata": {}, + "outputs": [], + "source": [ + "TypeAdapter(HexColor).validate_python(\"red\")" + ] + }, + { + "cell_type": "markdown", + "id": "551e9c19", + "metadata": {}, + "source": [ + "**Try it:** find one good and one bad value for `PhoneNumber` and `WikidataId`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14f463e9", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e4a4cd24", + "metadata": {}, + "source": [ + "Across fields, as a class decorator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e092868", + "metadata": {}, + "outputs": [], + "source": [ + "from overture.schema.system.model_constraint import require_any_of\n", + "\n", + "\n", + "@require_any_of(\"name\", \"ref\")\n", + "class Road(Feature):\n", + " name: str | None = None\n", + " ref: str | None = None\n", + "\n", + "\n", + "check(Road, POINT)" + ] + }, + { + "cell_type": "markdown", + "id": "7c512090", + "metadata": {}, + "source": [ + "## Rules as data, not functions\n", + "\n", + "The same rule, twice. Both reject a motorway without a speed limit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "498fe479", + "metadata": {}, + "outputs": [], + "source": [ + "from pydantic import model_validator\n", + "\n", + "from overture.schema.system.json_schema import json_schema\n", + "from overture.schema.system.model_constraint import FieldEqCondition, require_if\n", + "\n", + "\n", + "class AsFunction(Feature):\n", + " road_type: str | None = None\n", + " speed_limit_kph: int | None = None\n", + "\n", + " @model_validator(mode=\"after\")\n", + " def motorways_need_speed_limit(self):\n", + " if self.road_type == \"motorway\" and self.speed_limit_kph is None:\n", + " raise ValueError(\"speed_limit_kph is required on motorways\")\n", + " return self\n", + "\n", + "\n", + "@require_if([\"speed_limit_kph\"], FieldEqCondition(\"road_type\", \"motorway\"))\n", + "class AsData(Feature):\n", + " road_type: str | None = None\n", + " speed_limit_kph: int | None = None\n", + "\n", + "\n", + "check(AsData, road_type=\"motorway\")" + ] + }, + { + "cell_type": "markdown", + "id": "31bf50ba", + "metadata": {}, + "source": [ + "Only one of them reaches JSON Schema:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df75e68b", + "metadata": {}, + "outputs": [], + "source": [ + "\"if\" in json.dumps(json_schema(AsFunction)), \"if\" in json.dumps(json_schema(AsData))" + ] + }, + { + "cell_type": "markdown", + "id": "9f771fb8", + "metadata": {}, + "source": [ + "**Try it:** add `@forbid_if([\"speed_limit_kph\"], FieldEqCondition(\"road_type\", \"local\"))`\n", + "to `AsData`, and look for it in `json_schema(AsData)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5f874dc", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index cadf94c..ddfd76f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,11 +11,26 @@ dependencies = [ "jupyter>=1.0", "lonboard>=0.16.0", "matplotlib>=3.7", + "my-schema", "notebook>=7.0", "numpy>=1.24", "overturemaps>=1.0.0", "pandas>=2.0", "pyarrow>=14.0", "pystac>=1.14.3", + "schema-bootstrap", "shapely>=2.0", ] + +[tool.uv.workspace] +members = [ + "my-schema", + "schema-bootstrap", +] + +[tool.uv.sources] +my-schema = { workspace = true, editable = true } +schema-bootstrap = { workspace = true, editable = true } +# stac-table-columns output is unreleased (OvertureMaps/schema#724); install codegen from +# that branch until it ships. +overture-schema-codegen = { git = "https://github.com/OvertureMaps/schema.git", branch = "stac-table-columns", subdirectory = "packages/overture-schema-codegen" } diff --git a/requirements.txt b/requirements.txt index 2cab6a1..727149e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,11 @@ # This file was autogenerated by uv via the following command: -# uv export --no-hashes --format requirements-txt +# uv export --frozen --no-hashes --format requirements-txt -o requirements.txt +-e ./my-schema + # via workshop +-e ./schema-bootstrap + # via workshop +annotated-types==0.8.0 + # via pydantic anyio==4.13.0 # via # httpx @@ -58,6 +64,8 @@ click==8.3.3 # click-plugins # cligj # fiona + # overture-schema-cli + # overture-schema-codegen # overturemaps click-plugins==1.1.1.2 # via fiona @@ -83,8 +91,14 @@ decorator==5.2.1 # via ipython defusedxml==0.7.1 # via nbconvert -duckdb==1.5.2 - # via workshop +dnspython==2.8.0 + # via email-validator +duckdb==1.5.5 + # via + # schema-bootstrap + # workshop +email-validator==2.3.0 + # via pydantic executing==2.2.1 # via stack-data fastjsonschema==2.21.2 @@ -108,6 +122,7 @@ httpx==0.28.1 idna==3.14 # via # anyio + # email-validator # httpx # jsonschema # requests @@ -138,6 +153,7 @@ jinja2==3.1.6 # jupyterlab # jupyterlab-server # nbconvert + # overture-schema-codegen json5==0.14.0 # via jupyterlab-server jsonpointer==3.1.1 @@ -200,6 +216,8 @@ lark==1.3.1 # via rfc3987-syntax lonboard==0.16.0 # via workshop +markdown-it-py==4.2.0 + # via rich markupsafe==3.0.3 # via # jinja2 @@ -210,6 +228,8 @@ matplotlib-inline==0.2.2 # via # ipykernel # ipython +mdurl==0.1.2 + # via markdown-it-py mistune==3.2.1 # via nbconvert nbclient==0.10.4 @@ -246,6 +266,51 @@ numpy==2.4.4 # workshop orjson==3.11.9 # via overturemaps +overture-schema==2.0.0 + # via my-schema +overture-schema-cli @ git+https://github.com/OvertureMaps/schema.git@1422ae58fbc67009e6c220afdf1eeb393f41512d#subdirectory=packages/overture-schema-cli + # via + # overture-schema + # overture-schema-codegen +overture-schema-codegen @ git+https://github.com/OvertureMaps/schema.git@1422ae58fbc67009e6c220afdf1eeb393f41512d#subdirectory=packages/overture-schema-codegen + # via my-schema +overture-schema-common @ git+https://github.com/OvertureMaps/schema.git@1422ae58fbc67009e6c220afdf1eeb393f41512d#subdirectory=packages/overture-schema-common + # via + # overture-schema-cli + # overture-schema-codegen + # overture-schema-theme-addresses + # overture-schema-theme-base + # overture-schema-theme-buildings + # overture-schema-theme-divisions + # overture-schema-theme-places + # overture-schema-theme-transportation + # overture-schema-validation +overture-schema-system @ git+https://github.com/OvertureMaps/schema.git@1422ae58fbc67009e6c220afdf1eeb393f41512d#subdirectory=packages/overture-schema-system + # via + # overture-schema-cli + # overture-schema-codegen + # overture-schema-common + # overture-schema-theme-addresses + # overture-schema-theme-base + # overture-schema-theme-buildings + # overture-schema-theme-divisions + # overture-schema-theme-places + # overture-schema-theme-transportation + # overture-schema-validation +overture-schema-theme-addresses==2.0.0 + # via overture-schema +overture-schema-theme-base==2.0.0 + # via overture-schema +overture-schema-theme-buildings==2.0.0 + # via overture-schema +overture-schema-theme-divisions==2.0.0 + # via overture-schema +overture-schema-theme-places==2.0.0 + # via overture-schema +overture-schema-theme-transportation==2.0.0 + # via overture-schema +overture-schema-validation==2.0.0 + # via overture-schema overturemaps==1.0.0 # via workshop packaging==26.2 @@ -297,6 +362,20 @@ pyarrow==24.0.0 # workshop pycparser==3.0 ; implementation_name != 'PyPy' # via cffi +pydantic==2.13.5 + # via + # overture-schema-cli + # overture-schema-common + # overture-schema-system + # overture-schema-theme-addresses + # overture-schema-theme-base + # overture-schema-theme-buildings + # overture-schema-theme-divisions + # overture-schema-theme-places + # overture-schema-theme-transportation + # overture-schema-validation +pydantic-core==2.46.5 + # via pydantic pyfiglet==1.0.4 # via overturemaps pygments==2.20.0 @@ -305,6 +384,7 @@ pygments==2.20.0 # ipython-pygments-lexers # jupyter-console # nbconvert + # rich pyogrio==0.12.1 # via geopandas pyparsing==3.3.2 @@ -331,7 +411,10 @@ pywinpty==3.0.3 ; os_name == 'nt' # jupyter-server-terminals # terminado pyyaml==6.0.3 - # via jupyter-events + # via + # jupyter-events + # overture-schema-cli + # yamlcore pyzmq==27.1.0 # via # ipykernel @@ -355,6 +438,8 @@ rfc3986-validator==0.1.1 # jupyter-events rfc3987-syntax==1.1.0 # via jsonschema +rich==15.0.0 + # via overture-schema-cli rpds-py==0.30.0 # via # jsonschema @@ -366,6 +451,8 @@ setuptools==82.0.1 shapely==2.1.2 # via # geopandas + # overture-schema-common + # overture-schema-system # overturemaps # workshop six==1.17.0 @@ -408,13 +495,20 @@ traitlets==5.15.0 # nbclient # nbconvert # nbformat -typing-extensions==4.15.0 +typing-extensions==4.16.0 # via # anyio # anywidget # beautifulsoup4 # lonboard + # overture-schema-codegen + # overture-schema-system + # pydantic + # pydantic-core # referencing + # typing-inspection +typing-inspection==0.4.4 + # via pydantic tzdata==2026.2 # via # arrow @@ -435,3 +529,5 @@ websocket-client==1.9.0 # via jupyter-server widgetsnbextension==4.0.15 # via ipywidgets +yamlcore==0.0.4 + # via overture-schema-cli diff --git a/schema-bootstrap/.gitignore b/schema-bootstrap/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/schema-bootstrap/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/schema-bootstrap/README.md b/schema-bootstrap/README.md new file mode 100644 index 0000000..f33a565 --- /dev/null +++ b/schema-bootstrap/README.md @@ -0,0 +1,91 @@ +# schema-bootstrap + +Bootstrap an Overture-convention Pydantic model from a data file. + +Writing a model for someone else's data from scratch is slow. schema-bootstrap writes the first +draft: fields, types, geometry, descriptions and enum domains, each taken from the data or the +metadata shipped with it. **Anything it cannot find a source for is left as a TODO** — a column +with no description, a code with no documented meaning — for you to fill in. + +```console +$ schema-bootstrap places.shp --class-name UtahPlace -o model.py +``` + +## Where each part of the model comes from + +| Source | Gives | +|---|---| +| The data, read with DuckDB (`ST_Read` for Shapefile, GeoPackage and GeoJSON; directly for Parquet) | column names, types, geometry type and SRID | +| A `DISTINCT` pass over each column | the values present, for columns with few enough to be a vocabulary | +| Metadata shipped beside the data: ISO 19110 (`.shp.ea.iso.xml`) or FGDC CSDGM (`.shp.xml`) | a description per column, and the **declared domain** (the legal values) of coded columns | + +`OGC_FID`, which GDAL adds on read, is left out. + +## Declared and observed values + +`DISTINCT` reports the values present in one file, which can be fewer than the values the column +allows. The metadata's declared domain lists the values it allows. `--report` shows both: + +```console +$ schema-bootstrap tests/fixtures/utah_places.shp --report +... + STATEFP str observed=1 +... + LSAD str domain=14 observed=4 UNDECLARED=35 +... +``` + +The generated model handles the three cases differently: + +- **`LSAD` has a declared domain.** Its enum lists all 14 declared values, not only the 4 in this + file. +- **`STATEFP` has none.** Its enum holds the one value observed (the file covers one state), and + its docstring warns that the list is a lower bound to check against the authority. +- **`LSAD` 35 is in the data but not in the declared domain.** It becomes an enum member whose + meaning is a TODO. 35 is *metro township*, a Utah municipal form created in 2015. Neither the + shipped metadata nor the Census Bureau's published code list includes it; only the sibling + column `NAMELSAD` ("Copperton metro township") gives the meaning. + +## Registering the model with Overture's tools + +The model subclasses `overture.schema.system.feature.Feature`. Overture's `theme`/`type` +generics are for Overture's own themes; a model from another package is found through a tag +provider instead. `--tag-provider` writes one (`tags.py`) beside the model and prints the +`pyproject.toml` entry points that register both: + +```console +$ schema-bootstrap places.shp --class-name UtahPlace -o src/utah_schema/utah_places.py \ + --tag-provider utah_schema +[project.entry-points."overture.models"] +utah_place = "utah_schema.utah_places:UtahPlace" + +[project.entry-points."overture.tag_providers"] +utah_schema = "utah_schema.tags:utah_schema_provider" +``` + +Add those to the package's `pyproject.toml` and run `uv sync --all-packages` from the repository +root; `--tag utah_schema` then selects the model. An existing `tags.py` is never overwritten. + +## Modules + +| Module | Role | +|---|---| +| `column.py` | `Column`, the record every stage reads and fills in | +| `introspect.py` | reads the data: types, geometry, distinct values | +| `sidecar.py` | reads the metadata: descriptions, declared domains | +| `render.py` | writes the Pydantic source in Overture's idiom | +| `tag_provider.py` | writes the tag provider and its entry points | +| `bootstrap.py` | runs the stages in order | + +## Development + +```console +uv run pytest && uv run mypy . +``` + +The fixture is 88 Utah places from U.S. Census Bureau TIGER/Line 2023 +([`tl_2023_49_place.zip`](https://www2.census.gov/geo/tiger/TIGER2023/PLACE/tl_2023_49_place.zip)). +The rows still cover every coded value in the full file, and geometry is reduced to bounding boxes +to keep it small. `utah_places.shp.ea.iso.xml` is the archive's `tl_2023_49_place.shp.ea.iso.xml`, +unmodified. [docs/tiger-vocabulary.md](docs/tiger-vocabulary.md) documents what those coded columns +mean and where each meaning came from. diff --git a/schema-bootstrap/docs/tiger-vocabulary.md b/schema-bootstrap/docs/tiger-vocabulary.md new file mode 100644 index 0000000..4d9558e --- /dev/null +++ b/schema-bootstrap/docs/tiger-vocabulary.md @@ -0,0 +1,84 @@ +# TIGER/Line place vocabularies, as they appear in `tl_2023_49_place` + +Utah incorporated places and CDPs, U.S. Census Bureau TIGER/Line 2023. 334 rows. +Source: [`tl_2023_49_place.zip`](https://www2.census.gov/geo/tiger/TIGER2023/PLACE/tl_2023_49_place.zip). + +The DISTINCT pass finds a column's value vocabulary in about a second. It cannot find what +the values mean. This file is the other half: every code present in the extract, with its +meaning sourced from the Census Bureau, and the ones the authority does not cover called out +rather than guessed. + +Two limits worth carrying into any model built from this. First, these are the values +PRESENT IN THIS EXTRACT, not the values legal in the domain -- STATEFP below has exactly one +value here and fifty-six in the United States. Second, an absent code is not an invalid +code: LSAD 35 is real, is used five times, and is not on the Census code list. + +## `LSAD` + +Authority: + +| code | rows | meaning | +|---|---:|---| +| `25` | 147 | city (suffix) | +| `35` | 5 | **not in the authority** -- see note below | +| `43` | 103 | town (suffix) | +| `57` | 79 | CDP (suffix) | + +## `CLASSFP` + +Authority: + +| code | rows | meaning | +|---|---:|---| +| `C1` | 255 | An active incorporated place that does not serve as a county subdivision equivalent | +| `M2` | 1 | A military or other defense installation entirely within a place | +| `U1` | 73 | A census designated place with an official federally recognized name | +| `U2` | 5 | A census designated place without an official federally recognized name | + +## `FUNCSTAT` + +Authority: + +| code | rows | meaning | +|---|---:|---| +| `A` | 255 | Active government providing primary general-purpose functions | +| `S` | 79 | Statistical entity | + +## `MTFCC` + +Authority: + +| code | rows | meaning | +|---|---:|---| +| `G4110` | 255 | Incorporated Place — A legal entity incorporated under state law to provide general-purpose governmental services to a concentration of population.... | +| `G4210` | 79 | Census Designated Place — A statistical area that is defined for a named concentration of population and is the statistical counterpart of an incor... | + +## `PCICBSA` + +No Census code list located for this field. + +| code | rows | meaning | +|---|---:|---| +| `N` | 321 | **not in the authority** -- see note below | +| `Y` | 13 | **not in the authority** -- see note below | + +## `LSAD` 35 -- the code the authority does not have + +Five rows carry LSAD 35 and the Census legal-status code list has no entry for it. The data +answers the question the authority could not: NAMELSAD for those rows reads "Copperton metro +township", "Emigration Canyon metro township", and so on. So LSAD 35 is 'metro township +(suffix)' -- a Utah-specific municipal form created in 2015, present in the extract and +missing from the national code list. + +This is the case worth showing in a room. The mechanical pass found the value, the authority +failed to explain it, and a sibling column settled it. A model that promoted the DISTINCT +output straight to a closed enum would have carried an undocumented member and nobody would +have noticed. + +## `STATEFP` -- one value here, fifty-six in the domain + +STATEFP came back with a single distinct value, 49, because this extract is Utah. A DISTINCT +pass over one file reports 49 as the whole vocabulary. It is a FIPS state code with fifty- +six legal values. Anything inferred from one extract is a lower bound on its domain, which +is why the generated enums carry that warning in their docstrings. + diff --git a/schema-bootstrap/examples/utah_places.py b/schema-bootstrap/examples/utah_places.py new file mode 100644 index 0000000..ac8e16c --- /dev/null +++ b/schema-bootstrap/examples/utah_places.py @@ -0,0 +1,341 @@ +""" +Bootstrapped from utah_places.shp (+ utah_places.shp.ea.iso.xml); 1 metadata attribute(s) +matched no column and were NOT applied: PCINECTA. + +NOT FINISHED. Types and value vocabularies were read off the data and its metadata; +meanings were taken from the metadata where it had them. Every TODO below is a +judgment the sources could not make. +""" + +import textwrap +from typing import Annotated + +from pydantic import Field + +from overture.schema.system.feature import Feature +from overture.schema.system.doc import DocumentedEnum +from overture.schema.system.geometric import ( + Geometry, + GeometryType, + GeometryTypeConstraint, +) +from overture.schema.system.numeric import ( + int64, +) + + +class Statefp(str, DocumentedEnum): + """ + Values for STATEFP. + + Current state Federal Information Processing Series (FIPS) code + + WARNING: no declared domain was found, so these 1 value(s) are only what is + PRESENT IN THIS EXTRACT. That is a lower bound on the vocabulary, not the + vocabulary. Confirm against the authority before treating this as closed. + """ + + V_49 = ("49", "TODO: '49' -- meaning not in any source consulted") + + +class Lsad(str, DocumentedEnum): + """ + Values for LSAD. + + Current legal/statistical area description code for place + + The data's own metadata declares 14 legal value(s), of + which 11 do not occur in this extract. Members + below are the declared domain, so this enum is not narrowed to what one file + happened to contain. + + TODO: 1 value(s) occur in the data and are NOT declared: + '35'. An undeclared code is not an invalid + one -- check a sibling column before assuming a defect in the data. + """ + + V_00 = ("00", "Blank") + V_21 = ("21", "borough (suffix)") + V_25 = ("25", "city (suffix)") + V_37 = ("37", "municipality (suffix)") + V_43 = ("43", "town (suffix)") + V_47 = ("47", "village (suffix)") + V_53 = ("53", "city and borough (suffix)") + V_55 = ("55", "comunidad (suffix)") + V_57 = ("57", "census designated place (CDP) (suffix)") + V_62 = ("62", "zona urbana (suffix)") + CN = ("CN", "corporation (suffix)") + MG = ("MG", "metropolitan government (suffix)") + UC = ("UC", "urban county (suffix)") + UG = ("UG", "unified government (suffix)") + V_35 = ("35", "TODO: '35' -- meaning not in any source consulted") + + +class Classfp(str, DocumentedEnum): + """ + Values for CLASSFP. + + Current Federal Information Processing Series (FIPS) class code + + The data's own metadata declares 10 legal value(s), of + which 6 do not occur in this extract. Members + below are the declared domain, so this enum is not narrowed to what one file + happened to contain. + """ + + C1 = ("C1", "An active incorporated place that does not serve as a county subdivision") + C2 = ( + "C2", + "An active incorporated place that is legally coextensive with a county subdivision but " + "treated as independent of any county subdivision", + ) + C5 = ( + "C5", + "An active incorporated place that is independent of any county subdivision and serves as " + "a county subdivision equivalent", + ) + C6 = ( + "C6", + "An active incorporated place that is coextensive with or approximates an Alaska Native " + "village statistical area", + ) + C7 = ("C7", "An incorporated place that is independent of any county") + C8 = ( + "C8", + "The balance of a consolidated city excluding the separately incorporated place(s) within " + "that consolidated government", + ) + C9 = ("C9", "An inactive or nonfunctioning incorporated place") + M2 = ("M2", "A military or other defense installation entirely within a place") + U1 = ("U1", "A census designated place with an official federally recognized name") + U2 = ("U2", "A census designated place without an official federally recognized name") + + +class Pcicbsa(str, DocumentedEnum): + """ + Values for PCICBSA. + + Current metropolitan or micropolitan statistical area principal city indicator + + The data's own metadata declares 2 legal value(s), of + which 0 do not occur in this extract. Members + below are the declared domain, so this enum is not narrowed to what one file + happened to contain. + """ + + N = ("N", "Geographic entity is not a principal city of a CBSA") + Y = ("Y", "Geographic entity is a principal city of a CBSA") + + +class Mtfcc(str, DocumentedEnum): + """ + Values for MTFCC. + + MAF/TIGER feature class code + + The data's own metadata declares 2 legal value(s), of + which 0 do not occur in this extract. Members + below are the declared domain, so this enum is not narrowed to what one file + happened to contain. + """ + + G4110 = ("G4110", "Incorporated place") + G4210 = ("G4210", "Census designated place") + + +class Funcstat(str, DocumentedEnum): + """ + Values for FUNCSTAT. + + Current functional status + + The data's own metadata declares 6 legal value(s), of + which 4 do not occur in this extract. Members + below are the declared domain, so this enum is not narrowed to what one file + happened to contain. + """ + + A = ("A", "Active government providing primary general-purpose functions") + B = ( + "B", + "Active government that is partially consolidated with another government but with " + "separate officials providing primary general-purpose functions", + ) + F = ("F", "Fictitious entity created to fill the Census Bureau geographic hierachy") + I = ( + "I", + "Inactive governemental unit that has the power to provide primary special-purpose " + "functions", + ) + N = ("N", "Nonfunctioning legal entity") + S = ("S", "Statistical entity") + + +class UtahPlace(Feature): + """ + TODO: what is a UtahPlace, and what does one row represent? + """ + + # Overture Feature + + geometry: Annotated[ + Geometry, + GeometryTypeConstraint( + GeometryType.POLYGON, GeometryType.MULTI_POLYGON + ), # TODO: confirm against the data + Field( + description="""TODO: describe the geometry. Source SRID 4269.""", + ), + ] + + # Optional + + statefp: Annotated[ + Statefp | None, + Field( + alias="STATEFP", + description=textwrap.dedent(""" + Current state Federal Information Processing Series (FIPS) code + """).strip(), + ), + ] = None + placefp: Annotated[ + str | None, + Field( + alias="PLACEFP", + description=textwrap.dedent(""" + Current place Federal Information Processing Series (FIPS) code + """).strip(), + ), + ] = None + placens: Annotated[ + str | None, + Field( + alias="PLACENS", + description=textwrap.dedent(""" + Current place GNIS code + """).strip(), + ), + ] = None + geoid: Annotated[ + str | None, + Field( + alias="GEOID", + description=textwrap.dedent(""" + Place identifier; a concatenation of Current state FIPS code and place FIPS + code + """).strip(), + ), + ] = None + geoidfq: Annotated[ + str | None, + Field( + alias="GEOIDFQ", + description=textwrap.dedent(""" + Fully qualified place identifier; a concatenation of census survey summary + level information with the place identifier. The GEOIDFQ attribute is + calculated to facilitate joining census spatial data to census survey + summary files. + """).strip(), + ), + ] = None + name: Annotated[ + str | None, + Field( + alias="NAME", + description=textwrap.dedent(""" + Current place name + """).strip(), + ), + ] = None + namelsad: Annotated[ + str | None, + Field( + alias="NAMELSAD", + description=textwrap.dedent(""" + Current name and the translated legal/statistical area description for place + """).strip(), + ), + ] = None + lsad: Annotated[ + Lsad | None, + Field( + alias="LSAD", + description=textwrap.dedent(""" + Current legal/statistical area description code for place + """).strip(), + ), + ] = None + classfp: Annotated[ + Classfp | None, + Field( + alias="CLASSFP", + description=textwrap.dedent(""" + Current Federal Information Processing Series (FIPS) class code + """).strip(), + ), + ] = None + pcicbsa: Annotated[ + Pcicbsa | None, + Field( + alias="PCICBSA", + description=textwrap.dedent(""" + Current metropolitan or micropolitan statistical area principal city + indicator + """).strip(), + ), + ] = None + mtfcc: Annotated[ + Mtfcc | None, + Field( + alias="MTFCC", + description=textwrap.dedent(""" + MAF/TIGER feature class code + """).strip(), + ), + ] = None + funcstat: Annotated[ + Funcstat | None, + Field( + alias="FUNCSTAT", + description=textwrap.dedent(""" + Current functional status + """).strip(), + ), + ] = None + aland: Annotated[ + int64 | None, + Field( + alias="ALAND", + description=textwrap.dedent(""" + Current land area (square meters) + """).strip(), + ), + ] = None + awater: Annotated[ + int64 | None, + Field( + alias="AWATER", + description=textwrap.dedent(""" + Current water area (square meters) + """).strip(), + ), + ] = None + intptlat: Annotated[ + str | None, + Field( + alias="INTPTLAT", + description=textwrap.dedent(""" + Current latitude of the internal point + """).strip(), + ), + ] = None + intptlon: Annotated[ + str | None, + Field( + alias="INTPTLON", + description=textwrap.dedent(""" + Current longitude of the internal point + """).strip(), + ), + ] = None diff --git a/schema-bootstrap/pyproject.toml b/schema-bootstrap/pyproject.toml new file mode 100644 index 0000000..ece121d --- /dev/null +++ b/schema-bootstrap/pyproject.toml @@ -0,0 +1,76 @@ +[project] +name = "schema-bootstrap" +version = "0.1.0" +description = "Bootstrap Overture-convention Pydantic models from a data file, seeding descriptions and enum domains from sidecar metadata" +readme = "README.md" +authors = [ + { name = "Seth Fitzsimmons", email = "seth@mojodna.net" } +] +requires-python = ">=3.12" +dependencies = [ + "duckdb>=1.5.5", +] + +[project.scripts] +schema-bootstrap = "schema_bootstrap.cli:main" + + +[build-system] +requires = ["uv_build>=0.11.32,<0.13"] +build-backend = "uv_build" + + +[dependency-groups] +dev = [ + "mypy>=2.3.1", + # Package-specific dev dependencies (if any) + # Tests import the generated model to validate the fixture's own rows against it. + "overture-schema>=2.0.0", + "pytest>=9.1.1", + "ruff>=0.16.6", +] + + + +# Per-package mypy configuration +[tool.mypy] +mypy_path = "$MYPY_CONFIG_FILE_DIR/src" +files = ["src", "tests"] +# Generated output is an artifact for reading, not code this project builds. +exclude = ["^examples/"] +python_version = "3.12" +strict = true + +[[tool.mypy.overrides]] +module = "tests.*" +disallow_untyped_defs = false +warn_return_any = false + + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "--strict-markers", + "--strict-config", + "-q", + "-ra", # show summary of all test outcomes +] +markers = [ + "slow: marks tests as slow", + "integration: marks tests as integration tests", +] + + +[tool.ruff] +line-length = 100 +src = ["src", "tests"] + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B", "SIM", "RUF"] + +[tool.ruff.lint.per-file-ignores] +# Generated output is formatted by ruff, not written to ruff's rules by hand. +"examples/*.py" = ["ALL"] diff --git a/schema-bootstrap/src/schema_bootstrap/__init__.py b/schema-bootstrap/src/schema_bootstrap/__init__.py new file mode 100644 index 0000000..b63da38 --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/__init__.py @@ -0,0 +1,10 @@ +"""Bootstrap Overture-convention Pydantic models from a data file. + +Writes the first draft of a model from the data and the metadata shipped with it. Anything +it cannot find a source for is left as a TODO for a person to fill in. +""" + +from .bootstrap import bootstrap, inspect_source +from .column import Column + +__all__ = ["Column", "bootstrap", "inspect_source"] diff --git a/schema-bootstrap/src/schema_bootstrap/bootstrap.py b/schema-bootstrap/src/schema_bootstrap/bootstrap.py new file mode 100644 index 0000000..9d0b7a0 --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/bootstrap.py @@ -0,0 +1,36 @@ +"""The pipeline: read the data, read what ships beside it, emit a model.""" + +from __future__ import annotations + +from pathlib import Path + +from .column import Column +from .introspect import describe, observe_values +from .render import render +from .sidecar import SidecarResult, apply_sidecar, resolve_meanings + + +def inspect_source(path: str | Path) -> tuple[list[Column], SidecarResult]: + """Everything known about a file's columns, and what the sidecar did and did not supply.""" + columns = describe(path) + observe_values(path, columns) + sidecar = apply_sidecar(columns, path) + resolve_meanings(columns) + return columns, sidecar + + +def bootstrap( + path: str | Path, + *, + class_name: str, +) -> str: + columns, sidecar = inspect_source(path) + source = Path(path).name + if sidecar.path is not None: + source = f"{source} (+ {sidecar.path.name})" + if sidecar.unmatched: + source += ( + f"; {len(sidecar.unmatched)} metadata attribute(s) matched no column " + f"and were NOT applied: {', '.join(sorted(sidecar.unmatched))}" + ) + return render(columns, class_name=class_name, source=source) diff --git a/schema-bootstrap/src/schema_bootstrap/cli.py b/schema-bootstrap/src/schema_bootstrap/cli.py new file mode 100644 index 0000000..cd7b048 --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/cli.py @@ -0,0 +1,102 @@ +"""Command-line interface for schema-bootstrap.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +from .bootstrap import bootstrap, inspect_source +from .tag_provider import check_package, render_entry_points, render_tag_provider + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + prog="schema-bootstrap", + description=( + "Bootstrap an Overture-convention Pydantic model from a data file. " + "Proposes; never decides -- everything it could not source is a TODO." + ), + ) + parser.add_argument("source", type=Path, help="Shapefile, Parquet, GeoPackage, or GeoJSON") + parser.add_argument("--class-name", help="Model class to emit, e.g. TigerPlace") + parser.add_argument("-o", "--output", type=Path, help="Write here instead of stdout") + parser.add_argument( + "--tag-provider", + metavar="PACKAGE", + help=( + "Also write tags.py beside --output, tagging models from PACKAGE so the tools " + "select them with --tag PACKAGE, and print the pyproject.toml entry points" + ), + ) + parser.add_argument( + "--report", + action="store_true", + help="Describe what was found and where it came from, instead of emitting a model", + ) + args = parser.parse_args(argv) + + if not args.source.exists(): + parser.error(f"no such file: {args.source}") + + if args.report: + columns, sidecar = inspect_source(args.source) + print(f"source: {args.source}") + print(f"sidecar: {sidecar.path or 'none found'}") + if sidecar.path is not None: + print( + f" {sidecar.matched} attribute(s) matched, " + f"{len(sidecar.unmatched)} unmatched" + ) + if sidecar.unmatched: + print( + f" UNMATCHED (described in metadata, applied to nothing): " + f"{', '.join(sorted(sidecar.unmatched))}" + ) + print(f"columns: {len(columns)}") + for column in columns: + bits = [column.py_type] + if column.is_geometry and column.srid: + bits.append(f"EPSG:{column.srid}") + if column.domain: + bits.append(f"domain={len(column.domain)}") + if column.observed_values: + bits.append(f"observed={len(column.observed_values)}") + if column.undeclared_values: + bits.append(f"UNDECLARED={','.join(column.undeclared_values)}") + if not column.description: + bits.append("no-description") + print(f" {column.name:<12} {' '.join(bits)}") + return 0 + + if not args.class_name: + parser.error("required unless --report: --class-name") + + tags_path = None + if args.tag_provider: + if not args.output: + parser.error("--tag-provider writes tags.py beside the model; it needs --output") + if problem := check_package(args.tag_provider): + parser.error(f"--tag-provider: {problem}") + if not args.output.stem.isidentifier(): + parser.error(f"--output: {args.output.stem!r} is not an importable module name") + tags_path = args.output.parent / "tags.py" + if tags_path.exists(): + parser.error(f"{tags_path} exists; not overwriting a provider that may be edited") + + source = bootstrap(args.source, class_name=args.class_name) + if args.output: + args.output.write_text(source) + print(f"wrote {args.output}", file=sys.stderr) + else: + sys.stdout.write(source) + + if tags_path is not None: + tags_path.write_text(render_tag_provider(args.tag_provider)) + print(f"wrote {tags_path}; add to pyproject.toml:", file=sys.stderr) + sys.stdout.write(render_entry_points(args.tag_provider, args.output.stem, args.class_name)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/schema-bootstrap/src/schema_bootstrap/column.py b/schema-bootstrap/src/schema_bootstrap/column.py new file mode 100644 index 0000000..1b7b120 --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/column.py @@ -0,0 +1,62 @@ +"""The shared vocabulary: what every stage of the pipeline knows about a column. + +`introspect` fills in what the data says, `sidecar` fills in what the metadata says, +and `render` writes it out. They agree on nothing else. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + + +@dataclass +class Column: + """One column, as progressively understood. + + `name`/`source_type` come from the data. `description`/`domain` come from metadata + beside it. `observed_values` comes from counting. Everything else is derived. + """ + + name: str + source_type: str + + py_type: str = "str" + is_geometry: bool = False + srid: int | None = None + + #: Distinct values present in THIS extract, when few enough to be a vocabulary. + observed_values: list[str] | None = None + + #: Values the data's own metadata declares legal. Wider than `observed_values`. + domain: dict[str, str] | None = None + + #: Meanings resolved for `observed_values`, from the domain or a fallback authority. + meanings: dict[str, str] = field(default_factory=dict) + + description: str | None = None + authority_url: str | None = None + notes: list[str] = field(default_factory=list) + + @property + def undeclared_values(self) -> list[str]: + """Values in the data that the declared domain does not cover. + + Not an error. A code can be real, in use, and absent from every authority -- + TIGER's `LSAD` 35 ("metro township") is missing from both the Census code list + and the shipped feature catalogue, and appears five times in Utah. + """ + if not self.observed_values or not self.domain: + return [] + return [v for v in self.observed_values if v not in self.domain] + + @property + def unobserved_values(self) -> list[str]: + """Declared values this extract happens not to contain.""" + if not self.domain: + return [] + observed = set(self.observed_values or ()) + return [v for v in self.domain if v not in observed] + + @property + def is_enum(self) -> bool: + return bool(self.observed_values) diff --git a/schema-bootstrap/src/schema_bootstrap/introspect.py b/schema-bootstrap/src/schema_bootstrap/introspect.py new file mode 100644 index 0000000..bbe6ccb --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/introspect.py @@ -0,0 +1,148 @@ +"""Read a data file's shape, using DuckDB as the reader for every format. + +Shapefiles go through `ST_Read`, which types the geometry column explicitly. Parquet +goes through the file directly, where a geometry column written as WKB is indistinguishable +from any other blob -- see `GEOMETRY_IN_BLOB_NOTE`. +""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + +import duckdb + +from .column import Column + +#: GDAL synthesizes this when reading OGR sources; it is not a column in the data. +SYNTHETIC_COLUMNS = frozenset({"OGC_FID"}) + +#: A column is proposed as an enum when its distinct count is at or under both bounds. +ENUM_MAX_RATIO = 0.1 +ENUM_MAX_VALUES = 60 + +GEOMETRY_IN_BLOB_NOTE = ( + "stored as a blob: if this is WKB, the type alone cannot say so. " + "Check the file's GeoParquet 'geo' metadata." +) + +_SCALARS = { + "BIGINT": "int64", + "INTEGER": "int32", + "SMALLINT": "int16", + "TINYINT": "int8", + "UBIGINT": "uint64", + "UINTEGER": "uint32", + "USMALLINT": "uint16", + "UTINYINT": "uint8", + "DOUBLE": "float64", + "FLOAT": "float32", + "REAL": "float32", + "VARCHAR": "str", + "BOOLEAN": "bool", + "DATE": "date", + "BLOB": "bytes", +} + +#: Types that come from `overture.schema.system.numeric` rather than builtins. +NUMERIC_TYPES = frozenset( + {"int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "float32", "float64"} +) + +_GEOMETRY_RE = re.compile(r"^GEOMETRY\s*\(", re.IGNORECASE) +_EPSG_RE = re.compile(r"^epsg\s*:\s*(\d+)$", re.IGNORECASE) + + +def _connect() -> duckdb.DuckDBPyConnection: + con = duckdb.connect() + con.execute("INSTALL spatial; LOAD spatial;") + return con + + +def source_expr(path: str | Path) -> str: + """How DuckDB should address this file.""" + p = str(path) + return f"ST_Read('{p}')" if p.lower().endswith((".shp", ".geojson", ".gpkg")) else f"'{p}'" + + +def srid_from_geometry(source_type: str) -> tuple[int | None, list[str]]: + """Recover an SRID from a `GEOMETRY(...)` type string. + + Two forms occur: `'epsg:4269'`, which DuckDB writes when reading a shapefile, and an + entire embedded PROJJSON document, which it writes into its own Parquet output. The + second is why this is a parser rather than a regex over the whole type string. + """ + if "(" not in source_type: + return None, [] + arg = source_type[source_type.index("(") + 1 : source_type.rindex(")")].strip().strip("'\"") + if not arg: + return None, [] + if m := _EPSG_RE.match(arg): + return int(m.group(1)), [] + if arg.lstrip().startswith("{"): + try: + doc = json.loads(arg) + except json.JSONDecodeError: + return None, ["geometry CRS argument looks like JSON but does not parse"] + code = (doc.get("id") or {}).get("code") + if isinstance(code, int): + return code, [] + if isinstance(code, str) and code.isdigit(): + return int(code), [] + return None, ["embedded PROJJSON carries no id.code"] + if arg.isdigit(): + return int(arg), [] + return None, [f"unrecognized CRS argument {arg[:40]!r}"] + + +def describe(path: str | Path) -> list[Column]: + """One `Column` per column in the file, typed but not yet explained.""" + con = _connect() + rows = con.execute(f"DESCRIBE SELECT * FROM {source_expr(path)}").fetchall() + columns: list[Column] = [] + for name, source_type, *_ in rows: + if name in SYNTHETIC_COLUMNS: + continue + column = Column(name=name, source_type=source_type) + if _GEOMETRY_RE.match(source_type): + column.is_geometry = True + column.py_type = "Geometry" + column.srid, column.notes = srid_from_geometry(source_type) + elif source_type.upper() == "BLOB": + column.py_type = "bytes" + column.notes.append(GEOMETRY_IN_BLOB_NOTE) + else: + column.py_type = _SCALARS.get(source_type.upper(), "str") + if source_type.upper() not in _SCALARS: + column.notes.append(f"unmapped source type {source_type!r}; defaulted to str") + columns.append(column) + return columns + + +def observe_values(path: str | Path, columns: list[Column]) -> None: + """Fill in `observed_values` for columns whose distinct count reads as a vocabulary. + + This finds the values *present*, which is a lower bound on the values *legal*. A + declared domain from `sidecar` is the wider and better source where one exists. + """ + con = _connect() + src = source_expr(path) + (nrows,) = con.execute(f"SELECT count(*) FROM {src}").fetchone() or (0,) + if not nrows: + return + ceiling = min(ENUM_MAX_VALUES, max(1, int(nrows * ENUM_MAX_RATIO))) + for column in columns: + if column.is_geometry or column.py_type not in ("str", "int32", "int64"): + continue + (approx,) = con.execute( + f'SELECT approx_count_distinct("{column.name}") FROM {src}' + ).fetchone() or (0,) + if approx > ceiling: + continue + rows = con.execute( + f'SELECT DISTINCT "{column.name}" FROM {src} ' + f'WHERE "{column.name}" IS NOT NULL ORDER BY 1' + ).fetchall() + if len(rows) <= ceiling: + column.observed_values = [str(r[0]) for r in rows] diff --git a/schema-bootstrap/src/schema_bootstrap/py.typed b/schema-bootstrap/src/schema_bootstrap/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/schema-bootstrap/src/schema_bootstrap/render.py b/schema-bootstrap/src/schema_bootstrap/render.py new file mode 100644 index 0000000..fdf7f19 --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/render.py @@ -0,0 +1,266 @@ +"""Emit Pydantic source in Overture's own idiom. + +Models subclass `overture.schema.system.feature.Feature`, the base for third-party +models. The theme/type generics in `overture.schema.common` are Overture's contract with +its own themes; a model from elsewhere is selected through a tag provider instead. + +Imports `Geometry`/`GeometryType`/`GeometryTypeConstraint` from +`overture.schema.system.geometric` and the width-typed numerics from +`overture.schema.system.numeric` rather than redeclaring shims, so the output is a model +someone can extend rather than a lookalike. + +Proposes; never decides. Anything the pipeline could not source is a visible TODO. +""" + +from __future__ import annotations + +import re +import shutil +import subprocess +import textwrap + +from .column import Column +from .introspect import NUMERIC_TYPES + +_DEFAULT_GEOMETRY_TYPES = ("POLYGON", "MULTI_POLYGON") + + +def member_name(value: str) -> str: + """A Python enum member name for an arbitrary code value.""" + cleaned = re.sub(r"[^0-9A-Za-z]+", "_", value).strip("_").upper() + if not cleaned: + return "UNNAMED" + return f"V_{cleaned}" if cleaned[0].isdigit() else cleaned + + +def enum_class_name(column_name: str) -> str: + parts = [p for p in re.split(r"[^0-9A-Za-z]+", column_name.lower()) if p] + return "".join(p.capitalize() for p in parts) or "Value" + + +def _imports(columns: list[Column]) -> list[str]: + has_geometry = any(c.is_geometry for c in columns) + numerics = sorted({c.py_type for c in columns if c.py_type in NUMERIC_TYPES}) + + out = [ + "import textwrap", + "from typing import Annotated", + "", + "from pydantic import Field", + "", + "from overture.schema.system.feature import Feature", + ] + if any(c.is_enum for c in columns): + out.append("from overture.schema.system.doc import DocumentedEnum") + if has_geometry: + out += [ + "from overture.schema.system.geometric import (", + " Geometry,", + " GeometryType,", + " GeometryTypeConstraint,", + ")", + ] + if numerics: + out += ["from overture.schema.system.numeric import ("] + out += [f" {n}," for n in numerics] + out += [")"] + return out + + +def _enum_docstring(column: Column) -> list[str]: + out = [' """', f" Values for {column.name}."] + if column.description: + out += ["", *(f" {line}" for line in textwrap.wrap(column.description, 84))] + + if column.domain: + out += [ + "", + f" The data's own metadata declares {len(column.domain)} legal value(s), of", + f" which {len(column.unobserved_values)} do not occur in this extract. Members", + " below are the declared domain, so this enum is not narrowed to what one file", + " happened to contain.", + ] + else: + n = len(column.observed_values or ()) + out += [ + "", + f" WARNING: no declared domain was found, so these {n} value(s) are only what is", + " PRESENT IN THIS EXTRACT. That is a lower bound on the vocabulary, not the", + " vocabulary. Confirm against the authority before treating this as closed.", + ] + + undeclared = column.undeclared_values + if undeclared: + out += [ + "", + f" TODO: {len(undeclared)} value(s) occur in the data and are NOT declared:", + f" {', '.join(repr(v) for v in undeclared)}. An undeclared code is not an invalid", + " one -- check a sibling column before assuming a defect in the data.", + ] + if column.authority_url: + out += ["", f" Authority: {column.authority_url}"] + out.append(' """') + return out + + +def _enum_members(column: Column) -> list[str]: + """Members as `DocumentedEnum` pairs, so each value's meaning is data, not a comment. + + A member with no sourced meaning still gets one: the raw value, marked TODO. That is + placeholder documentation rather than an invented fact -- it says only what the data + already says -- and it keeps every member the same shape, so filling one in is an edit + rather than a restructure. + """ + # Prefer the declared domain; fall back to what was observed. + values = list(column.domain) if column.domain else list(column.observed_values or ()) + for v in column.observed_values or (): + if v not in values: + values.append(v) + + out: list[str] = [] + for value in values: + meaning = column.meanings.get(value) or (column.domain or {}).get(value) + doc = meaning if meaning else f"TODO: {value!r} -- meaning not in any source consulted" + # No trailing comma: ruff's magic-trailing-comma rule would explode every + # member onto four lines, including the short ones. Emitted as one logical + # expression, ruff collapses what fits and splits what does not. + literal = " ".join(_string_literal(doc)) + out.append(f' {member_name(value)} = ("{_escape(value)}", {literal})') + return out + + +def _escape(text: str) -> str: + return text.replace("\\", "\\\\").replace('"', '\\"') + + +def _string_literal(text: str, width: int = 88) -> list[str]: + """One or more `"..."` literals, implicitly concatenated when the text is long. + + ruff formats code; it does not break string literals, and truncating a sourced + definition to fit a line would discard the only thing that made it worth reading. + Splitting across adjacent literals keeps the whole text and lets ruff own the layout. + """ + escaped = _escape(text) + if len(escaped) <= width: + return [f'"{escaped}"'] + pieces = textwrap.wrap(escaped, width, break_long_words=False, break_on_hyphens=False) + return [f'"{piece} "' for piece in pieces[:-1]] + [f'"{pieces[-1]}"'] + + +def field_name(column_name: str) -> str: + """The Python attribute name for a column.""" + return column_name.lower() + + +def _field(column: Column, enum_names: dict[str, str]) -> list[str]: + """One optional field, aliased to the column whenever the Python name differs. + + Without the alias the model still validates the source -- Feature ignores extra keys + and every field defaults to None -- so `LSAD` would be dropped and `lsad` left empty + with no error. The alias is also the name Overture's tools emit: JSON Schema, docs, + and Spark all use it. + """ + annotation = enum_names.get(column.name, column.py_type) + name = field_name(column.name) + out = [ + f" {name}: Annotated[", + f" {annotation} | None,", + " Field(", + ] + if name != column.name: + out.append(f' alias="{_escape(column.name)}",') + out.append(' description=textwrap.dedent("""') + if column.description: + out += [f" {line}" for line in textwrap.wrap(column.description, 76)] + else: + out.append(f" TODO: what is {column.name}, and why does it exist?") + out += [f" NOTE: {n}" for n in column.notes] + out += [' """).strip()', " ),", " ] = None"] + return out + + +def render( + columns: list[Column], + *, + class_name: str, + source: str, +) -> str: + enums = [c for c in columns if c.is_enum] + enum_names = {c.name: enum_class_name(c.name) for c in enums} + geometry = next((c for c in columns if c.is_geometry), None) + + out: list[str] = [ + '"""', + *textwrap.wrap(f"Bootstrapped from {source}.", 94), + "", + "NOT FINISHED. Types and value vocabularies were read off the data and its metadata;", + "meanings were taken from the metadata where it had them. Every TODO below is a", + "judgment the sources could not make.", + '"""', + "", + *_imports(columns), + "", + "", + ] + + for column in enums: + out.append(f"class {enum_names[column.name]}(str, DocumentedEnum):") + out += _enum_docstring(column) + out.append("") + out += _enum_members(column) + out += ["", ""] + + out += [ + f"class {class_name}(Feature):", + ' """', + f" TODO: what is a {class_name}, and what does one row represent?", + ' """', + "", + ] + + if geometry is not None: + constraint = ", ".join(f"GeometryType.{g}" for g in _DEFAULT_GEOMETRY_TYPES) + srid = f" Source SRID {geometry.srid}." if geometry.srid else "" + description = geometry.description or "TODO: describe the geometry." + out += [ + " # Overture Feature", + "", + " geometry: Annotated[", + " Geometry,", + f" GeometryTypeConstraint({constraint}), # TODO: confirm against the data", + " Field(", + f' description="""{description}{srid}""",', + " ),", + " ]", + "", + ] + + out += [" # Optional", ""] + for column in columns: + if column.is_geometry: + continue + out += _field(column, enum_names) + return _format("\n".join(out) + "\n") + + +def _format(source: str) -> str: + """Hand the generated module to ruff. + + Line length and import order are ruff's job, not something to hand-manage while + building strings. Degrades to unformatted output when ruff is not on PATH -- the + module is valid Python either way. + """ + ruff = shutil.which("ruff") + if ruff is None: + return source + try: + result = subprocess.run( + [ruff, "format", "--stdin-filename", "generated.py", "-"], + input=source, + capture_output=True, + text=True, + check=True, + ) + except (subprocess.CalledProcessError, OSError): + return source + return result.stdout diff --git a/schema-bootstrap/src/schema_bootstrap/sidecar.py b/schema-bootstrap/src/schema_bootstrap/sidecar.py new file mode 100644 index 0000000..298745e --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/sidecar.py @@ -0,0 +1,158 @@ +"""Read column meanings from metadata shipped beside the data. + +Two encodings are read, because between them they cover most published government GIS: + +* **ISO 19110** feature catalogue (`.shp.ea.iso.xml`), the `gfc:` namespace. Carries + a definition per attribute and, for coded attributes, the declared domain as + label/definition pairs. +* **FGDC CSDGM** (`.shp.xml`), the older US standard. Its Entity and Attribute + section carries the same two things under different element names. + +A declared domain beats a `DISTINCT` pass: it is versioned with the extract, needs no +network, and is *wider* than the extract, so it does not mistake "absent here" for +"illegal". What no sidecar supplies is why the column exists -- that remains a person's +job, which is the point of the TODOs `render` leaves behind. +""" + +from __future__ import annotations + +import re +from dataclasses import dataclass +from dataclasses import field as dc_field +from pathlib import Path +from xml.etree import ElementTree + +from .column import Column + +#: Names a metadata document uses for the geometry attribute. The reader names the +#: column after the file (`geom`, `SHAPE`, `wkb_geometry`, ...), and the two rarely +#: agree -- FGDC calls it SHAPE while DuckDB's ST_Read calls it geom, so the geometry +#: description is otherwise lost to a name mismatch rather than to absence. +GEOMETRY_ALIASES = ("SHAPE", "GEOMETRY", "GEOM", "THE_GEOM", "WKB_GEOMETRY", "SHAPE_") + +_ISO_NS = { + "gfc": "http://www.isotc211.org/2005/gfc", + "gco": "http://www.isotc211.org/2005/gco", +} + + +def _clean(text: str | None) -> str: + return re.sub(r"\s+", " ", text or "").strip() + + +def candidate_paths(data_path: str | Path) -> list[Path]: + """Where a sidecar for this data file would live, most specific first.""" + p = Path(data_path) + return [ + Path(f"{p}.ea.iso.xml"), # ISO 19110 feature catalogue (TIGER) + p.with_suffix(".ea.iso.xml"), + Path(f"{p}.xml"), # FGDC CSDGM, or ISO alongside + p.with_suffix(".xml"), + p.with_suffix(".shp.xml"), + ] + + +def _read_iso_19110(root: ElementTree.Element) -> dict[str, tuple[str, dict[str, str]]]: + out: dict[str, tuple[str, dict[str, str]]] = {} + for attr in root.iterfind(".//gfc:FC_FeatureAttribute", _ISO_NS): + name = _clean(attr.findtext("gfc:memberName/gco:LocalName", None, _ISO_NS)) + if not name: + continue + definition = _clean(attr.findtext("gfc:definition/gco:CharacterString", None, _ISO_NS)) + domain: dict[str, str] = {} + for listed in attr.iterfind("gfc:listedValue/gfc:FC_ListedValue", _ISO_NS): + # gfc 1.1 gives FC_ListedValue both `label` and `code`. TIGER populates only + # `label`; other producers use `code`. Take whichever is there. + label = _clean( + listed.findtext("gfc:label/gco:CharacterString", None, _ISO_NS) + or listed.findtext("gfc:code/gco:CharacterString", None, _ISO_NS) + ) + meaning = _clean(listed.findtext("gfc:definition/gco:CharacterString", None, _ISO_NS)) + # A listed value with no label is a prose citation of an external register + # (TIGER does this for FIPS and GNIS), not an enumerable value. + if label and meaning: + domain[label] = meaning + out[name] = (definition, domain) + return out + + +def _read_fgdc(root: ElementTree.Element) -> dict[str, tuple[str, dict[str, str]]]: + out: dict[str, tuple[str, dict[str, str]]] = {} + for attr in root.iterfind(".//detailed/attr"): + name = _clean(attr.findtext("attrlabl")) + if not name: + continue + definition = _clean(attr.findtext("attrdef")) + domain: dict[str, str] = {} + for edom in attr.iterfind(".//edom"): + label = _clean(edom.findtext("edomv")) + meaning = _clean(edom.findtext("edomvd")) + if label and meaning: + domain[label] = meaning + out[name] = (definition, domain) + return out + + +@dataclass +class SidecarResult: + """What a sidecar supplied, and what it offered that nothing claimed. + + `unmatched` is the load-bearing field. A metadata attribute whose name matches no + column is dropped, and a dropped attribute is indistinguishable from an absent one + unless it is reported: the model still generates, still imports, and is merely + emptier. DBF truncates names to ten characters, publishers rename between metadata + and data, and the geometry attribute is named differently by almost everyone. + """ + + path: Path | None = None + matched: int = 0 + unmatched: list[str] = dc_field(default_factory=list) + + def __bool__(self) -> bool: + return self.path is not None + + +def apply_sidecar(columns: list[Column], data_path: str | Path) -> SidecarResult: + """Attach descriptions and declared domains, reporting what could not be matched.""" + for path in candidate_paths(data_path): + if not path.exists(): + continue + try: + root = ElementTree.parse(path).getroot() + except ElementTree.ParseError: + continue + found = _read_iso_19110(root) or _read_fgdc(root) + if not found: + continue + by_name = {c.name: c for c in columns} + result = SidecarResult(path=path) + for name, (definition, domain) in found.items(): + column = by_name.get(name) or _geometry_match(name, columns) + if column is None: + result.unmatched.append(name) + continue + result.matched += 1 + if definition and not column.description: + column.description = definition + if domain: + column.domain = domain + return result + return SidecarResult() + + +def _geometry_match(name: str, columns: list[Column]) -> Column | None: + """Match a metadata attribute naming the geometry to whatever the reader called it.""" + if name.upper() not in GEOMETRY_ALIASES: + return None + geometries = [c for c in columns if c.is_geometry] + return geometries[0] if len(geometries) == 1 else None + + +def resolve_meanings(columns: list[Column]) -> None: + """Fill `meanings` for observed values, preferring the declared domain.""" + for column in columns: + if not column.observed_values or not column.domain: + continue + for value in column.observed_values: + if value in column.domain: + column.meanings.setdefault(value, column.domain[value]) diff --git a/schema-bootstrap/src/schema_bootstrap/tag_provider.py b/schema-bootstrap/src/schema_bootstrap/tag_provider.py new file mode 100644 index 0000000..8c30cbf --- /dev/null +++ b/schema-bootstrap/src/schema_bootstrap/tag_provider.py @@ -0,0 +1,61 @@ +"""Emit the tag provider that lets Overture's tools select a generated model. + +A `Feature` subclass carries no theme or type, so `--theme`/`--type` cannot find it; a +tag provider registered under `overture.tag_providers` tags it instead, and the tools +select it with `--tag `. Shape follows the workshop's my-schema template: +`tags.py` holds the provider, and `pyproject.toml` registers it beside the model. +""" + +from __future__ import annotations + +import re + +# Discovery drops these with a warning unless an Overture package sets them. +_RESERVED_TAGS = frozenset({"feature", "overture"}) +_PACKAGE = re.compile(r"[a-z][a-z0-9_]*") + + +def check_package(package: str) -> str | None: + """Why `package` cannot be both an import name and a tag, or None if it can.""" + if not _PACKAGE.fullmatch(package): + return f"{package!r} must be lowercase letters, digits and underscores" + if package in _RESERVED_TAGS: + return f"{package!r} is a tag Overture reserves for its own packages" + return None + + +def provider_name(package: str) -> str: + return f"{package}_provider" + + +def render_tag_provider(package: str) -> str: + return f'''"""Tag this package's models so the tools can select them with `--tag {package}`.""" + +from collections.abc import Iterable + +from pydantic import BaseModel + +from overture.schema.system.discovery import ModelKey + + +def {provider_name(package)}( + types: Iterable[type[BaseModel]], key: ModelKey, tags: set[str] +) -> set[str]: + """Add `{package}` to every model registered from this package.""" + module = key.entry_point.partition(":")[0] + if module == "{package}" or module.startswith("{package}."): + tags.add("{package}") + return tags +''' + + +def render_entry_points(package: str, module: str, class_name: str) -> str: + """The `pyproject.toml` tables that register the model and its tag provider.""" + model_key = re.sub(r"(?USDA Forest ServiceAutomated Lands Program (ALP)2025-06-29T09:38:18074913Administrative Forestvector digital datahttp://data.fs.usda.gov/geodata/edw/datasets.phpAn area encompassing all the National Forest System lands administered by an administrative unit. The area encompasses private lands, other governmental agency lands, and may contain National Forest System lands within the proclaimed boundaries of another administrative unit. All National Forest System lands fall within one and only one Administrative Forest Area.This data is intended for read-only use. These data were prepared to describe Forest Service administrative area boundaries. The purpose of the data is to provide display, identification, and analysis tools for determining current boundary information for Forest Service managers, GIS Specialists, and others.The Forest Service has multiple types of boundaries represented by different feature classes (layers): Administrative, Ownership and Proclaimed. + ADMINISTRATIVE boundaries (e.g. AdministrativeForest and RangerDistrict feature classes) encompass National Forest System lands managed by an administrative unit. These are dynamic layers that should not be considered "legal" boundaries as they are simply intended to identify the specific organizational units that administer areas. As lands are acquired and disposed, the administrative boundaries are adjusted to expand or shrink accordingly. Please note that ranger districts are sub units of National Forests. An administrative forest boundary can contain one or more Proclaimed National Forests, National Grasslands, Purchase Units, Research and Experimental Areas, Land Utilization Projects and various "Other" Areas. If needed, OWNERSHIP boundaries (e.g. BasicOwnership and SurfaceOwnership feature classes) should be reviewed along with these datasets to determine parcels that are federally managed within the administrative boundaries. + OWNERSHIP boundaries (e.g. BasicOwnership and SurfaceOwnership feature classes) represent parcels that are tied to legal transactions of ownership. These are parcels of Federal land managed by the USDA Forest Service. Please note that the BasicOwnership layer is simply a dissolved version of the SurfaceOwnership layer. + PROCLAIMED boundaries (e.g. ProclaimedForest and ProclaimedForest_Grassland) encompass areas of National Forest System land that is set aside and reserved from public domain by executive order or proclamation. Please note that the ProclaimedForest layer contains only proclaimed forests while ProclaimedForest_Grassland layer contains both proclaimed forests and proclaimed grasslands. For boundaries that reflect current National Forest System lands managed by an administrative unit, see the ADMINISTRATIVE boundaries (AdministrativeForest and RangerDistrict feature classes). + For a visual comparison of the different kinds of USFS boundary datasets maintained by the USFS, see the Forest Service Boundary Comparison map at https://usfs.maps.arcgis.com/apps/CompareAnalysis/index.html?appid=fe7b9f56217949a291356f08cfccb119. + USFS boundaries are often referenced in national datasets maintained by other federal agencies. Please note that variations may be found between USFS data and other boundary datasets due to differing update frequencies. PAD-US (Protected Areas Database of the United States), maintained by the U.S. Geological Survey, is a "best available" inventory of protected areas including data provided by managing agencies and organizations including the Forest Service. For more information see https://gapanalysis.usgs.gov/padus/data/metadata/. SMA (Surface Management Agency), maintained by the Bureau of Land Management, depicts Federal land for the United States and classifies this land by its active Federal surface managing agency. It uses data provided by the Forest Service and other agencies, combined with National Regional Offices collection efforts. For more information see https://landscape.blm.gov/geoportal/catalog/search/resource/details.page?uuid=%7B2A8B8906-7711-4AF7-9510-C6C7FD991177%7D.20150826publication dateIn workWeekly-150.007928-64.73432961.51899217.738983NoneForest Service Land DatasetRegionForest NumberLand StatusUSDA Forest ServiceAdministrative ForestForest NameNFS LandsForest Service Lands ProgramISO 19115 Topic CategoriesboundariesNoneThe USDA Forest Service makes no warranty, expressed or implied, including the warranties of merchantability and fitness for a particular purpose, nor assumes any legal liability or responsibility for the accuracy, reliability, completeness or utility of these geospatial data, or for the improper or incorrect use of these geospatial data. These geospatial data and related maps or graphics are not legal documents and are not intended to be used as such. The data and maps may not be used to determine title, ownership, legal descriptions or boundaries, legal jurisdiction, or restrictions that may be in place on either public or private land. Natural hazards may or may not be depicted on the data and maps, and land users should exercise due caution. The data are dynamic and may change over time. The user is responsible to verify the limitations of the geospatial data and to use the data accordingly.USFS Chief Information Office, Enterprise Data WarehousephysicalWashingtonDC20250USPlease send an e-mail to the address below.data@fs.fed.us Version 6.2 (Build 9200) ; Esri ArcGIS 10.5.1.7333USDA Forest Service Automated Lands Program (ALP)2025-06-29T09:38:18Automated Lands Program (ALP)The Regional Land Status personnel are responsible for their local data stewardship and maintenance. The overall collection nationwide consists of 100% attribute accuracy based on currentness and completeness.None.This theme is current to date of publication.All features integrate vertically to the source data with the highest level of accuracy (usually survey layers of townships, sections, etc.). Features that share those highly accurate boundaries are vertically integrated. Legal descriptions found in proclamations, legal documents, etc. are used in places where the features do not share boundaries with data which has the highest level of accuracy. Generally the source information used to record spatial locations are from: 1) on-screen digitizing with a georeferenced topographic or image background; or 2) GPS field collected positions.USFS2025-06-29T09:38:18074913Administrative Unit24000onLine20150101publication dateAdministrative Units from USFS.Regional data utilizing subtypes.Data was merged from a regional infrastructure, utilizing subtypes, into specific national datasets. This data was published using Feature Manipulation Engine Software. The spatial features were extracted from the transactional database, manipulated to meet the desired output format and published to the target feature class.20150101VectorSimpleFALSE0FALSEFALSE8.9900000000000004e-0088.9900000000000004e-008Decimal DegreesD North American 1983GRS 19806378137.0298.257222101AdministrativeForestFeature Class: A collection of geographic features with the same geometry type (such as point, line, or polygon), the same attributes, and the same spatial reference.Esri GIS Data DictionaryFeature Class0FIDFIDOID400Internal feature number.EsriSequential unique whole numbers that are automatically generated.REGIONU.S. Forest Service Region, consisting of Region 01 (Northern Region), Region 02 (Rocky Mountain Region), Region 03 (Southwestern Region), Region 04 (Intermountain Region), Region 05 (Pacific Southwest Region), Region 06 (Pacific Northwest Region), Region 08 (Southern Region), Region 09 (Eastern Region), and Region 10 (Alaska Region). There is no Region 07. Regions are listed on https://www.fs.usda.gov/organization. Within this dataset, the region represents the Land Status Record System Production Regional schema from which data was derived.USFS01Region 01 - NorthernU.S. Forest Service02Region 02 - Rocky MountainU.S. Forest Service03Region 03 - SouthwesternU.S. Forest Service04Region 04 - IntermountainU.S. Forest Service05Region 05 - Pacific SouthwestU.S. Forest Service06Region 06 - Pacific NorthwestU.S. Forest Service08Region 08 - SouthernU.S. Forest Service09Region 09 - EasternU.S. Forest Service10Region 10 - AlaskaU.S. Forest ServiceREGIONString200ADMINFORESADMINFORESString4000FORESTNAMEThe official name of the National Forest (or other Administrative Area).USDA Forest ServiceThe official name of the National Forest (or other Administrative Area).FORESTNAMEString15000FORESTNUMBFORESTNUMBString200FORESTORGCFORESTORGCString400GIS_ACRESThe area of the feature in acres, calculated using a projected surface computation. A NAD 83, USA Contiguous Albers Equal Area Conic projection was used for Regions 1-9, and NAD 83, Alaska Albers for Region 10.USFSThe area of the feature in acres, calculated using a projected surface computation. A NAD 83, USA Contiguous Albers Equal Area Conic projection was used for Regions 1-9, and NAD 83, Alaska Albers for Region 10.GIS_ACRESDouble19188SHAPEFeature geometry.ESRIFeature geometry.ShapeGeometry000SHAPE_AREASHAPE_AREADouble1900Area of feature in internal units squared.EsriPositive real numbers that are automatically generated.SHAPE_LENSHAPE_LENDouble1900USFS Chief Information Office, Enterprise Data WarehousephysicalWashingtonDC20250Please send an e-mail to the address below.data@fs.fed.usDownloadable data.The U.S. Forest Service makes no warranty, express or implied, nor assumes any liability or responsibility for the accuracy, reliability, completeness, or utility of these geospatial data or for the improper or incorrect use of those data. The data are dynamic and may change over time. The user is responsible for verifying the limitations of the geospatial data and for using the data accordingly.20190318USFS Chief Information Office, Enterprise Data WarehousephysicalWashingtonDC20250Please send an e-mail to the address below.data@fs.fed.usFGDC Content Standard for Digital Geospatial MetadataFGDC-STD-001-1998local timeNoneUnclassifiedNoneUSFS Chief Information Office, Enterprise Data WarehousePlease send an e-mail to the address below.WashingtonDC20250SM.FS.data@usda.gov20250706ArcGIS Metadata1.0USFS Chief Information Office, Enterprise Data WarehousePlease send an e-mail to the address below.WashingtonDC20250SM.FS.data@usda.govDownloadable data.http://data.fs.usda.gov/geodata/edw/datasets.php0.000ShapefileAdministrative Forest2025-06-29T09:38:18Automated Lands Program (ALP)USDA Forest Servicevector digital dataLink to map service: https://apps.fs.usda.gov/arcx/rest/services/EDW/EDW_ForestSystemBoundaries_01/MapServer<DIV STYLE="text-align:Left;"><DIV><DIV><P><SPAN>An area encompassing all the National Forest System lands administered by an administrative unit. The area encompasses private lands, other governmental agency lands, and may contain National Forest System lands within the proclaimed boundaries of another administrative unit. All National Forest System lands fall within one and only one Administrative Forest Area.</SPAN></P></DIV></DIV></DIV>This data is intended for read-only use. These data were prepared to describe Forest Service administrative area boundaries. The purpose of the data is to provide display, identification, and analysis tools for determining current boundary information for Forest Service managers, GIS Specialists, and others.USFS Chief Information Office, Enterprise Data WarehousePlease send an e-mail to the address below.WashingtonDC20250USSM.FS.data@usda.govRegionLand StatusNFS LandsForest NumberForest Service Land DatasetForest Service Lands ProgramAdministrative ForestForest NameUSDA Forest ServiceISO 19115 Topic CategoriesboundariesRegionLand StatusNFS LandsForest NumberboundariesForest Service Land DatasetForest Service Lands ProgramAdministrative ForestForest NameUSDA Forest ServiceThe U.S. Forest Service makes no warranty, express or implied, nor assumes any liability or responsibility for the accuracy, reliability, completeness, or utility of these geospatial data or for the improper or incorrect use of those data. The data are dynamic and may change over time. The user is responsible for verifying the limitations of the geospatial data and for using the data accordingly.<DIV STYLE="text-align:Left;"><DIV><DIV><P><SPAN>The USDA Forest Service makes no warranty, expressed or implied, including the warranties of merchantability and fitness for a particular purpose, nor assumes any legal liability or responsibility for the accuracy, reliability, completeness or utility of these geospatial data, or for the improper or incorrect use of these geospatial data. These geospatial data and related maps or graphics are not legal documents and are not intended to be used as such. The data and maps may not be used to determine title, ownership, legal descriptions or boundaries, legal jurisdiction, or restrictions that may be in place on either public or private land. Natural hazards may or may not be depicted on the data and maps, and land users should exercise due caution. The data are dynamic and may change over time. The user is responsible to verify the limitations of the geospatial data and to use the data accordingly.</SPAN></P></DIV></DIV></DIV>Automated Lands Program (ALP)USDA Forest Service Automated Lands Program (ALP)Microsoft Windows Server 2016 Technical Preview Version 10.0 (Build 17763) ; Esri ArcGIS 13.3.0.52636publication date2015-08-26-150.007928-64.73432917.73898361.518992The Forest Service has multiple types of boundaries represented by different feature classes (layers): Administrative, Ownership and Proclaimed. + +1) ADMINISTRATIVE boundaries (e.g. AdministrativeForest and RangerDistrict feature classes) encompass National Forest System lands managed by an administrative unit. These are dynamic layers that should not be considered "legal" boundaries as they are simply intended to identify the specific organizational units that administer areas. As lands are acquired and disposed, the administrative boundaries are adjusted to expand or shrink accordingly. Please note that ranger districts are sub units of National Forests. An administrative forest boundary can contain one or more Proclaimed National Forests, National Grasslands, Purchase Units, Research and Experimental Areas, Land Utilization Projects and various "Other" Areas. If needed, OWNERSHIP boundaries (e.g. BasicOwnership and SurfaceOwnership feature classes) should be reviewed along with these datasets to determine parcels that are federally managed within the administrative boundaries. + +2) OWNERSHIP boundaries (e.g. BasicOwnership and SurfaceOwnership feature classes) represent parcels that are tied to legal transactions of ownership. These are parcels of Federal land managed by the USDA Forest Service. Please note that the BasicOwnership layer is simply a dissolved version of the SurfaceOwnership layer. + +3) PROCLAIMED boundaries (e.g. ProclaimedForest and ProclaimedForest_Grassland) encompass areas of National Forest System land that is set aside and reserved from public domain by executive order or proclamation. Please note that the ProclaimedForest layer contains only proclaimed forests while ProclaimedForest_Grassland layer contains both proclaimed forests and proclaimed grasslands. For boundaries that reflect current National Forest System lands managed by an administrative unit, see the ADMINISTRATIVE boundaries (AdministrativeForest and RangerDistrict feature classes). + +For a visual comparison of the different kinds of USFS boundary datasets maintained by the USFS, see the Forest Service Boundary Comparison map at https://usfs.maps.arcgis.com/apps/CompareAnalysis/index.html?appid=fe7b9f56217949a291356f08cfccb119. + +USFS boundaries are often referenced in national datasets maintained by other federal agencies. Please note that variations may be found between USFS data and other boundary datasets due to differing update frequencies. PAD-US (Protected Areas Database of the United States), maintained by the U.S. Geological Survey, is a "best available" inventory of protected areas including data provided by managing agencies and organizations including the Forest Service. For more information see https://gapanalysis.usgs.gov/padus/data/metadata/. SMA (Surface Management Agency), maintained by the Bureau of Land Management, depicts Federal land for the United States and classifies this land by its active Federal surface managing agency. It uses data provided by the Forest Service and other agencies, combined with National Regional Offices collection efforts. For more information see https://landscape.blm.gov/geoportal/catalog/search/resource/details.page?uuid=%7B2A8B8906-7711-4AF7-9510-C6C7FD991177%7D.1-150.007928-64.73432961.51899217.738983USANoneNoneNone.This theme is current to date of publication.The Regional Land Status personnel are responsible for their local data stewardship and maintenance. The overall collection nationwide consists of 100% attribute accuracy based on currentness and completeness.All features integrate vertically to the source data with the highest level of accuracy (usually survey layers of townships, sections, etc.). Features that share those highly accurate boundaries are vertically integrated. Legal descriptions found in proclamations, legal documents, etc. are used in places where the features do not share boundaries with data which has the highest level of accuracy. Generally the source information used to record spatial locations are from: 1) on-screen digitizing with a georeferenced topographic or image background; or 2) GPS field collected positions.Regional data utilizing subtypes.24000Administrative UnitAdministrative Units from USFS.2025-06-29T09:38:18USFSpublication date2015-01-01Data was merged from a regional infrastructure, utilizing subtypes, into specific national datasets. This data was published using Feature Manipulation Engine Software. The spatial features were extracted from the transactional database, manipulated to meet the desired output format and published to the target feature class.2015-01-01020190326152037001.0500005000AdministrativeForest-150.007928-64.73432917.73898361.5189921file://\\Usda.net\fs\FS\BusOps\CIO\Project\EDW\FME_Work\data.fs.usda.gov\deploy_datasets\out\temp\S_USA.AdministrativeForest\AdministrativeForest.shpLocal Area Network0.000GeographicGCS_North_American_1983Angular Unit: Degree (0.017453)<GeographicCoordinateSystem xsi:type='typens:GeographicCoordinateSystem' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:typens='http://www.esri.com/schemas/ArcGIS/3.3.0'><WKT>GEOGCS[&quot;GCS_North_American_1983&quot;,DATUM[&quot;D_North_American_1983&quot;,SPHEROID[&quot;GRS_1980&quot;,6378137.0,298.257222101]],PRIMEM[&quot;Greenwich&quot;,0.0],UNIT[&quot;Degree&quot;,0.0174532925199433],AUTHORITY[&quot;EPSG&quot;,4269]]</WKT><XOrigin>-400</XOrigin><YOrigin>-400</YOrigin><XYScale>11258999068426.238</XYScale><ZOrigin>-100000</ZOrigin><ZScale>10000</ZScale><MOrigin>-100000</MOrigin><MScale>10000</MScale><XYTolerance>8.983152841195215e-09</XYTolerance><ZTolerance>0.001</ZTolerance><MTolerance>0.001</MTolerance><HighPrecision>true</HighPrecision><LeftLongitude>-180</LeftLongitude><WKID>4269</WKID><LatestWKID>4269</LatestWKID></GeographicCoordinateSystem>CopyFeatures \\usda.net\FS\FS\BusOps\CIO\Project\EDW\FME_Work\FME_CONNECTIONS\EDW_READ_ONLY.sde\S_USA.AdministrativeForest \\Usda.net\fs\FS\BusOps\CIO\Project\EDW\FME_Work\data.fs.usda.gov\deploy_datasets\out\temp\S_USA.AdministrativeForest\AdministrativeForest.shp # # # #20250706101849002025070610184900FGDCoriginal metadataPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxtZXRhZGF0YT4NCiAgPGlk +aW5mbz4NCiAgICA8Y2l0YXRpb24+DQogICAgICA8Y2l0ZWluZm8+DQogICAgICAgIDxvcmlnaW4+ +VVNEQSBGb3Jlc3QgU2VydmljZTwvb3JpZ2luPg0KICAgICAgICA8b3JpZ2luPkF1dG9tYXRlZCBM +YW5kcyBQcm9ncmFtIChBTFApPC9vcmlnaW4+DQogICAgICAgIDxwdWJkYXRlPjIwMTkwMzE3PC9w +dWJkYXRlPg0KICAgICAgICA8cHVidGltZT4wNzQ5MTM8L3B1YnRpbWU+DQogICAgICAgIDx0aXRs +ZT5BZG1pbmlzdHJhdGl2ZSBGb3Jlc3Q8L3RpdGxlPg0KICAgICAgICA8Z2VvZm9ybT52ZWN0b3Ig +ZGlnaXRhbCBkYXRhPC9nZW9mb3JtPg0KICAgICAgICA8b25saW5rPmh0dHA6Ly9kYXRhLmZzLnVz +ZGEuZ292L2dlb2RhdGEvZWR3L2RhdGFzZXRzLnBocDwvb25saW5rPg0KICAgICAgPC9jaXRlaW5m +bz4NCiAgICA8L2NpdGF0aW9uPg0KICAgIDxkZXNjcmlwdD4NCiAgICAgIDxhYnN0cmFjdD5BbiBh +cmVhIGVuY29tcGFzc2luZyBhbGwgdGhlIE5hdGlvbmFsIEZvcmVzdCBTeXN0ZW0gbGFuZHMgYWRt +aW5pc3RlcmVkIGJ5IGFuIGFkbWluaXN0cmF0aXZlIHVuaXQuIFRoZSBhcmVhIGVuY29tcGFzc2Vz +IHByaXZhdGUgbGFuZHMsIG90aGVyIGdvdmVybm1lbnRhbCBhZ2VuY3kgbGFuZHMsIGFuZCBtYXkg +Y29udGFpbiBOYXRpb25hbCBGb3Jlc3QgU3lzdGVtIGxhbmRzIHdpdGhpbiB0aGUgcHJvY2xhaW1l +ZCBib3VuZGFyaWVzIG9mIGFub3RoZXIgYWRtaW5pc3RyYXRpdmUgdW5pdC4gQWxsIE5hdGlvbmFs +IEZvcmVzdCBTeXN0ZW0gbGFuZHMgZmFsbCB3aXRoaW4gb25lIGFuZCBvbmx5IG9uZSBBZG1pbmlz +dHJhdGl2ZSBGb3Jlc3QgQXJlYS48L2Fic3RyYWN0Pg0KICAgICAgPHB1cnBvc2U+VGhpcyBkYXRh +IGlzIGludGVuZGVkIGZvciByZWFkLW9ubHkgdXNlLiBUaGVzZSBkYXRhIHdlcmUgcHJlcGFyZWQg +dG8gZGVzY3JpYmUgRm9yZXN0IFNlcnZpY2UgYWRtaW5pc3RyYXRpdmUgYXJlYSBib3VuZGFyaWVz +LiBUaGUgcHVycG9zZSBvZiB0aGUgZGF0YSBpcyB0byBwcm92aWRlIGRpc3BsYXksIGlkZW50aWZp +Y2F0aW9uLCBhbmQgYW5hbHlzaXMgdG9vbHMgZm9yIGRldGVybWluaW5nIGN1cnJlbnQgYm91bmRh +cnkgaW5mb3JtYXRpb24gZm9yIEZvcmVzdCBTZXJ2aWNlIG1hbmFnZXJzLCBHSVMgU3BlY2lhbGlz +dHMsIGFuZCBvdGhlcnMuPC9wdXJwb3NlPg0KICAgICAgPHN1cHBsaW5mPlRoZSBGb3Jlc3QgU2Vy +dmljZSBoYXMgbXVsdGlwbGUgdHlwZXMgb2YgYm91bmRhcmllcyByZXByZXNlbnRlZCBieSBkaWZm +ZXJlbnQgZmVhdHVyZSBjbGFzc2VzIChsYXllcnMpOiBBZG1pbmlzdHJhdGl2ZSwgT3duZXJzaGlw +IGFuZCBQcm9jbGFpbWVkLiANCiAgICAgICAgQURNSU5JU1RSQVRJVkUgYm91bmRhcmllcyAoZS5n +LiBBZG1pbmlzdHJhdGl2ZUZvcmVzdCBhbmQgUmFuZ2VyRGlzdHJpY3QgZmVhdHVyZSBjbGFzc2Vz +KSBlbmNvbXBhc3MgTmF0aW9uYWwgRm9yZXN0IFN5c3RlbSBsYW5kcyBtYW5hZ2VkIGJ5IGFuIGFk +bWluaXN0cmF0aXZlIHVuaXQuIFRoZXNlIGFyZSBkeW5hbWljIGxheWVycyB0aGF0IHNob3VsZCBu +b3QgYmUgY29uc2lkZXJlZCAibGVnYWwiIGJvdW5kYXJpZXMgYXMgdGhleSBhcmUgc2ltcGx5IGlu +dGVuZGVkIHRvIGlkZW50aWZ5IHRoZSBzcGVjaWZpYyBvcmdhbml6YXRpb25hbCB1bml0cyB0aGF0 +IGFkbWluaXN0ZXIgYXJlYXMuIEFzIGxhbmRzIGFyZSBhY3F1aXJlZCBhbmQgZGlzcG9zZWQsIHRo +ZSBhZG1pbmlzdHJhdGl2ZSBib3VuZGFyaWVzIGFyZSBhZGp1c3RlZCB0byBleHBhbmQgb3Igc2hy +aW5rIGFjY29yZGluZ2x5LiBQbGVhc2Ugbm90ZSB0aGF0IHJhbmdlciBkaXN0cmljdHMgYXJlIHN1 +YiB1bml0cyBvZiBOYXRpb25hbCBGb3Jlc3RzLiBBbiBhZG1pbmlzdHJhdGl2ZSBmb3Jlc3QgYm91 +bmRhcnkgY2FuIGNvbnRhaW4gb25lIG9yIG1vcmUgUHJvY2xhaW1lZCBOYXRpb25hbCBGb3Jlc3Rz +LCBOYXRpb25hbCBHcmFzc2xhbmRzLCBQdXJjaGFzZSBVbml0cywgUmVzZWFyY2ggYW5kIEV4cGVy +aW1lbnRhbCBBcmVhcywgTGFuZCBVdGlsaXphdGlvbiBQcm9qZWN0cyBhbmQgdmFyaW91cyAiT3Ro +ZXIiIEFyZWFzLiAgSWYgbmVlZGVkLCBPV05FUlNISVAgYm91bmRhcmllcyAoZS5nLiBCYXNpY093 +bmVyc2hpcCBhbmQgU3VyZmFjZU93bmVyc2hpcCBmZWF0dXJlIGNsYXNzZXMpIHNob3VsZCBiZSBy +ZXZpZXdlZCBhbG9uZyB3aXRoIHRoZXNlIGRhdGFzZXRzIHRvIGRldGVybWluZSBwYXJjZWxzIHRo +YXQgYXJlIGZlZGVyYWxseSBtYW5hZ2VkIHdpdGhpbiB0aGUgYWRtaW5pc3RyYXRpdmUgYm91bmRh +cmllcy4NCiAgICAgICAgT1dORVJTSElQIGJvdW5kYXJpZXMgKGUuZy4gQmFzaWNPd25lcnNoaXAg +YW5kIFN1cmZhY2VPd25lcnNoaXAgZmVhdHVyZSBjbGFzc2VzKSByZXByZXNlbnQgcGFyY2VscyB0 +aGF0IGFyZSB0aWVkIHRvIGxlZ2FsIHRyYW5zYWN0aW9ucyBvZiBvd25lcnNoaXAuIFRoZXNlIGFy +ZSBwYXJjZWxzIG9mIEZlZGVyYWwgbGFuZCBtYW5hZ2VkIGJ5IHRoZSBVU0RBIEZvcmVzdCBTZXJ2 +aWNlLiBQbGVhc2Ugbm90ZSB0aGF0IHRoZSBCYXNpY093bmVyc2hpcCBsYXllciBpcyBzaW1wbHkg +YSBkaXNzb2x2ZWQgdmVyc2lvbiBvZiB0aGUgU3VyZmFjZU93bmVyc2hpcCBsYXllci4gDQogICAg +ICAgIFBST0NMQUlNRUQgYm91bmRhcmllcyAoZS5nLiBQcm9jbGFpbWVkRm9yZXN0IGFuZCBQcm9j +bGFpbWVkRm9yZXN0X0dyYXNzbGFuZCkgZW5jb21wYXNzIGFyZWFzIG9mIE5hdGlvbmFsIEZvcmVz +dCBTeXN0ZW0gbGFuZCB0aGF0IGlzIHNldCBhc2lkZSBhbmQgcmVzZXJ2ZWQgZnJvbSBwdWJsaWMg +ZG9tYWluIGJ5IGV4ZWN1dGl2ZSBvcmRlciBvciBwcm9jbGFtYXRpb24uIFBsZWFzZSBub3RlIHRo +YXQgdGhlIFByb2NsYWltZWRGb3Jlc3QgbGF5ZXIgY29udGFpbnMgb25seSBwcm9jbGFpbWVkIGZv +cmVzdHMgd2hpbGUgUHJvY2xhaW1lZEZvcmVzdF9HcmFzc2xhbmQgbGF5ZXIgY29udGFpbnMgYm90 +aCBwcm9jbGFpbWVkIGZvcmVzdHMgYW5kIHByb2NsYWltZWQgZ3Jhc3NsYW5kcy4gRm9yIGJvdW5k +YXJpZXMgdGhhdCByZWZsZWN0IGN1cnJlbnQgTmF0aW9uYWwgRm9yZXN0IFN5c3RlbSBsYW5kcyBt +YW5hZ2VkIGJ5IGFuIGFkbWluaXN0cmF0aXZlIHVuaXQsIHNlZSB0aGUgQURNSU5JU1RSQVRJVkUg +Ym91bmRhcmllcyAoQWRtaW5pc3RyYXRpdmVGb3Jlc3QgYW5kIFJhbmdlckRpc3RyaWN0IGZlYXR1 +cmUgY2xhc3NlcykuDQogICAgICAgIEZvciBhIHZpc3VhbCBjb21wYXJpc29uIG9mIHRoZSBkaWZm +ZXJlbnQga2luZHMgb2YgVVNGUyBib3VuZGFyeSBkYXRhc2V0cyBtYWludGFpbmVkIGJ5IHRoZSBV +U0ZTLCBzZWUgdGhlIEZvcmVzdCBTZXJ2aWNlIEJvdW5kYXJ5IENvbXBhcmlzb24gbWFwIGF0ICBo +dHRwczovL3VzZnMubWFwcy5hcmNnaXMuY29tL2FwcHMvQ29tcGFyZUFuYWx5c2lzL2luZGV4Lmh0 +bWw/YXBwaWQ9ZmU3YjlmNTYyMTc5NDlhMjkxMzU2ZjA4Y2ZjY2IxMTkuDQogICAgICAgIFVTRlMg +Ym91bmRhcmllcyBhcmUgb2Z0ZW4gcmVmZXJlbmNlZCBpbiBuYXRpb25hbCBkYXRhc2V0cyBtYWlu +dGFpbmVkIGJ5IG90aGVyIGZlZGVyYWwgYWdlbmNpZXMuIFBsZWFzZSBub3RlIHRoYXQgdmFyaWF0 +aW9ucyBtYXkgYmUgZm91bmQgYmV0d2VlbiBVU0ZTIGRhdGEgYW5kIG90aGVyIGJvdW5kYXJ5IGRh +dGFzZXRzIGR1ZSB0byBkaWZmZXJpbmcgdXBkYXRlIGZyZXF1ZW5jaWVzLiBQQUQtVVMgKFByb3Rl +Y3RlZCBBcmVhcyBEYXRhYmFzZSBvZiB0aGUgVW5pdGVkIFN0YXRlcyksIG1haW50YWluZWQgYnkg +dGhlIFUuUy4gR2VvbG9naWNhbCBTdXJ2ZXksIGlzIGEgImJlc3QgYXZhaWxhYmxlIiBpbnZlbnRv +cnkgb2YgcHJvdGVjdGVkIGFyZWFzIGluY2x1ZGluZyBkYXRhIHByb3ZpZGVkIGJ5IG1hbmFnaW5n +IGFnZW5jaWVzIGFuZCBvcmdhbml6YXRpb25zIGluY2x1ZGluZyB0aGUgRm9yZXN0IFNlcnZpY2Uu +IEZvciBtb3JlIGluZm9ybWF0aW9uIHNlZSBodHRwczovL2dhcGFuYWx5c2lzLnVzZ3MuZ292L3Bh +ZHVzL2RhdGEvbWV0YWRhdGEvLiBTTUEgKFN1cmZhY2UgTWFuYWdlbWVudCBBZ2VuY3kpLCBtYWlu +dGFpbmVkIGJ5IHRoZSBCdXJlYXUgb2YgTGFuZCBNYW5hZ2VtZW50LCBkZXBpY3RzIEZlZGVyYWwg +bGFuZCBmb3IgdGhlIFVuaXRlZCBTdGF0ZXMgYW5kIGNsYXNzaWZpZXMgdGhpcyBsYW5kIGJ5IGl0 +cyBhY3RpdmUgRmVkZXJhbCBzdXJmYWNlIG1hbmFnaW5nIGFnZW5jeS4gSXQgdXNlcyBkYXRhIHBy +b3ZpZGVkIGJ5IHRoZSBGb3Jlc3QgU2VydmljZSBhbmQgb3RoZXIgYWdlbmNpZXMsIGNvbWJpbmVk +IHdpdGggTmF0aW9uYWwgUmVnaW9uYWwgT2ZmaWNlcyBjb2xsZWN0aW9uIGVmZm9ydHMuIEZvciBt +b3JlIGluZm9ybWF0aW9uIHNlZSBodHRwczovL2xhbmRzY2FwZS5ibG0uZ292L2dlb3BvcnRhbC9j +YXRhbG9nL3NlYXJjaC9yZXNvdXJjZS9kZXRhaWxzLnBhZ2U/dXVpZD0lN0IyQThCODkwNi03NzEx +LTRBRjctOTUxMC1DNkM3RkQ5OTExNzclN0QuPC9zdXBwbGluZj4NCiAgICA8L2Rlc2NyaXB0Pg0K +ICAgIDx0aW1lcGVyZD4NCiAgICAgIDx0aW1laW5mbz4NCiAgICAgICAgPHNuZ2RhdGU+DQogICAg +ICAgICAgPGNhbGRhdGU+MjAxNTA4MjY8L2NhbGRhdGU+DQogICAgICAgIDwvc25nZGF0ZT4NCiAg +ICAgIDwvdGltZWluZm8+DQogICAgICA8Y3VycmVudD5wdWJsaWNhdGlvbiBkYXRlPC9jdXJyZW50 +Pg0KICAgIDwvdGltZXBlcmQ+DQogICAgPHN0YXR1cz4NCiAgICAgIDxwcm9ncmVzcz5JbiB3b3Jr +PC9wcm9ncmVzcz4NCiAgICAgIDx1cGRhdGU+V2Vla2x5PC91cGRhdGU+DQogICAgPC9zdGF0dXM+ +DQogICAgPHNwZG9tPg0KICAgICAgPGJvdW5kaW5nPg0KICAgICAgICA8d2VzdGJjPi0xNTAuMDA3 +OTI4PC93ZXN0YmM+DQogICAgICAgIDxlYXN0YmM+LTY0LjczNDMyOTwvZWFzdGJjPg0KICAgICAg +ICA8bm9ydGhiYz42MS41MTg5OTI8L25vcnRoYmM+DQogICAgICAgIDxzb3V0aGJjPjE3LjczODk4 +Mzwvc291dGhiYz4NCiAgICAgIDwvYm91bmRpbmc+DQogICAgPC9zcGRvbT4NCiAgICA8a2V5d29y +ZHM+DQogICAgICA8dGhlbWU+DQogICAgICAgIDx0aGVtZWt0Pk5vbmU8L3RoZW1la3Q+DQogICAg +ICAgIDx0aGVtZWtleT5Gb3Jlc3QgU2VydmljZSBMYW5kIERhdGFzZXQ8L3RoZW1la2V5Pg0KICAg +ICAgICA8dGhlbWVrZXk+UmVnaW9uPC90aGVtZWtleT4NCiAgICAgICAgPHRoZW1la2V5PkZvcmVz +dCBOdW1iZXI8L3RoZW1la2V5Pg0KICAgICAgICA8dGhlbWVrZXk+TGFuZCBTdGF0dXM8L3RoZW1l +a2V5Pg0KICAgICAgICA8dGhlbWVrZXk+VVNEQSBGb3Jlc3QgU2VydmljZTwvdGhlbWVrZXk+DQog +ICAgICAgIDx0aGVtZWtleT5BZG1pbmlzdHJhdGl2ZSBGb3Jlc3Q8L3RoZW1la2V5Pg0KICAgICAg +ICA8dGhlbWVrZXk+Rm9yZXN0IE5hbWU8L3RoZW1la2V5Pg0KICAgICAgICA8dGhlbWVrZXk+TkZT +IExhbmRzPC90aGVtZWtleT4NCiAgICAgICAgPHRoZW1la2V5PkZvcmVzdCBTZXJ2aWNlIExhbmRz +IFByb2dyYW08L3RoZW1la2V5Pg0KICAgICAgPC90aGVtZT4NCiAgICAgIDx0aGVtZT4NCiAgICAg +ICAgPHRoZW1la3Q+SVNPIDE5MTE1IFRvcGljIENhdGVnb3JpZXM8L3RoZW1la3Q+DQogICAgICAg +IDx0aGVtZWtleT5ib3VuZGFyaWVzPC90aGVtZWtleT4NCiAgICAgIDwvdGhlbWU+DQogICAgPC9r +ZXl3b3Jkcz4NCiAgICA8YWNjY29uc3Q+Tm9uZTwvYWNjY29uc3Q+DQogICAgPHVzZWNvbnN0PlRo +ZSBVU0RBIEZvcmVzdCBTZXJ2aWNlIG1ha2VzIG5vIHdhcnJhbnR5LCBleHByZXNzZWQgb3IgaW1w +bGllZCwgaW5jbHVkaW5nIHRoZSB3YXJyYW50aWVzIG9mIG1lcmNoYW50YWJpbGl0eSBhbmQgZml0 +bmVzcyBmb3IgYSBwYXJ0aWN1bGFyIHB1cnBvc2UsIG5vciBhc3N1bWVzIGFueSBsZWdhbCBsaWFi +aWxpdHkgb3IgcmVzcG9uc2liaWxpdHkgZm9yIHRoZSBhY2N1cmFjeSwgcmVsaWFiaWxpdHksIGNv +bXBsZXRlbmVzcyBvciB1dGlsaXR5IG9mIHRoZXNlIGdlb3NwYXRpYWwgZGF0YSwgb3IgZm9yIHRo +ZSBpbXByb3BlciBvciBpbmNvcnJlY3QgdXNlIG9mIHRoZXNlIGdlb3NwYXRpYWwgZGF0YS4gVGhl +c2UgZ2Vvc3BhdGlhbCBkYXRhIGFuZCByZWxhdGVkIG1hcHMgb3IgZ3JhcGhpY3MgYXJlIG5vdCBs +ZWdhbCBkb2N1bWVudHMgYW5kIGFyZSBub3QgaW50ZW5kZWQgdG8gYmUgdXNlZCBhcyBzdWNoLiBU +aGUgZGF0YSBhbmQgbWFwcyBtYXkgbm90IGJlIHVzZWQgdG8gZGV0ZXJtaW5lIHRpdGxlLCBvd25l +cnNoaXAsIGxlZ2FsIGRlc2NyaXB0aW9ucyBvciBib3VuZGFyaWVzLCBsZWdhbCBqdXJpc2RpY3Rp +b24sIG9yIHJlc3RyaWN0aW9ucyB0aGF0IG1heSBiZSBpbiBwbGFjZSBvbiBlaXRoZXIgcHVibGlj +IG9yIHByaXZhdGUgbGFuZC4gTmF0dXJhbCBoYXphcmRzIG1heSBvciBtYXkgbm90IGJlIGRlcGlj +dGVkIG9uIHRoZSBkYXRhIGFuZCBtYXBzLCBhbmQgbGFuZCB1c2VycyBzaG91bGQgZXhlcmNpc2Ug +ZHVlIGNhdXRpb24uIFRoZSBkYXRhIGFyZSBkeW5hbWljIGFuZCBtYXkgY2hhbmdlIG92ZXIgdGlt +ZS4gVGhlIHVzZXIgaXMgcmVzcG9uc2libGUgdG8gdmVyaWZ5IHRoZSBsaW1pdGF0aW9ucyBvZiB0 +aGUgZ2Vvc3BhdGlhbCBkYXRhIGFuZCB0byB1c2UgdGhlIGRhdGEgYWNjb3JkaW5nbHkuPC91c2Vj +b25zdD4NCiAgICA8cHRjb250YWM+DQogICAgICA8Y250aW5mbz4NCiAgICAgICAgPGNudG9yZ3A+ +DQogICAgICAgICAgPGNudG9yZz5VU0ZTIENoaWVmIEluZm9ybWF0aW9uIE9mZmljZSwgRW50ZXJw +cmlzZSBEYXRhIFdhcmVob3VzZTwvY250b3JnPg0KICAgICAgICA8L2NudG9yZ3A+DQogICAgICAg +IDxjbnRhZGRyPg0KICAgICAgICAgIDxhZGRydHlwZT5waHlzaWNhbDwvYWRkcnR5cGU+DQogICAg +ICAgICAgPGNpdHk+V2FzaGluZ3RvbjwvY2l0eT4NCiAgICAgICAgICA8c3RhdGU+REM8L3N0YXRl +Pg0KICAgICAgICAgIDxwb3N0YWw+MjAyNTA8L3Bvc3RhbD4NCiAgICAgICAgICA8Y291bnRyeT5V +UzwvY291bnRyeT4NCiAgICAgICAgPC9jbnRhZGRyPg0KICAgICAgICA8Y250dm9pY2U+UGxlYXNl +IHNlbmQgYW4gZS1tYWlsIHRvIHRoZSBhZGRyZXNzIGJlbG93LjwvY250dm9pY2U+DQogICAgICAg +IDxjbnRlbWFpbD5kYXRhQGZzLmZlZC51czwvY250ZW1haWw+DQogICAgICA8L2NudGluZm8+DQog +ICAgPC9wdGNvbnRhYz4NCiAgICA8bmF0aXZlPiBWZXJzaW9uIDYuMiAoQnVpbGQgOTIwMCkgOyBF +c3JpIEFyY0dJUyAxMC41LjEuNzMzMzwvbmF0aXZlPg0KICAgIDxjcm9zc3JlZj4NCiAgICAgIDxj +aXRlaW5mbz4NCiAgICAgICAgPG9yaWdpbj5VU0RBIEZvcmVzdCBTZXJ2aWNlIEF1dG9tYXRlZCBM +YW5kcyBQcm9ncmFtIChBTFApPC9vcmlnaW4+DQogICAgICAgIDxwdWJkYXRlPnVua25vd248L3B1 +YmRhdGU+DQogICAgICAgIDx0aXRsZT5BdXRvbWF0ZWQgTGFuZHMgUHJvZ3JhbSAoQUxQKTwvdGl0 +bGU+DQogICAgICA8L2NpdGVpbmZvPg0KICAgIDwvY3Jvc3NyZWY+DQogIDwvaWRpbmZvPg0KICA8 +ZGF0YXF1YWw+DQogICAgPGF0dHJhY2M+DQogICAgICA8YXR0cmFjY3I+VGhlIFJlZ2lvbmFsIExh +bmQgU3RhdHVzIHBlcnNvbm5lbCBhcmUgcmVzcG9uc2libGUgZm9yIHRoZWlyIGxvY2FsIGRhdGEg +c3Rld2FyZHNoaXAgYW5kIG1haW50ZW5hbmNlLiBUaGUgb3ZlcmFsbCBjb2xsZWN0aW9uIG5hdGlv +bndpZGUgY29uc2lzdHMgb2YgMTAwJSBhdHRyaWJ1dGUgYWNjdXJhY3kgYmFzZWQgb24gY3VycmVu +dG5lc3MgYW5kIGNvbXBsZXRlbmVzcy48L2F0dHJhY2NyPg0KICAgIDwvYXR0cmFjYz4NCiAgICA8 +bG9naWM+Tm9uZS48L2xvZ2ljPg0KICAgIDxjb21wbGV0ZT5UaGlzIHRoZW1lIGlzIGN1cnJlbnQg +dG8gZGF0ZSBvZiBwdWJsaWNhdGlvbi48L2NvbXBsZXRlPg0KICAgIDxwb3NhY2M+DQogICAgICA8 +aG9yaXpwYT4NCiAgICAgICAgPGhvcml6cGFyPkFsbCBmZWF0dXJlcyBpbnRlZ3JhdGUgdmVydGlj +YWxseSB0byB0aGUgc291cmNlIGRhdGEgd2l0aCB0aGUgaGlnaGVzdCBsZXZlbCBvZiBhY2N1cmFj +eSAodXN1YWxseSBzdXJ2ZXkgbGF5ZXJzIG9mIHRvd25zaGlwcywgc2VjdGlvbnMsIGV0Yy4pLiBG +ZWF0dXJlcyB0aGF0IHNoYXJlIHRob3NlIGhpZ2hseSBhY2N1cmF0ZSBib3VuZGFyaWVzIGFyZSB2 +ZXJ0aWNhbGx5IGludGVncmF0ZWQuIExlZ2FsIGRlc2NyaXB0aW9ucyBmb3VuZCBpbiBwcm9jbGFt +YXRpb25zLCBsZWdhbCBkb2N1bWVudHMsIGV0Yy4gYXJlIHVzZWQgaW4gcGxhY2VzIHdoZXJlIHRo +ZSBmZWF0dXJlcyBkbyBub3Qgc2hhcmUgYm91bmRhcmllcyB3aXRoIGRhdGEgd2hpY2ggaGFzIHRo +ZSBoaWdoZXN0IGxldmVsIG9mIGFjY3VyYWN5LiBHZW5lcmFsbHkgdGhlIHNvdXJjZSBpbmZvcm1h +dGlvbiB1c2VkIHRvIHJlY29yZCBzcGF0aWFsIGxvY2F0aW9ucyBhcmUgZnJvbTogMSkgb24tc2Ny +ZWVuIGRpZ2l0aXppbmcgd2l0aCBhIGdlb3JlZmVyZW5jZWQgdG9wb2dyYXBoaWMgb3IgaW1hZ2Ug +YmFja2dyb3VuZDsgb3IgMikgR1BTIGZpZWxkIGNvbGxlY3RlZCBwb3NpdGlvbnMuPC9ob3JpenBh +cj4NCiAgICAgIDwvaG9yaXpwYT4NCiAgICA8L3Bvc2FjYz4NCiAgICA8bGluZWFnZT4NCiAgICAg +IDxzcmNpbmZvPg0KICAgICAgICA8c3JjY2l0ZT4NCiAgICAgICAgICA8Y2l0ZWluZm8+DQogICAg +ICAgICAgICA8b3JpZ2luPlVTRlM8L29yaWdpbj4NCiAgICAgICAgICAgIDxwdWJkYXRlPjIwMTkw +MzE3PC9wdWJkYXRlPg0KICAgICAgICAgICAgPHB1YnRpbWU+MDc0OTEzPC9wdWJ0aW1lPg0KICAg +ICAgICAgICAgPHRpdGxlPkFkbWluaXN0cmF0aXZlIFVuaXQ8L3RpdGxlPg0KICAgICAgICAgIDwv +Y2l0ZWluZm8+DQogICAgICAgIDwvc3JjY2l0ZT4NCiAgICAgICAgPHNyY3NjYWxlPjI0MDAwPC9z +cmNzY2FsZT4NCiAgICAgICAgPHR5cGVzcmM+b25MaW5lPC90eXBlc3JjPg0KICAgICAgICA8c3Jj +dGltZT4NCiAgICAgICAgICA8dGltZWluZm8+DQogICAgICAgICAgICA8c25nZGF0ZT4NCiAgICAg +ICAgICAgICAgPGNhbGRhdGU+MjAxNTAxMDE8L2NhbGRhdGU+DQogICAgICAgICAgICA8L3NuZ2Rh +dGU+DQogICAgICAgICAgPC90aW1laW5mbz4NCiAgICAgICAgICA8c3JjY3Vycj5wdWJsaWNhdGlv +biBkYXRlPC9zcmNjdXJyPg0KICAgICAgICA8L3NyY3RpbWU+DQogICAgICAgIDxzcmNjaXRlYT5B +ZG1pbmlzdHJhdGl2ZSBVbml0cyBmcm9tIFVTRlMuPC9zcmNjaXRlYT4NCiAgICAgICAgPHNyY2Nv +bnRyPlJlZ2lvbmFsIGRhdGEgdXRpbGl6aW5nIHN1YnR5cGVzLjwvc3JjY29udHI+DQogICAgICA8 +L3NyY2luZm8+DQogICAgICA8cHJvY3N0ZXA+DQogICAgICAgIDxwcm9jZGVzYz5EYXRhIHdhcyBt +ZXJnZWQgZnJvbSBhIHJlZ2lvbmFsIGluZnJhc3RydWN0dXJlLCB1dGlsaXppbmcgc3VidHlwZXMs +IGludG8gc3BlY2lmaWMgbmF0aW9uYWwgZGF0YXNldHMuIFRoaXMgZGF0YSB3YXMgcHVibGlzaGVk +IHVzaW5nIEZlYXR1cmUgTWFuaXB1bGF0aW9uIEVuZ2luZSBTb2Z0d2FyZS4gVGhlIHNwYXRpYWwg +ZmVhdHVyZXMgd2VyZSBleHRyYWN0ZWQgZnJvbSB0aGUgdHJhbnNhY3Rpb25hbCBkYXRhYmFzZSwg +bWFuaXB1bGF0ZWQgdG8gbWVldCB0aGUgZGVzaXJlZCBvdXRwdXQgZm9ybWF0IGFuZCBwdWJsaXNo +ZWQgdG8gdGhlIHRhcmdldCBmZWF0dXJlIGNsYXNzLjwvcHJvY2Rlc2M+DQogICAgICAgIDxwcm9j +ZGF0ZT4yMDE1MDEwMTwvcHJvY2RhdGU+DQogICAgICA8L3Byb2NzdGVwPg0KICAgIDwvbGluZWFn +ZT4NCiAgPC9kYXRhcXVhbD4NCiAgPHNwZG9pbmZvPg0KICAgIDxkaXJlY3Q+VmVjdG9yPC9kaXJl +Y3Q+DQogICAgPHB0dmN0aW5mPg0KICAgICAgPHNkdHN0ZXJtPg0KICAgICAgICA8c2R0c3R5cGU+ +R1QtcG9seWdvbiBjb21wb3NlZCBvZiBjaGFpbnM8L3NkdHN0eXBlPg0KICAgICAgICA8cHR2Y3Rj +bnQ+MTEyPC9wdHZjdGNudD4NCiAgICAgIDwvc2R0c3Rlcm0+DQogICAgPC9wdHZjdGluZj4NCiAg +PC9zcGRvaW5mbz4NCiAgPHNwcmVmPg0KICAgIDxob3JpenN5cz4NCiAgICAgIDxnZW9ncmFwaD4N +CiAgICAgICAgPGxhdHJlcz44Ljk5MDAwMDAwMDAwMDAwMDRlLTAwODwvbGF0cmVzPg0KICAgICAg +ICA8bG9uZ3Jlcz44Ljk5MDAwMDAwMDAwMDAwMDRlLTAwODwvbG9uZ3Jlcz4NCiAgICAgICAgPGdl +b2d1bml0PkRlY2ltYWwgRGVncmVlczwvZ2VvZ3VuaXQ+DQogICAgICA8L2dlb2dyYXBoPg0KICAg +ICAgPGdlb2RldGljPg0KICAgICAgICA8aG9yaXpkbj5EIE5vcnRoIEFtZXJpY2FuIDE5ODM8L2hv +cml6ZG4+DQogICAgICAgIDxlbGxpcHM+R1JTIDE5ODA8L2VsbGlwcz4NCiAgICAgICAgPHNlbWlh +eGlzPjYzNzgxMzcuMDwvc2VtaWF4aXM+DQogICAgICAgIDxkZW5mbGF0PjI5OC4yNTcyMjIxMDE8 +L2RlbmZsYXQ+DQogICAgICA8L2dlb2RldGljPg0KICAgIDwvaG9yaXpzeXM+DQogIDwvc3ByZWY+ +DQogIDxlYWluZm8+DQogICAgPGRldGFpbGVkPg0KICAgICAgPGVudHR5cD4NCiAgICAgICAgPGVu +dHR5cGw+U19VU0EuQWRtaW5pc3RyYXRpdmVGb3Jlc3Q8L2VudHR5cGw+DQogICAgICAgIDxlbnR0 +eXBkPkZlYXR1cmUgQ2xhc3M6IEEgY29sbGVjdGlvbiBvZiBnZW9ncmFwaGljIGZlYXR1cmVzIHdp +dGggdGhlIHNhbWUgZ2VvbWV0cnkgdHlwZSAoc3VjaCBhcyBwb2ludCwgbGluZSwgb3IgcG9seWdv +biksIHRoZSBzYW1lIGF0dHJpYnV0ZXMsIGFuZCB0aGUgc2FtZSBzcGF0aWFsIHJlZmVyZW5jZS48 +L2VudHR5cGQ+DQogICAgICAgIDxlbnR0eXBkcz5Fc3JpIEdJUyBEYXRhIERpY3Rpb25hcnk8L2Vu +dHR5cGRzPg0KICAgICAgPC9lbnR0eXA+DQogICAgICA8YXR0cj4NCiAgICAgICAgPGF0dHJsYWJs +Pk9CSkVDVElEPC9hdHRybGFibD4NCiAgICAgICAgPGF0dHJkZWY+SW50ZXJuYWwgZmVhdHVyZSBu +dW1iZXIuPC9hdHRyZGVmPg0KICAgICAgICA8YXR0cmRlZnM+RVNSSTwvYXR0cmRlZnM+DQogICAg +ICAgIDxhdHRyZG9tdj4NCiAgICAgICAgICA8dWRvbT5JbnRlcm5hbCBmZWF0dXJlIG51bWJlci48 +L3Vkb20+DQogICAgICAgIDwvYXR0cmRvbXY+DQogICAgICA8L2F0dHI+DQogICAgICA8YXR0cj4N +CiAgICAgICAgPGF0dHJsYWJsPkFETUlORk9SRVNUSUQ8L2F0dHJsYWJsPg0KICAgICAgICA8YXR0 +cmRlZj5VbmlxdWUgaWRlbnRpZmllciBnZW5lcmF0ZWQgYnkgT3JhY2xlIHNlcXVlbmNlIG9yIFNZ +U19HVUlEKCkgZnVuY3Rpb24uICBUaGlzIGlzIHRoZSBQcmltYXJ5IEtleS48L2F0dHJkZWY+DQog +ICAgICAgIDxhdHRyZGVmcz5VU0ZTPC9hdHRyZGVmcz4NCiAgICAgICAgPGF0dHJkb212Pg0KICAg +ICAgICAgIDx1ZG9tPlVuaXF1ZSBpZGVudGlmaWVyIGdlbmVyYXRlZCBieSBPcmFjbGUgc2VxdWVu +Y2Ugb3IgU1lTX0dVSUQoKSBmdW5jdGlvbi4gIFRoaXMgaXMgdGhlIFByaW1hcnkgS2V5LjwvdWRv +bT4NCiAgICAgICAgPC9hdHRyZG9tdj4NCiAgICAgIDwvYXR0cj4NCiAgICAgIDxhdHRyPg0KICAg +ICAgICA8YXR0cmxhYmw+UkVHSU9OPC9hdHRybGFibD4NCiAgICAgICAgPGF0dHJkZWY+VS5TLiBG +b3Jlc3QgU2VydmljZSBSZWdpb24sIGNvbnNpc3Rpbmcgb2YgUmVnaW9uIDAxIChOb3J0aGVybiBS +ZWdpb24pLCBSZWdpb24gMDIgKFJvY2t5IE1vdW50YWluIFJlZ2lvbiksIFJlZ2lvbiAwMyAoU291 +dGh3ZXN0ZXJuIFJlZ2lvbiksIFJlZ2lvbiAwNCAoSW50ZXJtb3VudGFpbiBSZWdpb24pLCBSZWdp +b24gMDUgKFBhY2lmaWMgU291dGh3ZXN0IFJlZ2lvbiksIFJlZ2lvbiAwNiAoUGFjaWZpYyBOb3J0 +aHdlc3QgUmVnaW9uKSwgUmVnaW9uIDA4IChTb3V0aGVybiBSZWdpb24pLCBSZWdpb24gMDkgKEVh +c3Rlcm4gUmVnaW9uKSwgYW5kIFJlZ2lvbiAxMCAoQWxhc2thIFJlZ2lvbikuIFRoZXJlIGlzIG5v +IFJlZ2lvbiAwNy4gUmVnaW9ucyBhcmUgbGlzdGVkIG9uIGh0dHA6Ly93d3cuZnMuZmVkLnVzL29y +Z2FuaXphdGlvbi81MDYuIFdpdGhpbiB0aGlzIGRhdGFzZXQsIHRoZSByZWdpb24gcmVwcmVzZW50 +cyB0aGUgTGFuZCBTdGF0dXMgUmVjb3JkIFN5c3RlbSBQcm9kdWN0aW9uIFJlZ2lvbmFsIHNjaGVt +YSBmcm9tIHdoaWNoIGRhdGEgd2FzIGRlcml2ZWQuPC9hdHRyZGVmPg0KICAgICAgICA8YXR0cmRl +ZnM+VVNGUzwvYXR0cmRlZnM+DQogICAgICAgIDxhdHRyZG9tdj4NCiAgICAgICAgICA8ZWRvbT4N +CiAgICAgICAgICAgIDxlZG9tdj4wMTwvZWRvbXY+DQogICAgICAgICAgICA8ZWRvbXZkPlJlZ2lv +biAwMSAtIE5vcnRoZXJuPC9lZG9tdmQ+DQogICAgICAgICAgICA8ZWRvbXZkcz5VLlMuIEZvcmVz +dCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAgICAgICAgIDwvZWRvbT4NCiAgICAgICAgICA8ZWRvbT4N +CiAgICAgICAgICAgIDxlZG9tdj4wMjwvZWRvbXY+DQogICAgICAgICAgICA8ZWRvbXZkPlJlZ2lv +biAwMiAtIFJvY2t5IE1vdW50YWluPC9lZG9tdmQ+DQogICAgICAgICAgICA8ZWRvbXZkcz5VLlMu +IEZvcmVzdCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAgICAgICAgIDwvZWRvbT4NCiAgICAgICAgICA8 +ZWRvbT4NCiAgICAgICAgICAgIDxlZG9tdj4wMzwvZWRvbXY+DQogICAgICAgICAgICA8ZWRvbXZk +PlJlZ2lvbiAwMyAtIFNvdXRod2VzdGVybjwvZWRvbXZkPg0KICAgICAgICAgICAgPGVkb212ZHM+ +VS5TLiBGb3Jlc3QgU2VydmljZTwvZWRvbXZkcz4NCiAgICAgICAgICA8L2Vkb20+DQogICAgICAg +ICAgPGVkb20+DQogICAgICAgICAgICA8ZWRvbXY+MDQ8L2Vkb212Pg0KICAgICAgICAgICAgPGVk +b212ZD5SZWdpb24gMDQgLSBJbnRlcm1vdW50YWluPC9lZG9tdmQ+DQogICAgICAgICAgICA8ZWRv +bXZkcz5VLlMuIEZvcmVzdCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAgICAgICAgIDwvZWRvbT4NCiAg +ICAgICAgICA8ZWRvbT4NCiAgICAgICAgICAgIDxlZG9tdj4wNTwvZWRvbXY+DQogICAgICAgICAg +ICA8ZWRvbXZkPlJlZ2lvbiAwNSAtIFBhY2lmaWMgU291dGh3ZXN0PC9lZG9tdmQ+DQogICAgICAg +ICAgICA8ZWRvbXZkcz5VLlMuIEZvcmVzdCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAgICAgICAgIDwv +ZWRvbT4NCiAgICAgICAgICA8ZWRvbT4NCiAgICAgICAgICAgIDxlZG9tdj4wNjwvZWRvbXY+DQog +ICAgICAgICAgICA8ZWRvbXZkPlJlZ2lvbiAwNiAtIFBhY2lmaWMgTm9ydGh3ZXN0PC9lZG9tdmQ+ +DQogICAgICAgICAgICA8ZWRvbXZkcz5VLlMuIEZvcmVzdCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAg +ICAgICAgIDwvZWRvbT4NCiAgICAgICAgICA8ZWRvbT4NCiAgICAgICAgICAgIDxlZG9tdj4wODwv +ZWRvbXY+DQogICAgICAgICAgICA8ZWRvbXZkPlJlZ2lvbiAwOCAtIFNvdXRoZXJuPC9lZG9tdmQ+ +DQogICAgICAgICAgICA8ZWRvbXZkcz5VLlMuIEZvcmVzdCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAg +ICAgICAgIDwvZWRvbT4NCiAgICAgICAgICA8ZWRvbT4NCiAgICAgICAgICAgIDxlZG9tdj4wOTwv +ZWRvbXY+DQogICAgICAgICAgICA8ZWRvbXZkPlJlZ2lvbiAwOSAtIEVhc3Rlcm48L2Vkb212ZD4N +CiAgICAgICAgICAgIDxlZG9tdmRzPlUuUy4gRm9yZXN0IFNlcnZpY2U8L2Vkb212ZHM+DQogICAg +ICAgICAgPC9lZG9tPg0KICAgICAgICAgIDxlZG9tPg0KICAgICAgICAgICAgPGVkb212PjEwPC9l +ZG9tdj4NCiAgICAgICAgICAgIDxlZG9tdmQ+UmVnaW9uIDEwIC0gQWxhc2thPC9lZG9tdmQ+DQog +ICAgICAgICAgICA8ZWRvbXZkcz5VLlMuIEZvcmVzdCBTZXJ2aWNlPC9lZG9tdmRzPg0KICAgICAg +ICAgIDwvZWRvbT4NCiAgICAgICAgPC9hdHRyZG9tdj4NCiAgICAgIDwvYXR0cj4NCiAgICAgIDxh +dHRyPg0KICAgICAgICA8YXR0cmxhYmw+Rk9SRVNUTlVNQkVSPC9hdHRybGFibD4NCiAgICAgICAg +PGF0dHJkZWY+QSBudW1iZXIgdXNlZCB0byBpZGVudGlmeSBhIE5hdGlvbmFsIEZvcmVzdCAob3Ig +b3RoZXIgQWRtaW5pc3RyYXRpdmUgdW5pdCkuIFRoZSBmb3Jlc3QgbnVtYmVyIGlzIHVuaXF1ZSBv +bmx5IHdpdGhpbiBhIEZvcmVzdCBTZXJ2aWNlIFJlZ2lvbi48L2F0dHJkZWY+DQogICAgICAgIDxh +dHRyZGVmcz5VU0ZTPC9hdHRyZGVmcz4NCiAgICAgICAgPGF0dHJkb212Pg0KICAgICAgICAgIDx1 +ZG9tPkEgbnVtYmVyIHVzZWQgdG8gaWRlbnRpZnkgYSBOYXRpb25hbCBGb3Jlc3QgKG9yIG90aGVy +IEFkbWluaXN0cmF0aXZlIHVuaXQpLiBUaGUgZm9yZXN0IG51bWJlciBpcyB1bmlxdWUgb25seSB3 +aXRoaW4gYSBGb3Jlc3QgU2VydmljZSBSZWdpb24uPC91ZG9tPg0KICAgICAgICA8L2F0dHJkb212 +Pg0KICAgICAgPC9hdHRyPg0KICAgICAgPGF0dHI+DQogICAgICAgIDxhdHRybGFibD5GT1JFU1RP +UkdDT0RFPC9hdHRybGFibD4NCiAgICAgICAgPGF0dHJkZWY+VGhlIGZ1bGwgb3JnYW5pemF0aW9u +YWwgY29kZSBvZiBhbiBhZG1pbmlzdHJhdGl2ZSB1bml0LjwvYXR0cmRlZj4NCiAgICAgICAgPGF0 +dHJkZWZzPlVTREEgRm9yZXN0IFNlcnZpY2U8L2F0dHJkZWZzPg0KICAgICAgICA8YXR0cmRvbXY+ +DQogICAgICAgICAgPHVkb20+VGhlIGZ1bGwgb3JnYW5pemF0aW9uYWwgY29kZSBvZiBhbiBhZG1p +bmlzdHJhdGl2ZSB1bml0LjwvdWRvbT4NCiAgICAgICAgPC9hdHRyZG9tdj4NCiAgICAgIDwvYXR0 +cj4NCiAgICAgIDxhdHRyPg0KICAgICAgICA8YXR0cmxhYmw+Rk9SRVNUTkFNRTwvYXR0cmxhYmw+ +DQogICAgICAgIDxhdHRyZGVmPlRoZSBvZmZpY2lhbCBuYW1lIG9mIHRoZSBOYXRpb25hbCBGb3Jl +c3QgKG9yIG90aGVyIEFkbWluaXN0cmF0aXZlIEFyZWEpLjwvYXR0cmRlZj4NCiAgICAgICAgPGF0 +dHJkZWZzPlVTREEgRm9yZXN0IFNlcnZpY2U8L2F0dHJkZWZzPg0KICAgICAgICA8YXR0cmRvbXY+ +DQogICAgICAgICAgPHVkb20+VGhlIG9mZmljaWFsIG5hbWUgb2YgdGhlIE5hdGlvbmFsIEZvcmVz +dCAob3Igb3RoZXIgQWRtaW5pc3RyYXRpdmUgQXJlYSkuPC91ZG9tPg0KICAgICAgICA8L2F0dHJk +b212Pg0KICAgICAgPC9hdHRyPg0KICAgICAgPGF0dHI+DQogICAgICAgIDxhdHRybGFibD5HSVNf +QUNSRVM8L2F0dHJsYWJsPg0KICAgICAgICA8YXR0cmRlZj5UaGUgYXJlYSBvZiB0aGUgZmVhdHVy +ZSBpbiBhY3JlcywgY2FsY3VsYXRlZCB1c2luZyBhIHByb2plY3RlZCBzdXJmYWNlIGNvbXB1dGF0 +aW9uLiBBIE5BRCA4MywgVVNBIENvbnRpZ3VvdXMgQWxiZXJzIEVxdWFsIEFyZWEgQ29uaWMgcHJv +amVjdGlvbiB3YXMgdXNlZCBmb3IgUmVnaW9ucyAxLTksIGFuZCBOQUQgODMsIEFsYXNrYSBBbGJl +cnMgZm9yIFJlZ2lvbiAxMC48L2F0dHJkZWY+DQogICAgICAgIDxhdHRyZGVmcz5VU0ZTPC9hdHRy +ZGVmcz4NCiAgICAgICAgPGF0dHJkb212Pg0KICAgICAgICAgIDx1ZG9tPlRoZSBhcmVhIG9mIHRo +ZSBmZWF0dXJlIGluIGFjcmVzLCBjYWxjdWxhdGVkIHVzaW5nIGEgcHJvamVjdGVkIHN1cmZhY2Ug +Y29tcHV0YXRpb24uIEEgTkFEIDgzLCBVU0EgQ29udGlndW91cyBBbGJlcnMgRXF1YWwgQXJlYSBD +b25pYyBwcm9qZWN0aW9uIHdhcyB1c2VkIGZvciBSZWdpb25zIDEtOSwgYW5kIE5BRCA4MywgQWxh +c2thIEFsYmVycyBmb3IgUmVnaW9uIDEwLjwvdWRvbT4NCiAgICAgICAgPC9hdHRyZG9tdj4NCiAg +ICAgIDwvYXR0cj4NCiAgICAgIDxhdHRyPg0KICAgICAgICA8YXR0cmxhYmw+U0hBUEU8L2F0dHJs +YWJsPg0KICAgICAgICA8YXR0cmRlZj5GZWF0dXJlIGdlb21ldHJ5LjwvYXR0cmRlZj4NCiAgICAg +ICAgPGF0dHJkZWZzPkVTUkk8L2F0dHJkZWZzPg0KICAgICAgICA8YXR0cmRvbXY+DQogICAgICAg +ICAgPHVkb20+RmVhdHVyZSBnZW9tZXRyeS48L3Vkb20+DQogICAgICAgIDwvYXR0cmRvbXY+DQog +ICAgICA8L2F0dHI+DQogICAgICA8YXR0cj4NCiAgICAgICAgPGF0dHJsYWJsPlNIQVBFLkFSRUE8 +L2F0dHJsYWJsPg0KICAgICAgICA8YXR0cmRlZj5BcmVhIG9mIGZlYXR1cmUgaW4gaW50ZXJuYWwg +dW5pdHMgc3F1YXJlZC48L2F0dHJkZWY+DQogICAgICAgIDxhdHRyZGVmcz5FU1JJPC9hdHRyZGVm +cz4NCiAgICAgICAgPGF0dHJkb212Pg0KICAgICAgICAgIDx1ZG9tPkFyZWEgb2YgZmVhdHVyZSBp +biBpbnRlcm5hbCB1bml0cyBzcXVhcmVkLjwvdWRvbT4NCiAgICAgICAgPC9hdHRyZG9tdj4NCiAg +ICAgIDwvYXR0cj4NCiAgICAgIDxhdHRyPg0KICAgICAgICA8YXR0cmxhYmw+U0hBUEUuTEVOPC9h +dHRybGFibD4NCiAgICAgICAgPGF0dHJkZWY+TGVuZ3RoIG9mIGZlYXR1cmUgaW4gaW50ZXJuYWwg +dW5pdHMuPC9hdHRyZGVmPg0KICAgICAgICA8YXR0cmRlZnM+RVNSSTwvYXR0cmRlZnM+DQogICAg +ICAgIDxhdHRyZG9tdj4NCiAgICAgICAgICA8dWRvbT5MZW5ndGggb2YgZmVhdHVyZSBpbiBpbnRl +cm5hbCB1bml0cy48L3Vkb20+DQogICAgICAgIDwvYXR0cmRvbXY+DQogICAgICA8L2F0dHI+DQog +ICAgPC9kZXRhaWxlZD4NCiAgPC9lYWluZm8+DQogIDxkaXN0aW5mbz4NCiAgICA8ZGlzdHJpYj4N +CiAgICAgIDxjbnRpbmZvPg0KICAgICAgICA8Y250b3JncD4NCiAgICAgICAgICA8Y250b3JnPlVT +RlMgQ2hpZWYgSW5mb3JtYXRpb24gT2ZmaWNlLCBFbnRlcnByaXNlIERhdGEgV2FyZWhvdXNlPC9j +bnRvcmc+DQogICAgICAgIDwvY250b3JncD4NCiAgICAgICAgPGNudGFkZHI+DQogICAgICAgICAg +PGFkZHJ0eXBlPnBoeXNpY2FsPC9hZGRydHlwZT4NCiAgICAgICAgICA8Y2l0eT5XYXNoaW5ndG9u +PC9jaXR5Pg0KICAgICAgICAgIDxzdGF0ZT5EQzwvc3RhdGU+DQogICAgICAgICAgPHBvc3RhbD4y +MDI1MDwvcG9zdGFsPg0KICAgICAgICA8L2NudGFkZHI+DQogICAgICAgIDxjbnR2b2ljZT5QbGVh +c2Ugc2VuZCBhbiBlLW1haWwgdG8gdGhlIGFkZHJlc3MgYmVsb3cuPC9jbnR2b2ljZT4NCiAgICAg +ICAgPGNudGVtYWlsPmRhdGFAZnMuZmVkLnVzPC9jbnRlbWFpbD4NCiAgICAgIDwvY250aW5mbz4N +CiAgICA8L2Rpc3RyaWI+DQogICAgPHJlc2Rlc2M+RG93bmxvYWRhYmxlIGRhdGEuPC9yZXNkZXNj +Pg0KICAgIDxkaXN0bGlhYj5UaGUgVS5TLiBGb3Jlc3QgU2VydmljZSBtYWtlcyBubyB3YXJyYW50 +eSwgZXhwcmVzcyBvciBpbXBsaWVkLCBub3IgYXNzdW1lcyBhbnkgbGlhYmlsaXR5IG9yIHJlc3Bv +bnNpYmlsaXR5IGZvciB0aGUgYWNjdXJhY3ksIHJlbGlhYmlsaXR5LCBjb21wbGV0ZW5lc3MsIG9y +IHV0aWxpdHkgb2YgdGhlc2UgZ2Vvc3BhdGlhbCBkYXRhIG9yIGZvciB0aGUgaW1wcm9wZXIgb3Ig +aW5jb3JyZWN0IHVzZSBvZiB0aG9zZSBkYXRhLiBUaGUgZGF0YSBhcmUgZHluYW1pYyBhbmQgbWF5 +IGNoYW5nZSBvdmVyIHRpbWUuIFRoZSB1c2VyIGlzIHJlc3BvbnNpYmxlIGZvciB2ZXJpZnlpbmcg +dGhlIGxpbWl0YXRpb25zIG9mIHRoZSBnZW9zcGF0aWFsIGRhdGEgYW5kIGZvciB1c2luZyB0aGUg +ZGF0YSBhY2NvcmRpbmdseS48L2Rpc3RsaWFiPg0KICA8L2Rpc3RpbmZvPg0KICA8bWV0YWluZm8+ +DQogICAgPG1ldGQ+MjAxOTAzMTg8L21ldGQ+DQogICAgPG1ldGM+DQogICAgICA8Y250aW5mbz4N +CiAgICAgICAgPGNudG9yZ3A+DQogICAgICAgICAgPGNudG9yZz5VU0ZTIENoaWVmIEluZm9ybWF0 +aW9uIE9mZmljZSwgRW50ZXJwcmlzZSBEYXRhIFdhcmVob3VzZTwvY250b3JnPg0KICAgICAgICA8 +L2NudG9yZ3A+DQogICAgICAgIDxjbnRhZGRyPg0KICAgICAgICAgIDxhZGRydHlwZT5waHlzaWNh +bDwvYWRkcnR5cGU+DQogICAgICAgICAgPGNpdHk+V2FzaGluZ3RvbjwvY2l0eT4NCiAgICAgICAg +ICA8c3RhdGU+REM8L3N0YXRlPg0KICAgICAgICAgIDxwb3N0YWw+MjAyNTA8L3Bvc3RhbD4NCiAg +ICAgICAgPC9jbnRhZGRyPg0KICAgICAgICA8Y250dm9pY2U+UGxlYXNlIHNlbmQgYW4gZS1tYWls +IHRvIHRoZSBhZGRyZXNzIGJlbG93LjwvY250dm9pY2U+DQogICAgICAgIDxjbnRlbWFpbD5kYXRh +QGZzLmZlZC51czwvY250ZW1haWw+DQogICAgICA8L2NudGluZm8+DQogICAgPC9tZXRjPg0KICAg +IDxtZXRzdGRuPkZHREMgQ29udGVudCBTdGFuZGFyZCBmb3IgRGlnaXRhbCBHZW9zcGF0aWFsIE1l +dGFkYXRhPC9tZXRzdGRuPg0KICAgIDxtZXRzdGR2PkZHREMtU1RELTAwMS0xOTk4PC9tZXRzdGR2 +Pg0KICAgIDxtZXR0Yz5sb2NhbCB0aW1lPC9tZXR0Yz4NCiAgICA8bWV0c2k+DQogICAgICA8bWV0 +c2NzPk5vbmU8L21ldHNjcz4NCiAgICAgIDxtZXRzYz5VbmNsYXNzaWZpZWQ8L21ldHNjPg0KICAg +ICAgPG1ldHNoZD5Ob25lPC9tZXRzaGQ+DQogICAgPC9tZXRzaT4NCiAgPC9tZXRhaW5mbz4NCjwv +bWV0YWRhdGE+datasetEPSG6.5(3.0.1) diff --git a/schema-bootstrap/tests/fixtures/usfs_forests.shx b/schema-bootstrap/tests/fixtures/usfs_forests.shx new file mode 100644 index 0000000..c3d7549 Binary files /dev/null and b/schema-bootstrap/tests/fixtures/usfs_forests.shx differ diff --git a/schema-bootstrap/tests/fixtures/utah_places.dbf b/schema-bootstrap/tests/fixtures/utah_places.dbf new file mode 100644 index 0000000..0271d64 Binary files /dev/null and b/schema-bootstrap/tests/fixtures/utah_places.dbf differ diff --git a/schema-bootstrap/tests/fixtures/utah_places.prj b/schema-bootstrap/tests/fixtures/utah_places.prj new file mode 100644 index 0000000..5ded4bc --- /dev/null +++ b/schema-bootstrap/tests/fixtures/utah_places.prj @@ -0,0 +1 @@ +GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] \ No newline at end of file diff --git a/schema-bootstrap/tests/fixtures/utah_places.shp b/schema-bootstrap/tests/fixtures/utah_places.shp new file mode 100644 index 0000000..37a5d22 Binary files /dev/null and b/schema-bootstrap/tests/fixtures/utah_places.shp differ diff --git a/schema-bootstrap/tests/fixtures/utah_places.shp.ea.iso.xml b/schema-bootstrap/tests/fixtures/utah_places.shp.ea.iso.xml new file mode 100755 index 0000000..afc97ec --- /dev/null +++ b/schema-bootstrap/tests/fixtures/utah_places.shp.ea.iso.xml @@ -0,0 +1,913 @@ + + + + Feature Catalog for the Current Places TIGER/Line Shapefiles + + + Places + + + 2023 + + + 2023-11-09 + + + eng + + + utf8 + + + + + U.S. Department of Commerce, U.S. Census Bureau, Geography Division, Geospatial Products Branch + + + + + + + 301-763-1128 + + + 301-763-4710 + + + + + + + 4600 Silver Hill Road, Stop 7400 + + + Washington + + + DC + + + 20233-7400 + + + United States + + + geo.geography@census.gov + + + + + + + pointOfContact + + + + + + + PLACE.shp + + + Current Place State-based + + + false + + + + + + STATEFP + + + Current state Federal Information Processing Series (FIPS) code + + + + + + + + National Standard Codes (ANSI INCITS 38-2009), Federal Information Processing + Series (FIPS) - States/State Equivalents + + + + + + + + + + + PLACEFP + + + Current place Federal Information Processing Series (FIPS) code + + + + + + + + FederalInformation Processing Series Census Codes for Named Populated Places, + Primary County Divisions, and other Locational Entities of the United States, + Puerto Rico, and the Island Areas (Formerly FIPS 55-3) + + + + + + + + + + + PLACENS + + + Current place GNIS code + + + + + + + + INCITS 446:2008 (Geographic Names Information System (GNIS)), Identifying Attributes + for Named Physical and Cultural Geographic Features (Except Roads and Highways) of + the United States, Its Territories, Outlying Areas, and Freely Associated Areas, and + the Waters of the Same to the Limit of the Twelve-Mile Statutory Zone + + + + + + + + + + + GEOID + + + Place identifier; a concatenation of Current state FIPS code and place FIPS code + + + + + + + + + + The GEOID attribute is a concatenation of: a) the state FIPS code, and b) the place FIPS + code. No spaces are allowed between the two codes. The State FIPS code is taken from + "National Standard Codes (ANSI INCITS 38-2009), Federal Information Processing + Series(FIPS) - States". The place FIPS code is taken from FederalInformation + Processing Series Census Codes for Named Populated Places, Primary County Divisions, + and other Locational Entities of the United States, Puerto Rico, and the Island Areas + (Formerly FIPS 55-3) + + + + + + + + + + GEOIDFQ + + + Fully qualified place identifier; a concatenation of census survey summary level information with the place identifier. The GEOIDFQ attribute is calculated to facilitate joining census spatial data to census survey summary files. + + + + + + + + The GEOIDFQ attribute is a concatenation of the summary level code, followed by the geovariant code, followed by the geocomponent code, followed by 'US', followed by the place identifier. No spaces are allowed between the codes. The summary level code is '1600'. The geovariant code is '00'. The geocomponent code is '00'. The place identifier is taken from the 'GEOID' attribute. + + + + + + + + + NAME + + + Current place name + + + + + + + + + Values for this attribute are composed of a set of place names. As such, they do not + exist in a known, predefined set. + + + + + + + + + + NAMELSAD + + + Current name and the translated legal/statistical area description for place + + + + + + + + + Current name and the translated legal/statistical area description for place + + + + + + + + + + LSAD + + + Current legal/statistical area description code for place + + + + + + + 00 + + + Blank + + + + + + + + 21 + + + borough (suffix) + + + + + + + + 25 + + + city (suffix) + + + + + + + + 37 + + + municipality (suffix) + + + + + + + + 43 + + + town (suffix) + + + + + + + + 47 + + + village (suffix) + + + + + + + + 53 + + + city and borough (suffix) + + + + + + + + 55 + + + comunidad (suffix) + + + + + + + + 57 + + + census designated place (CDP) (suffix) + + + + + + + + + 62 + + + zona urbana (suffix) + + + + + + + + CN + + + corporation (suffix) + + + + + + + + MG + + + metropolitan government (suffix) + + + + + + + + UC + + + urban county (suffix) + + + + + + + + UG + + + unified government (suffix) + + + + + + + + + + CLASSFP + + + Current Federal Information Processing Series (FIPS) class code + + + + + + + C1 + + + An active incorporated place that does not serve as a county + subdivision + + + + + + + + C2 + + + + An active incorporated place that is legally coextensive with a county subdivision + but treated as independent of any county subdivision + + + + + + + + + C5 + + + + An active incorporated place that is independent of any county subdivision and + serves as a county subdivision equivalent + + + + + + + + + C6 + + + + An active incorporated place that is coextensive with or approximates an Alaska + Native village statistical area + + + + + + + + + C7 + + + An incorporated place that is independent of any county + + + + + + + + + C8 + + + + The balance of a consolidated city excluding the separately incorporated place(s) + within that consolidated government + + + + + + + + + C9 + + + An inactive or nonfunctioning incorporated place + + + + + + + + + M2 + + + A military or other defense installation entirely within a place + + + + + + + + + U1 + + + A census designated place with an official federally recognized name + + + + + + + + + U2 + + + A census designated place without an official federally recognized name + + + + + + + + + + + PCICBSA + + + Current metropolitan or micropolitan statistical area principal city indicator + + + + + + + + N + + + Geographic entity is not a principal city of a CBSA + + + + + + + + + Y + + + Geographic entity is a principal city of a CBSA + + + + + + + + + + + PCINECTA + + + Current New England city and town area principal city indicator + + + + + + + N + + + Geographic entity is not a principal city of a NECTA + + + + + + + + + Y + + + Geographic entity is a principal city of a NECTA + + + + + + + + + + + MTFCC + + + MAF/TIGER feature class code + + + + + + + G4110 + + + Incorporated place + + + + + + + + G4210 + + + Census designated place + + + + + + + + + + FUNCSTAT + + + Current functional status + + + + + + + A + + + Active government providing primary general-purpose functions + + + + + + + + + B + + + + Active government that is partially consolidated with another government but with + separate officials providing primary general-purpose functions + + + + + + + + + F + + + Fictitious entity created to fill the Census Bureau geographic hierachy + + + + + + + + + I + + + + Inactive governemental unit that has the power to provide primary special-purpose + functions + + + + + + + + + N + + + Nonfunctioning legal entity + + + + + + + + S + + + Statistical entity + + + + + + + + + + ALAND + + + Current land area (square meters) + + + + + + + 0 + + + 9,999,999,999,999 + + + + + + + + + + + + AWATER + + + Current water area (square meters) + + + + + + + 0 + + + 9,999,999,999,999 + + + + + + + + + + + + INTPTLAT + + + Current latitude of the internal point + + + + + + + -90 + + + 90 + + + + + + + + + + + + INTPTLON + + + Current longitude of the internal point + + + + + + + -180 + + + 180 + + + + + + + + + + + \ No newline at end of file diff --git a/schema-bootstrap/tests/fixtures/utah_places.shx b/schema-bootstrap/tests/fixtures/utah_places.shx new file mode 100644 index 0000000..1051d12 Binary files /dev/null and b/schema-bootstrap/tests/fixtures/utah_places.shx differ diff --git a/schema-bootstrap/tests/test_bootstrap.py b/schema-bootstrap/tests/test_bootstrap.py new file mode 100644 index 0000000..2c2c610 --- /dev/null +++ b/schema-bootstrap/tests/test_bootstrap.py @@ -0,0 +1,279 @@ +"""End-to-end and unit tests, against a real TIGER/Line extract. + +The fixture is 88 Utah places with envelope geometry, carrying its original ISO 19110 +feature catalogue. It is chosen because it exercises the three cases that matter: a +declared domain wider than the extract, a value in the data that no authority declares, +and a column whose whole vocabulary is one value only because of how the file was cut. +""" + +from __future__ import annotations + +import sys +import types +from enum import Enum +from pathlib import Path +from typing import Any + +import duckdb +import pytest + +from schema_bootstrap import bootstrap, inspect_source +from schema_bootstrap.column import Column +from schema_bootstrap.introspect import srid_from_geometry +from schema_bootstrap.render import enum_class_name, field_name, member_name +from schema_bootstrap.sidecar import SidecarResult + +FIXTURE = Path(__file__).parent / "fixtures" / "utah_places.shp" + + +@pytest.fixture(scope="module") +def inspected() -> tuple[list[Column], SidecarResult]: + return inspect_source(FIXTURE) + + +@pytest.fixture(scope="module") +def columns(inspected: tuple[list[Column], SidecarResult]) -> dict[str, Column]: + return {c.name: c for c in inspected[0]} + + +def test_sidecar_is_found(inspected: tuple[list[Column], SidecarResult]) -> None: + sidecar = inspected[1] + assert sidecar.path is not None + assert sidecar.path.name.endswith(".ea.iso.xml") + + +def test_unmatched_metadata_attributes_are_reported( + inspected: tuple[list[Column], SidecarResult], +) -> None: + """A described attribute matching no column must not vanish. + + TIGER's own catalogue documents PCINECTA -- a New England indicator -- which this + shapefile does not carry. Dropping it silently is indistinguishable from the + metadata never having mentioned it, and that is the failure this reports. + """ + assert inspected[1].unmatched == ["PCINECTA"] + assert inspected[1].matched == 16 + + +def test_synthetic_column_is_dropped(columns: dict[str, Column]) -> None: + assert "OGC_FID" not in columns + + +def test_geometry_is_typed_and_carries_its_srid(columns: dict[str, Column]) -> None: + geom = columns["geom"] + assert geom.is_geometry + assert geom.py_type == "Geometry" + assert geom.srid == 4269 + + +def test_sidecar_supplies_descriptions(columns: dict[str, Column]) -> None: + # Every non-geometry column in TIGER's catalogue is defined. + undescribed = [c.name for c in columns.values() if not c.is_geometry and not c.description] + assert undescribed == [] + assert "Federal Information Processing" in (columns["STATEFP"].description or "") + + +def test_declared_domain_is_wider_than_the_extract(columns: dict[str, Column]) -> None: + """The point of preferring a sidecar: it knows values this file does not contain.""" + lsad = columns["LSAD"] + assert lsad.domain is not None + assert len(lsad.domain) == 14 + assert lsad.observed_values is not None + assert len(lsad.observed_values) < len(lsad.domain) + assert lsad.unobserved_values, "a wider domain must report what the extract lacks" + assert lsad.meanings["25"] == "city (suffix)" + + +def test_undeclared_value_is_surfaced_not_swallowed(columns: dict[str, Column]) -> None: + """LSAD 35 is real, in use, and absent from every authority we can consult.""" + assert columns["LSAD"].undeclared_values == ["35"] + assert "35" not in columns["LSAD"].meanings + + +def test_single_value_column_is_still_only_a_lower_bound(columns: dict[str, Column]) -> None: + """STATEFP looks like a one-value enum only because the extract is one state.""" + statefp = columns["STATEFP"] + assert statefp.observed_values == ["49"] + # TIGER cites an external register for STATEFP rather than enumerating it, so there + # is no declared domain -- which is exactly when the emitted warning must appear. + assert statefp.domain is None + + +@pytest.mark.parametrize( + ("type_string", "expected"), + [ + ("GEOMETRY('EPSG:4269')", 4269), + ("geometry('epsg:3857')", 3857), + ('GEOMETRY(\'{"id":{"authority":"EPSG","code":4326}}\')', 4326), + ("GEOMETRY()", None), + ("GEOMETRY('NAD83')", None), + ], +) +def test_srid_recovery(type_string: str, expected: int | None) -> None: + assert srid_from_geometry(type_string)[0] == expected + + +def test_srid_failure_is_reported_not_silent() -> None: + srid, notes = srid_from_geometry("GEOMETRY('NAD83')") + assert srid is None + assert notes, "an unrecoverable CRS must leave a note" + + +@pytest.mark.parametrize( + ("value", "expected"), + [("25", "V_25"), ("G4110", "G4110"), ("city and borough", "CITY_AND_BOROUGH"), ("", "UNNAMED")], +) +def test_member_name(value: str, expected: str) -> None: + assert member_name(value) == expected + + +def test_enum_class_name() -> None: + assert enum_class_name("CLASSFP") == "Classfp" + assert enum_class_name("place_type") == "PlaceType" + + +class TestRenderedModel: + @pytest.fixture(scope="class") + @classmethod + def source(cls) -> str: + return bootstrap(FIXTURE, class_name="UtahPlace") + + def test_subclasses_feature_not_overture_generics(self, source: str) -> None: + """Third-party models subclass Feature; the theme/type generics are Overture's own.""" + assert "from overture.schema.system.feature import Feature" in source + assert "class UtahPlace(Feature):" in source + assert "overture.schema.common" not in source + + def test_imports_overture_system_types(self, source: str) -> None: + assert "from overture.schema.system.geometric import (" in source + assert "from overture.schema.system.numeric import (" in source + assert " int64," in source + # No locally redeclared shims. + assert "class Geometry" not in source + assert "NewType" not in source + + def test_uses_declared_domain_for_enum_members(self, source: str) -> None: + # 14 declared LSAD values, not the 4 this extract happens to contain. + assert '"city (suffix)"' in source + assert '("21", "borough (suffix)")' in source, ( + "a declared-but-unobserved value belongs in the enum" + ) + + def test_long_definitions_are_split_not_truncated(self, source: str) -> None: + """A sourced definition is the reason to read the file; never shorten it to fit.""" + assert max(len(line) for line in source.splitlines()) <= 100 + assert "treated as independent of any county subdivision" in source + + def test_enums_are_documented_enums(self, source: str) -> None: + """Meanings belong in the member, where codegen can read them, not in a comment.""" + assert "from overture.schema.system.doc import DocumentedEnum" in source + assert "(str, DocumentedEnum):" in source + assert "(str, Enum):" not in source + + def test_undeclared_value_is_marked(self, source: str) -> None: + assert '"35"' in source + assert "occur in the data and are NOT declared" in source + + def test_undocumented_member_still_carries_placeholder_documentation(self, source: str) -> None: + """Every member is the same shape, so filling one in is an edit not a restructure.""" + assert '("35", "TODO:' in source + + def test_unsourced_facts_are_todos(self, source: str) -> None: + assert "TODO: what is a UtahPlace, and what does one row represent?" in source + assert "TODO: confirm against the data" in source + + def test_sourced_facts_are_not_todos(self, source: str) -> None: + assert "TODO: what is STATEFP" not in source + assert "Current state Federal Information Processing Series (FIPS) code" in source + + def test_is_syntactically_valid_python(self, source: str) -> None: + compile(source, "", "exec") + + def test_source_rows_populate_the_model(self, source: str) -> None: + """The model must read the file it was bootstrapped from. + + Validation passing proves nothing here: every field is optional and Feature ignores + unknown keys, so a column the model cannot match is dropped without error. Assert + that each value lands instead. + """ + module = types.ModuleType("generated_utah_place") + sys.modules[module.__name__] = module + try: + exec(compile(source, "", "exec"), module.__dict__) + finally: + del sys.modules[module.__name__] + model = module.UtahPlace + + con = duckdb.connect() + con.execute("LOAD spatial") + result = con.execute( + "SELECT * EXCLUDE (OGC_FID, geom), ST_AsText(geom) AS geometry " + f"FROM ST_Read('{FIXTURE}')" + ) + names = [d[0] for d in result.description] + rows = [dict(zip(names, row, strict=True)) for row in result.fetchall()] + assert rows + + def plain(value: Any) -> Any: + return value.value if isinstance(value, Enum) else value + + for row in rows: + instance = model.model_validate(row) + landed = { + name: plain(getattr(instance, field_name(name))) + for name in names + if name != "geometry" + } + assert landed == {name: row[name] for name in landed} + + salt_lake = model.model_validate(rows[0]) + assert salt_lake.name == "Salt Lake City" + assert salt_lake.lsad.value == "25" + + +class TestSecondPublisher: + """USFS Administrative Forests: FGDC CSDGM, not ISO 19110. + + A second publisher is the control on the first. TIGER turned out to be unusually + well-documented, and it is also the only one of the two that ships ISO 19110 at all -- + `.shp.ea.iso.xml` is a Census convention, while the rest of federal GIS ships FGDC in + `.shp.xml`. Reading only ISO would have covered one publisher. + """ + + FIXTURE = Path(__file__).parent / "fixtures" / "usfs_forests.shp" + + @pytest.fixture(scope="class") + @classmethod + def inspected(cls) -> tuple[list[Column], SidecarResult]: + return inspect_source(TestSecondPublisher.FIXTURE) + + def test_fgdc_sidecar_is_read(self, inspected: tuple[list[Column], SidecarResult]) -> None: + assert inspected[1].path is not None + assert inspected[1].path.name.endswith(".shp.xml") + + def test_fgdc_unmatched_is_reported( + self, inspected: tuple[list[Column], SidecarResult] + ) -> None: + """FID is documented by FGDC and dropped by us as a GDAL synthetic; say so.""" + assert inspected[1].unmatched == ["FID"] + + def test_fgdc_enumerated_domain_is_extracted( + self, inspected: tuple[list[Column], SidecarResult] + ) -> None: + region = {c.name: c for c in inspected[0]}["REGION"] + assert region.domain is not None + assert len(region.domain) == 9 + + def test_geometry_description_survives_the_name_mismatch( + self, inspected: tuple[list[Column], SidecarResult] + ) -> None: + """FGDC calls it SHAPE; ST_Read calls it geom. Without aliasing this is lost.""" + geom = next(c for c in inspected[0] if c.is_geometry) + assert geom.description == "Feature geometry." + + def test_undocumented_columns_are_reported_as_such( + self, inspected: tuple[list[Column], SidecarResult] + ) -> None: + """Not every publisher fills the format in. Four of these are genuinely blank.""" + undescribed = {c.name for c in inspected[0] if not c.description} + assert undescribed == {"ADMINFORES", "FORESTNUMB", "FORESTORGC", "SHAPE_LEN"} diff --git a/schema-bootstrap/tests/test_tag_provider.py b/schema-bootstrap/tests/test_tag_provider.py new file mode 100644 index 0000000..16563df --- /dev/null +++ b/schema-bootstrap/tests/test_tag_provider.py @@ -0,0 +1,76 @@ +"""The tag provider: how a `Feature` subclass gets found once it has no theme or type. + +What is under test is the emitted provider's matching rule, which is the one place the +output departs from the workshop template: a generated model is registered as +`package.module:Class`, not re-exported from the package root. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest +from overture.schema.system.discovery import ModelKey + +from schema_bootstrap.cli import main +from schema_bootstrap.tag_provider import ( + check_package, + provider_name, + render_entry_points, + render_tag_provider, +) + +FIXTURE = Path(__file__).parent / "fixtures" / "utah_places.shp" + + +@pytest.fixture +def provider() -> Any: + namespace: dict[str, Any] = {} + exec(render_tag_provider("utah_schema"), namespace) + return namespace[provider_name("utah_schema")] + + +@pytest.mark.parametrize( + ("entry_point", "tagged"), + [ + ("utah_schema.utah_places:UtahPlace", True), + ("utah_schema:UtahPlace", True), + ("utah_schema_extra.models:Other", False), + ("overture.schema.places:Place", False), + ], +) +def test_provider_tags_only_its_own_package(provider: Any, entry_point: str, tagged: bool) -> None: + tags = provider([], ModelKey("m", entry_point, frozenset()), set()) + assert ("utah_schema" in tags) is tagged + + +def test_entry_points_name_what_was_written() -> None: + toml = render_entry_points("utah_schema", "utah_places", "UtahPlace") + assert 'utah_place = "utah_schema.utah_places:UtahPlace"' in toml + assert f'utah_schema = "utah_schema.tags:{provider_name("utah_schema")}"' in toml + assert '[project.entry-points."overture.tag_providers"]' in toml + + +@pytest.mark.parametrize("package", ["feature", "overture", "Utah", "utah-schema", "9lives"]) +def test_unusable_package_names_are_refused(package: str) -> None: + assert check_package(package) is not None + + +def test_cli_writes_provider_beside_model( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + output = tmp_path / "utah_places.py" + argv = [str(FIXTURE), "--class-name", "UtahPlace", "-o", str(output)] + assert main([*argv, "--tag-provider", "utah_schema"]) == 0 + assert "def utah_schema_provider(" in (tmp_path / "tags.py").read_text() + assert 'utah_place = "utah_schema.utah_places:UtahPlace"' in capsys.readouterr().out + + +def test_cli_does_not_overwrite_an_existing_provider(tmp_path: Path) -> None: + (tmp_path / "tags.py").write_text("# edited by hand\n") + argv = [str(FIXTURE), "--class-name", "UtahPlace", "-o", str(tmp_path / "m.py")] + with pytest.raises(SystemExit): + main([*argv, "--tag-provider", "utah_schema"]) + assert (tmp_path / "tags.py").read_text() == "# edited by hand\n" + assert not (tmp_path / "m.py").exists() diff --git a/uv.lock b/uv.lock index eb0dab1..9c890d1 100644 --- a/uv.lock +++ b/uv.lock @@ -2,14 +2,33 @@ version = 1 revision = 3 requires-python = ">=3.12" resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] +[manifest] +members = [ + "my-schema", + "schema-bootstrap", + "workshop", +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + [[package]] name = "anyio" version = "4.13.0" @@ -249,6 +268,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] +[[package]] +name = "ast-serialize" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/1e/4f6082cdd6e5a29093513e9a3eabc5ed1c5331a9a84386b2fece80a00a48/ast_serialize-0.11.2.tar.gz", hash = "sha256:976a5bd75845d22f4b52905ddf53ab669ef1b14dba7735f5512841a2ef2b5450", size = 954387, upload-time = "2026-09-13T18:48:55.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/2e/beec3364eef4b01793a676d8cd16e9014c42044a5505000ceae3955e33fa/ast_serialize-0.11.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f6a8dfc5ab204a706f6e5d39c6f77c18c27ef084fa2081803a64a9160ce89277", size = 897089, upload-time = "2026-09-13T18:47:22.69Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d7/ef56443df2891c6ba2c4019c2cb3dcaf97c9948da6d963068e04e8dac6ea/ast_serialize-0.11.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:cb073bfa15742699d408ac50f60878383b5665ae1791d1b6799ea6f08633cd77", size = 1235218, upload-time = "2026-09-13T18:47:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/42/8d/cff58d17ba1d0272ff0b7ab5d3bdfcf8f47317eb0f47c001d394bffebf95/ast_serialize-0.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1d6ad94edbe93bf1dabc06c9f37d55b898fdabc456aa6d7ced5e23c14f795f32", size = 1216399, upload-time = "2026-09-13T18:47:26.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/d2/a1da7675af5f42335c36e4da6d86ef4fd7168cead18de81df0a2d6faeb1a/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40b2801cf2221bd922d9f69d2f0ebc373c3db47207315d525b2d87fa161a2af4", size = 1282064, upload-time = "2026-09-13T18:47:27.787Z" }, + { url = "https://files.pythonhosted.org/packages/97/89/5a400a13b2c9c0152ebb5ad45408a3fe5e4e60e325d3ac4e5cf6e915a0cc/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd666cebd6ab3b3c0fd348a6202c26e18a401ee34293c3804d3472266bc146f6", size = 1285864, upload-time = "2026-09-13T18:47:29.667Z" }, + { url = "https://files.pythonhosted.org/packages/02/b8/80a381c70fd49f0316fb0383c4f9e4c13e81b010b64889bd45898ce8f5f4/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d01f61352c96370febf6c0dbd488dee9183a731fb2702170da9163ae317cded", size = 1554755, upload-time = "2026-09-13T18:47:31.257Z" }, + { url = "https://files.pythonhosted.org/packages/90/97/dcaa34a32d2db789221c125b3eb10feb5089715fe53d9874d627afc26231/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0fd40c668b0fa19b8fdb61d9e63d547e2e19cfbfe053a51ef0b6c37070298a8", size = 1301807, upload-time = "2026-09-13T18:47:32.714Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/84a22420cb312642d7d31547c644d09a3d101418c6d6b9ef2ec30735cf11/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa819d7c14c8e4153dcd84671826331538be7cbe460383fc6386f5eea5bd234", size = 1301941, upload-time = "2026-09-13T18:47:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/20/8a/aa5f3dcf1aed9678c25982f40d366004e3c0cac47bc0c240f6b837dcbb1f/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a9ffa8a197a721f07a352d0be6185f5b3e6f9aaebfdb66169ed652108531ae3b", size = 1307910, upload-time = "2026-09-13T18:47:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/78/79/91a5102797fe3dc992171382d8579bcb33cbd1424b864ad3117ac43fb3fe/ast_serialize-0.11.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00119a8fb8c1dc0f1fab023f4d8071fa49e3b0208ee54d589fd463c16ab0124e", size = 1356258, upload-time = "2026-09-13T18:47:37.984Z" }, + { url = "https://files.pythonhosted.org/packages/51/52/54eeef9918e187ced417c4363eecea66975314cd5b9c91759eef7f7b714b/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0de02520c11391a026e62987a9aa2c3c2ff01545155059ddf0c4bdf2c5ecbe9f", size = 1459057, upload-time = "2026-09-13T18:47:39.891Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cb/fd84b52b15d42f2423319cffd1fb7f1e9df5d5198e69ab0b449c450254cf/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4e4558956b6a0fb35e18fba58f7d1810b1f2c0e6b52352572cd5dfb6b4ef33a", size = 1562447, upload-time = "2026-09-13T18:47:41.727Z" }, + { url = "https://files.pythonhosted.org/packages/be/92/9fb34f2e64b84a63cca92fb86bd0847b995a63b67477f44c20502fb60352/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6061a54f39e82a9f2cbcb9c268fc441890e4818a6636473caa4f4063254e0750", size = 1556423, upload-time = "2026-09-13T18:47:43.357Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/c43c44449e7ebc4e83ebd48750088fb06234622faa2d62d2a6dc8970d2a3/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:85fbb01e83967a126d71f679f2b9528ef0912cb0854aa1a4657314c34e255b57", size = 1687156, upload-time = "2026-09-13T18:47:44.995Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a7/9e520f4a79b639da9ee20c1e747c3d739329e902fc55ac38065f25419f56/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7aaaffc32905159774a107d3cf33dad59bd41b7a0d1bc9885532186753ee7439", size = 1481008, upload-time = "2026-09-13T18:47:46.602Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/bdd3989f19de09cffcd8179c131f6741a5a8619705fc75b09541ff61530b/ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:08eda88a0f290a36c38cab33df8bf7e35eb95bc802ca5beb2c8fcda471a7d10c", size = 1501597, upload-time = "2026-09-13T18:47:48.265Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8a/ca2dce2950875a4ef1d7c298196f803b0adcdb7c15ed0cecc71d84bccd70/ast_serialize-0.11.2-cp314-cp314t-win32.whl", hash = "sha256:76cc294246e60a914326b4ca88c6a5ea89c064906614aaf1537ce82f09e9449f", size = 1119503, upload-time = "2026-09-13T18:47:49.896Z" }, + { url = "https://files.pythonhosted.org/packages/5a/12/3f38e3613d07c46f9f81c5b1352748c6552397cc52825502e2c6ae44c6ea/ast_serialize-0.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:43b51e6ebe6549bf21416c3c78ee886147b80875a87cc6f69e303dde0d75be0b", size = 1156828, upload-time = "2026-09-13T18:47:51.454Z" }, + { url = "https://files.pythonhosted.org/packages/22/90/f89a4f67428a261daafdb69a0d0132c27933268702d1ba47e0b61c51aff1/ast_serialize-0.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8df32ad4ff7843734a6c2f067ee974f6d3109ee5a2c3e1a9d2f79347bd282a9a", size = 1128298, upload-time = "2026-09-13T18:47:53.008Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/a1962188abf0e62d84d55892bb044347e434711763b9a1d4ad867a70c1be/ast_serialize-0.11.2-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:ab924ba260efd7509492f272d4e236d24564033f20c005d7c63a107c6a76fc85", size = 1235457, upload-time = "2026-09-13T18:47:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ad/439c2959150718446af76fbe2f4000f35eba9869ef8564f3d9a3d0b1c370/ast_serialize-0.11.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:a586be418eb70a9f1396cea29ddac8f4b9bf277fb73ea2340db31e218bc00f32", size = 1215705, upload-time = "2026-09-13T18:47:56.178Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d8/2c6542fc3e7c56a0a25d8d12d034d5a2d2e1900e292567b1c1dca8e83124/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8532f20916fa3189d4d785ef2a62d93c4d651ec9c5bffda66d2fc36898351f34", size = 1282530, upload-time = "2026-09-13T18:47:57.619Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ad/6f6755cd0842db46c3b10b1e4735f14aad78d71dea4753eb46933101711b/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee732ae167e686d1d3c00f98d7d82b23138304694f0441b14d7ddf9c0f8a921c", size = 1287792, upload-time = "2026-09-13T18:47:59.227Z" }, + { url = "https://files.pythonhosted.org/packages/03/40/5da672f5dd23fb7dc0c884c97711e56a3540f2fe3c4355a81f8beb385911/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75a1c7f46b9c19fc0ae01ca6fd076301628faa2ed7a8edbd55c6353c483946a3", size = 1557971, upload-time = "2026-09-13T18:48:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c7/2bb25684f697801eb72866fdb94ed5edbff3867ce878b0e542a4a5b9dab9/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdf31a0bb85ea2575cc91669f005e6647d2efed491231c4dc1497bc9a5b3aa6", size = 1303230, upload-time = "2026-09-13T18:48:02.337Z" }, + { url = "https://files.pythonhosted.org/packages/d8/85/754681846f26e0ff1da729b1ffe3171e93c22f0aa6ec3cea5b14e3703846/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b78e6fdef3b06c86ed263e1962fee5a7b9d2d158e738b212d13b2c605ee12f5", size = 1302271, upload-time = "2026-09-13T18:48:03.915Z" }, + { url = "https://files.pythonhosted.org/packages/fb/dc/f5521d8cb44b69095c3982ae3658a12c403e0efa19e51aeb9c8a79dff60c/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:8d62a47714c8bc432b9fabcc29989c815c5da17327d35151f2fd0d85c2a7a5ff", size = 1309529, upload-time = "2026-09-13T18:48:05.562Z" }, + { url = "https://files.pythonhosted.org/packages/73/0d/649182c7fd7c4f782279bed514de2dd67e48a5afecb605a098d64fdc01fd/ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a5ffa70e76191dcf240d3c43e20c93b3bfd26f54d89148c762d57837f5bcd2c", size = 1356869, upload-time = "2026-09-13T18:48:07.534Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cc/aff4d84c16afa742d13a75384127c7d24594dc8c304f0558a15924fd51af/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:bfbe47a3a7c368f28836e78b2440a3643ac0ec4c67d9fe53588e1448f0a3d35d", size = 1460006, upload-time = "2026-09-13T18:48:09.162Z" }, + { url = "https://files.pythonhosted.org/packages/94/a7/891cbec2e5e0d7159196159d3ff0646622f3120ff4576c839ac2dd56c719/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:7f1823275b246f9c7d373be6879e4eec09686948895d4ad083f4b27fd7e4da70", size = 1562935, upload-time = "2026-09-13T18:48:10.978Z" }, + { url = "https://files.pythonhosted.org/packages/45/c4/2c8c4498340ea9aff87a9fd408309aa25d56dd51d7bbddfdb46a3c31424a/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:57c0f5cb0021a5beb1e5e4d6e840ae2f23a28909703ef4d256a144cc1ad3d437", size = 1557109, upload-time = "2026-09-13T18:48:12.616Z" }, + { url = "https://files.pythonhosted.org/packages/0d/8b/c5d4e5226fa18885fe17f949aee3ab1aeb8389c384d946ec1b7c9489cc94/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:cd320a5c4f1f2742af97eea22954f776379175c5ef2504801e9a155f2ff9a4d7", size = 1691603, upload-time = "2026-09-13T18:48:14.293Z" }, + { url = "https://files.pythonhosted.org/packages/73/d6/1d2ca472586f9e3416a289a22f36eeb6dd6f47d77b1a4aba358405babbc7/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:13b13afe32e845c86a573497729e1b7ddeb26c572c78bf50ece51da23b8fad5e", size = 1483053, upload-time = "2026-09-13T18:48:15.789Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/d3703a7c1e3c76b144ac9349a54d3926d0749918a8dc13a66cede208b8ec/ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:9d80a81ec84660422579bdb8e789f656a794b48c7a1ae1261f6bd8bc1897d17d", size = 1502499, upload-time = "2026-09-13T18:48:17.405Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e4/d974e55c2e247ef26ed1df01c74940583db9a5b3a8bcaad5732c6e2047fb/ast_serialize-0.11.2-cp315-abi3.abi3t-win32.whl", hash = "sha256:af8c003ce721b0099dd55cef4ba733500fc3054ea0cc8565d8957aaf7cccdeb4", size = 1119739, upload-time = "2026-09-13T18:48:19.005Z" }, + { url = "https://files.pythonhosted.org/packages/0d/00/d229443488e095054d5e0c0cc20689a2633b899d735849ff1b2c8e4f0cbf/ast_serialize-0.11.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:554d117cb916d8032d85007c654d179efbbfd446174c048062778136a922944f", size = 1158602, upload-time = "2026-09-13T18:48:20.524Z" }, + { url = "https://files.pythonhosted.org/packages/11/75/389fc1a6cfa0c4b2ce522f47d8401329d8bb11732e516d46465960fef1d9/ast_serialize-0.11.2-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:d60515335750d431e462af6e722bb55720a5e7827192777bddfd9c4376065a4d", size = 1128842, upload-time = "2026-09-13T18:48:22.052Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/c4f36898f19c728d091cdfdf960c9488e8d82bbe2e49ba13c05f43907a5d/ast_serialize-0.11.2-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:89499a439955931281986e97ca4dd3c064bf0d2e0027c0017344eb86667733a1", size = 897204, upload-time = "2026-09-13T18:48:23.695Z" }, + { url = "https://files.pythonhosted.org/packages/b1/54/f67120006fc73a55b6d057d4662d061fbb4eceafce3047c76ca8b382eb11/ast_serialize-0.11.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:daadf1c3e0224621607ffe16f1379e4bd372271ed2e1db8a67878f0bab3ef7e4", size = 1240734, upload-time = "2026-09-13T18:48:25.287Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7e/8f2ab68bddbe58a66fbbaad87beeae3e7d7edddb17263d1fc423936cf34d/ast_serialize-0.11.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1844ed9a487fb3de7325c52ddb33f2918b66b65cd54d3f8d83d23785ffe99fa4", size = 1228053, upload-time = "2026-09-13T18:48:26.788Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1b/8a69ab68f4c1603819f0481d756abdd8caf27cec7f1d77caa71007ebe997/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b17869f4ba261a5fa468a753328a548f4dbaf74b4eadae9e28aff66df7f1425b", size = 1292542, upload-time = "2026-09-13T18:48:28.295Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ce/872f2e00f0467c289e483f0a34543463347243a2d0632748d89fcee5e0dc/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feb16d9c2a720e0120c58dd5d6e7b3c7c86b43249b60a3bc212bcb8fa031e2dd", size = 1294791, upload-time = "2026-09-13T18:48:29.969Z" }, + { url = "https://files.pythonhosted.org/packages/3a/82/36277c12af861c64b375c316135d8feffe3f400568463a8d2b2de4c2c4fb/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3109fe4805384effc8d0f8e41fbf875aa8f389af91b4348c1cfb60ea6e4cb82", size = 1567583, upload-time = "2026-09-13T18:48:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d7/ec643df91cea8bcbcb4e8011d6a8b08e5119b84f9554879f3e3c786d29d1/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abdb3e49ba053c3486ac1263bee9f16cc9a4a8abd9f8c90bfc21e3669f3ad9d1", size = 1312878, upload-time = "2026-09-13T18:48:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/04/6f/4c992cd7841ba589fefb14ddc9aff2f6db7f2a615d4074f9ad04115b5ce0/ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7004ba572f09be34342ccb98dcd4bad5707d3d81adc8cb4c3f685d2a2c51bbc", size = 1312642, upload-time = "2026-09-13T18:48:35.294Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/22aaa209c231a83cfea004fd67dee7a7a54da3f169c6c460b14b96887385/ast_serialize-0.11.2-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:59c25f47524efa052971b860e128b1add0c94ede7dd16b2962952c85c3582365", size = 1319776, upload-time = "2026-09-13T18:48:36.866Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f7/d4685fb54d10108ce44d3bc893ef670854d61645d47ed96d73524db90c23/ast_serialize-0.11.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3a367e0e05ed2d1b747ceb07aa728a8c204cc008b589127e9bd4f40053d7575", size = 1365324, upload-time = "2026-09-13T18:48:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/42/3a/250643ffad02bda520c50a9a5f02a5d43259a06f34ce393c91761d134d7e/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:00bbf1f6669f813b48925b759f7ae4591067d456d443924055cab386e7e0a719", size = 1467653, upload-time = "2026-09-13T18:48:40.348Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/af66a646b9b7f8fdec95ce83fc7b1fe538b06864bc79bd554ac4fae2e6ea/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ec1c20f89c3e0d83576e3c06f79375ce936266591fe0d5fd969914af3185cbaa", size = 1571914, upload-time = "2026-09-13T18:48:41.968Z" }, + { url = "https://files.pythonhosted.org/packages/34/82/77a9714564b9e8800087a8afec41527c65c39e49282baae2ac847b9c1c6a/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c58bb119b73657fdc5569692f316e1e25ca114bd62f7782eb527c6be438ba3a9", size = 1569862, upload-time = "2026-09-13T18:48:43.701Z" }, + { url = "https://files.pythonhosted.org/packages/65/06/fa77b52f46b9bd6dcd8ff2b880e3781f8c1a316bb1342bc3de92907c6f96/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:f739e0b601be7300c5697a2573d9200bd1db74b34ab111ef9537b9d5dcd7f106", size = 1699020, upload-time = "2026-09-13T18:48:45.261Z" }, + { url = "https://files.pythonhosted.org/packages/e1/09/239c83153c7e0798e5867d6909cb06f53dccfef02f6999c8e2e21ecb98c3/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:cae5addfbb54cc1d47fe947ef9138e9d83849ed1cbc72b819cf36d96a2315b07", size = 1492869, upload-time = "2026-09-13T18:48:46.922Z" }, + { url = "https://files.pythonhosted.org/packages/2f/eb/6108fb9a43fc7ab5529856e38e33c6e3e064fbfe375fdcbb208c7cd5438d/ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2fa3be25f7f5351b1b39c9f8a52779b2dbf21199efbae564b4746422e8edca4e", size = 1511621, upload-time = "2026-09-13T18:48:48.667Z" }, + { url = "https://files.pythonhosted.org/packages/8a/82/60367e58ef346a41ebc90d3f28593c1b8f5c2cb5314c7b2bbd98910ee131/ast_serialize-0.11.2-cp39-abi3-win32.whl", hash = "sha256:d70556a2f9230a44c99a655774cde823f056efc34466eabfb4085f0cb1ea9f99", size = 1125873, upload-time = "2026-09-13T18:48:50.661Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/b419c3205ce1143ba7c69baef4f0ba43c14d8712113bf34f9e0d27d609be/ast_serialize-0.11.2-cp39-abi3-win_amd64.whl", hash = "sha256:b9065dd23131a23b41f5bab3bf4e9b3c350a3fe8e36e8200eded9b729fcea484", size = 1165434, upload-time = "2026-09-13T18:48:52.169Z" }, + { url = "https://files.pythonhosted.org/packages/91/a7/c8bbb2173f7a7131b3b2412035b2d814ab5ef2ce9799bd06f07c451640e4/ast_serialize-0.11.2-cp39-abi3-win_arm64.whl", hash = "sha256:dab599cbdcb7b45b18c41fad746645580b3a24357082b7f0e8921cd373804f27", size = 1136031, upload-time = "2026-09-13T18:48:54.04Z" }, +] + [[package]] name = "asttokens" version = "3.0.1" @@ -622,33 +705,55 @@ 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 = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + [[package]] name = "duckdb" -version = "1.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/66/744b4931b799a42f8cb9bc7a6f169e7b8e51195b62b246db407fd90bf15f/duckdb-1.5.2.tar.gz", hash = "sha256:638da0d5102b6cb6f7d47f83d0600708ac1d3cb46c5e9aaabc845f9ba4d69246", size = 18017166, upload-time = "2026-04-13T11:30:09.065Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/de/ebe66bbe78125fc610f4fd415447a65349d94245950f3b3dfb31d028af02/duckdb-1.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e6495b00cad16888384119842797c49316a96ae1cb132bb03856d980d95afee1", size = 30064950, upload-time = "2026-04-13T11:29:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8a/3e25b5d03bcf1fb99d189912f8ce92b1db4f9c8778e1b1f55745973a855a/duckdb-1.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d72b8856b1839d35648f38301b058f6232f4d36b463fe4dc8f4d3fdff2df1a2e", size = 15969113, upload-time = "2026-04-13T11:29:14.139Z" }, - { url = "https://files.pythonhosted.org/packages/19/bb/58001f0815002b1a93431bf907f77854085c7d049b83d521814a07b9db0b/duckdb-1.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a1de4f4d454b8c97aec546c82003fc834d3422ce4bc6a19902f3462ef293bed", size = 14224774, upload-time = "2026-04-13T11:29:16.758Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2f/a7f0de9509d1cef35608aeb382919041cdd70f58c173865c3da6a0d87979/duckdb-1.5.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce0b8141a10d37ecef729c45bc41d334854013f4389f1488bd6035c5579aaac1", size = 19313510, upload-time = "2026-04-13T11:29:19.574Z" }, - { url = "https://files.pythonhosted.org/packages/26/78/eb1e064ea8b9df3b87b167bfd7a407b2f615a4291e06cba756727adfa06c/duckdb-1.5.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99ef73a277c8921bc0a1f16dee38d924484251d9cfd20951748c20fcd5ed855", size = 21429692, upload-time = "2026-04-13T11:29:22.575Z" }, - { url = "https://files.pythonhosted.org/packages/5b/12/05b0c47d14839925c5e35b79081d918ca82e3f236bb724a6f58409dd5291/duckdb-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:8d599758b4e48bf12e18c9b960cf491d219f0c4972d19a45489c05cc5ab36f83", size = 13107594, upload-time = "2026-04-13T11:29:25.43Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2c/80558a82b236e044330e84a154b96aacddb343316b479f3d49be03ea11cb/duckdb-1.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:fc85a5dbcbe6eccac1113c72370d1d3aacfdd49198d63950bdf7d8638a307f00", size = 13927537, upload-time = "2026-04-13T11:29:27.842Z" }, - { url = "https://files.pythonhosted.org/packages/98/f2/e3d742808f138d374be4bb516fade3d1f33749b813650810ab7885cdc363/duckdb-1.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4420b3f47027a7849d0e1815532007f377fa95ee5810b47ea717d35525c12f79", size = 30064879, upload-time = "2026-04-13T11:29:30.763Z" }, - { url = "https://files.pythonhosted.org/packages/72/0d/f3dc1cf97e1267ca15e4307d456f96ce583961f0703fd75e62b2ad8d64fa/duckdb-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb42e6ed543902e14eae647850da24103a89f0bc2587dec5601b1c1f213bd2ed", size = 15969327, upload-time = "2026-04-13T11:29:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e0/d5418def53ae4e05a63075705ff44ed5af5a1a5932627eb2b600c5df1c93/duckdb-1.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98c0535cd6d901f61a5ea3c2e26a1fd28482953d794deb183daf568e3aa5dda6", size = 14225107, upload-time = "2026-04-13T11:29:35.882Z" }, - { url = "https://files.pythonhosted.org/packages/16/a7/15aaa59dbecc35e9711980fcdbf525b32a52470b32d18ef678193a146213/duckdb-1.5.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:486c862bf7f163c0110b6d85b3e5c031d224a671cca468f12ebb1d3a348f6b39", size = 19313433, upload-time = "2026-04-13T11:29:38.367Z" }, - { url = "https://files.pythonhosted.org/packages/bd/21/d903cc63a5140c822b7b62b373a87dc557e60c29b321dfb435061c5e67cf/duckdb-1.5.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70631c847ca918ee710ec874241b00cf9d2e5be90762cbb2a0389f17823c08f7", size = 21429837, upload-time = "2026-04-13T11:29:41.135Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0a/b770d1f60c70597302130d6247f418549b7094251a02348fbaf1c7e147ae/duckdb-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:52a21823f3fbb52f0f0e5425e20b07391ad882464b955879499b5ff0b45a376b", size = 13107699, upload-time = "2026-04-13T11:29:43.905Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cf/e200fe431d700962d1a908d2ce89f53ccee1cc8db260174ae663ba09686b/duckdb-1.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:411ad438bd4140f189a10e7f515781335962c5d18bd07837dc6d202e3985253d", size = 13927646, upload-time = "2026-04-13T11:29:46.598Z" }, - { url = "https://files.pythonhosted.org/packages/83/a1/f6286c67726cc1ea60a6e3c0d9fbc66527dde24ae089a51bbe298b13ca78/duckdb-1.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6b0fe75c148000f060aa1a27b293cacc0ea08cc1cad724fbf2143d56070a3785", size = 30078598, upload-time = "2026-04-13T11:29:49.828Z" }, - { url = "https://files.pythonhosted.org/packages/de/6a/59febb02f21a4a5c6b0b0099ef7c965fdd5e61e4904cf813809bb792e35f/duckdb-1.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35579b8e3a064b5eaf15b0eafc558056a13f79a0a62e34cc4baf57119daecfec", size = 15975120, upload-time = "2026-04-13T11:29:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/09/70/ce750854d37bb5a45cccbb2c3cb04df4af56aea8fc30a2499bb643b4a9c0/duckdb-1.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea58ff5b0880593a280cf5511734b17711b32ee1f58b47d726e8600848358160", size = 14227762, upload-time = "2026-04-13T11:29:55.564Z" }, - { url = "https://files.pythonhosted.org/packages/28/dc/ad45ac3c0b6c4687dc649e8f6cf01af1c8b0443932a39b2abb4ebcb3babd/duckdb-1.5.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef461bca07313412dc09961c4a4757a851f56b95ac01c58fac6007632b7b94f2", size = 19315668, upload-time = "2026-04-13T11:29:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b1/1464f468d2e5813f5808de95df9d3113a645a5bfa2ffcaecbc542ddae272/duckdb-1.5.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be37680ddb380015cb37318e378c53511c45c4f0d8fac5599d22b7d092b9217a", size = 21434056, upload-time = "2026-04-13T11:30:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/ce/32/6673607e024722473fa7aafdd29c0e3dd231dd528f6cd8b5797fbeeb229d/duckdb-1.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:0b291786014df1133f8f18b9df4d004484613146e858d71a21791e0fcca16cf4", size = 13633667, upload-time = "2026-04-13T11:30:04.05Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e3/9d34173ec068631faea3ea6e73050700729363e7e33306a9a3218e5cdc61/duckdb-1.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:c9f3e0b71b8a50fccfb42794899285d9d318ce2503782b9dd54868e5ecd0ad31", size = 14402513, upload-time = "2026-04-13T11:30:06.609Z" }, +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/19/e57151753576373c6696a12022648546cca6038e8833fda2908ee2342d9b/duckdb-1.5.5.tar.gz", hash = "sha256:72f33ee57ca7595b23957671a2cc7f7fe2be0ecc2d68f63abedcfcaa3a5c1238", size = 18066741, upload-time = "2026-07-22T10:55:17.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/40/2e05d324400fdaa5656c9f48d6298da421cb034d85e509fa0e6e325cf04b/duckdb-1.5.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d4dd65f8941a604b947e0b9b4b4f7165988e29a23ec0b69b4038520956d9933e", size = 32753858, upload-time = "2026-07-22T10:54:05.514Z" }, + { url = "https://files.pythonhosted.org/packages/79/15/5ceb58ffb5bb8a62b3fd7abb39c41467cdf94850ece02e6d88664dfc75ce/duckdb-1.5.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33db46679b071f108d57139493dee2d37e1f5efcf5c5c039c2969eed11a6c8a7", size = 17368293, upload-time = "2026-07-22T10:54:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5c/bf02da0b354fe83cca4f95a4fbf762181af466f7d551ab2a093f7698882a/duckdb-1.5.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f0b88535a5d86fdd63dba6ea02ab68c003dfb9e4892b11256ef24c4da208baae", size = 15509131, upload-time = "2026-07-22T10:54:12.228Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a9/5f1f09da421d8e930e0b063d11c1b3f90363f40ede74438cd188afdd13a2/duckdb-1.5.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f316eae2323d9a851883fdf2dee91c1f9efe251ab33e14a2272f82a913422ed6", size = 19391959, upload-time = "2026-07-22T10:54:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/6549769f158126fa64fd6c1ac2eb59a18282146c939867a3eb31b7c1db07/duckdb-1.5.5-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a6d2d11859d82a936ebdcb30ce3d8a1cbb3e990bff05c12abb9b54c44fa7bd1", size = 21510909, upload-time = "2026-07-22T10:54:19.681Z" }, + { url = "https://files.pythonhosted.org/packages/af/b7/5753b41d3124838f868f9f523362812d9fc45409e9e4dd70dcbb0a25826e/duckdb-1.5.5-cp312-cp312-win_amd64.whl", hash = "sha256:ddfbdb096c11d51ee22492397d342c90a82e62c5d09961477895934d0a25372f", size = 13168544, upload-time = "2026-07-22T10:54:22.789Z" }, + { url = "https://files.pythonhosted.org/packages/5c/28/44b679c7d46245f8398feae7edac959d1b83d4eb143e25b3fce0630b78bd/duckdb-1.5.5-cp312-cp312-win_arm64.whl", hash = "sha256:2725d2b9ace3a4e75d72fc5a239f6a44b502c580edadb8fb2676db772c5f9282", size = 13988684, upload-time = "2026-07-22T10:54:26.003Z" }, + { url = "https://files.pythonhosted.org/packages/47/37/4a38116e7700720fd152c666292214fd3abdf916496991296d8d1f66efbf/duckdb-1.5.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd98829b67788609017e65c761bd42a5dd0f9129441bed8bda4d6881ccf819f0", size = 32754294, upload-time = "2026-07-22T10:54:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/7d392f1ba1eee0eaf4ab4c8c7a604bfe3536cd63f979cf5c98798664f807/duckdb-1.5.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feead93c56679b79592d437c62975d39cb67adedffa7592c763baf8160ac7366", size = 17368211, upload-time = "2026-07-22T10:54:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a5/0a6f4fa60562faa615e55e15bd1953a2f2b17a8edd8105e5cda215e43457/duckdb-1.5.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:49c963d9469373d7aba8d750d9ea565ab823e94166efed953f184dd9b169b98c", size = 15509136, upload-time = "2026-07-22T10:54:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cb/023c89f51978545b9fab318581bba0c457a58e7530d2d933e54ae7d8647c/duckdb-1.5.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a736217825461732b5442d05a220f3da2e23a0dae114efbf08c9bf171b53098a", size = 19392147, upload-time = "2026-07-22T10:54:39.551Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c5/41bef391fb8b23dbc133c9f2ba016e7a7a8124513d2cc1b430f1897d87e4/duckdb-1.5.5-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:078e6a60dd8eedde5832f45422ca5c4a6b8c837aeabd8a56ca0b7d933f588053", size = 21511060, upload-time = "2026-07-22T10:54:42.788Z" }, + { url = "https://files.pythonhosted.org/packages/07/9f/c44dfc1f924ac29b3252dc1b91393c01d009dbfe9f8ed33f10b986151bd1/duckdb-1.5.5-cp313-cp313-win_amd64.whl", hash = "sha256:6826504277dba513c0c5d71d828456c94d729c9d2482f94b2e289f90a9167e28", size = 13168028, upload-time = "2026-07-22T10:54:46.127Z" }, + { url = "https://files.pythonhosted.org/packages/ca/88/591384b2cd59abddd6f5dc175e60374f9abae6064429f0c4402854c10f44/duckdb-1.5.5-cp313-cp313-win_arm64.whl", hash = "sha256:baa9c5702002fabb559ded2a39008f9f421fcbc7237d388b8213eff1e08858de", size = 13989955, upload-time = "2026-07-22T10:54:49.262Z" }, + { url = "https://files.pythonhosted.org/packages/3e/56/12c65bfa2d2605b81981b264788891bcf11ec72227889554cead5d8d13b9/duckdb-1.5.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8e6413dd40facb7b8ab21bd844450cd8f549b29e138635be9cf090ef4d2049e2", size = 32761946, upload-time = "2026-07-22T10:54:53.412Z" }, + { url = "https://files.pythonhosted.org/packages/b9/46/682ce155f17e0d2822d4f13ee3db9ca4b5b7c2da61b841b2629035e1f4bc/duckdb-1.5.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:64078acfd16541132ac6e191eb81b2845554444a0305cc1aa581ba107e514aa8", size = 17375069, upload-time = "2026-07-22T10:54:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/39/ce/a24bcbd3289c8f305a430759c5fc12242740b4af3e17f7593f3a34e333d2/duckdb-1.5.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c11775cc99a447618d5f1840126db17f2652f3eae05529df4f81f40e2df7151", size = 15519791, upload-time = "2026-07-22T10:55:00.681Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/3a01afbc615c1d418c0de58a6b68ac5ce2a8563232c0464bfbc2ce552398/duckdb-1.5.5-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77bbc1e6ba12e1e06f9020117bdf848627ecfdf36f907550e62e008e6109dece", size = 19398251, upload-time = "2026-07-22T10:55:04.168Z" }, + { url = "https://files.pythonhosted.org/packages/a1/43/3a5e81d1728f4d234c79bfe385808ee7c04834f7c37a4b5c257459c25614/duckdb-1.5.5-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbf0f2d48b43c6c304d00463b463c27ead6c4b01c3c1816b750f728decf71afe", size = 21513851, upload-time = "2026-07-22T10:55:07.864Z" }, + { url = "https://files.pythonhosted.org/packages/91/41/fc7c829172c60ca22485251eab285f4f1a0d87b486a024c726f21471d86e/duckdb-1.5.5-cp314-cp314-win_amd64.whl", hash = "sha256:9dc826c4b50e64f6c4e4d07a3a9cb075ef70ba3899dc43ec5493dc3d7b04b353", size = 13691858, upload-time = "2026-07-22T10:55:11.181Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2c/95d9216b79e9273689d7ebce125a54503ed0c9bd7da931f0265888e99779/duckdb-1.5.5-cp314-cp314-win_arm64.whl", hash = "sha256:63e48d4b74b15aeacd688976432a7225163df8c226eddeb8536bba2d4d4ff433", size = 14470180, upload-time = "2026-07-22T10:55:14.445Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, ] [[package]] @@ -843,6 +948,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "ipykernel" version = "7.2.0" @@ -1304,6 +1418,107 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] +[[package]] +name = "librt" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/9b/356320fbae2ac8467e21c5e73e1389c80468e4998c62cc7d3536cc51b614/librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162", size = 214338, upload-time = "2026-08-07T10:49:42.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/39/99c25030e782bdfb7a21be8c05254806a2e4bbb05c8d50c2a2130acbfa05/librt-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e87bc679f86a99aa3b26e3c78eeb821a247c9a28eae48eaafcc32c3bf4c3bb9e", size = 151021, upload-time = "2026-08-07T10:47:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/14/43/f4b1bd1b2888798a1409808889a25ea1ba49eaabce7d681ed27734c2df9d/librt-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71599e011ac880e8e45d46047d714871894c7d4ab6f25626f8d4f89da21f368d", size = 155267, upload-time = "2026-08-07T10:47:01.311Z" }, + { url = "https://files.pythonhosted.org/packages/0c/db/3ad9c965c72f1e1d6beeec44ec10a54e17be8ae042fbb4baade16cbadced/librt-0.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c802434092b769b1d613ed2e13fac15fbfce1934a74bd10283b03c0fae231cd1", size = 503136, upload-time = "2026-08-07T10:47:02.45Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/5888a6d76acd62ebce66c61b74d94e9370b9c32929f111e487bb6546f8ed/librt-0.15.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5500eeae393a184d14e1f35645962c27129d20c81afa4069e6ef826ebc2b3aaa", size = 496670, upload-time = "2026-08-07T10:47:03.675Z" }, + { url = "https://files.pythonhosted.org/packages/29/39/ab57cc2f5b276156da02bb7f5a8921bada1cb1993ffec99acf811c602c23/librt-0.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ecfc32dfb46fb7b565bcd6abf9412acf978775a998273d22888a6d7953730dd", size = 513688, upload-time = "2026-08-07T10:47:04.981Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/bdbb0b648b5c2befb031f4c6f3b1dd857415e8fb492a25a3c764a6681e6c/librt-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cc46cfd15022e35084355478c9ac809d90b1152222706ac9a7655ec21df6fa", size = 531904, upload-time = "2026-08-07T10:47:06.211Z" }, + { url = "https://files.pythonhosted.org/packages/93/26/473c2e4b6c104e9e58e27ce95fc8005c8bd4fc36cae4f254371125a92db8/librt-0.15.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5f51401d102c885b9ca509e62c79b1dbff286e1b9b047fde6f763780789356d", size = 524427, upload-time = "2026-08-07T10:47:07.592Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/03b3abb82b41714671b907bf6989b228e31e6a8af52dec82b5b0728dc250/librt-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cc30523e3f1a23fb7511cc659834a0d01a1042bb9de359bc1c131cc4ec6c9656", size = 543155, upload-time = "2026-08-07T10:47:08.866Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0e/9bb1f0a4affbd0a1888f4f79dc03ed2a299d9a2c26c59ab2a97dcbf11903/librt-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59fe030d8ae4a57e3fb7756bf35a858de74e04066fc8555c53d0af979132af81", size = 546890, upload-time = "2026-08-07T10:47:10.327Z" }, + { url = "https://files.pythonhosted.org/packages/dc/84/6937a280d461f7de6e031ffb02edc2b7c3c90d49d630565ce8ff27cbc5f2/librt-0.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a6526a2a956bbb1e4ae3568c82e650fc99119c66bb011ea60715744955a2b4d", size = 555163, upload-time = "2026-08-07T10:47:11.798Z" }, + { url = "https://files.pythonhosted.org/packages/bc/95/2a2853c1ee014bf102116e7f897a04beeaeb2461b45b79af98bdfb95f1ef/librt-0.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:85ea21ec6730194d67156b0e0b5430ccb1d61f8b8b907e39b37f9812b74a13f0", size = 535812, upload-time = "2026-08-07T10:47:13.279Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4c/cf9601c1b4c5f09280acd5d83abdb2e68527a2be8257136eb42304218622/librt-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e47b8ba865d7ede071a91a7163073bbaeb72541f1ef8a07d512c45c7b5007f2", size = 573688, upload-time = "2026-08-07T10:47:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/47/6d/9ac7cbec46189a7625af4b5acbd25f10d827f4141b2002181848c8418923/librt-0.15.0-cp312-cp312-win32.whl", hash = "sha256:a5207ec414d1c4a2a7231b2086970dc036f94293cdf338190984958a013a42f1", size = 106138, upload-time = "2026-08-07T10:47:15.973Z" }, + { url = "https://files.pythonhosted.org/packages/38/d0/2ae99c83be86ce23f925ac1aeeedc777e97f427c4a8d190c70d0a16e9a87/librt-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:73b30cfa976659b3917c8f6153bdb0591c6a9ec6583599fd24a689b690622022", size = 126974, upload-time = "2026-08-07T10:47:17.049Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/dd24f9635c730b86b87587967dda7516b1845e8b17684603d31607fed598/librt-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:a54cf9e0ef47b96af580849db5471142200568ce1e02cbf416addab551369570", size = 112292, upload-time = "2026-08-07T10:47:18.222Z" }, + { url = "https://files.pythonhosted.org/packages/e7/42/467b53a601b406ccd7b97c1fd54b59cb34f9185ad5ce7e9d5c3c4e8961c8/librt-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:db13ca398005abcbe538deda87b686d9bd08b7001cf40c4c06b444960ae10a26", size = 151029, upload-time = "2026-08-07T10:47:19.312Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/36c2299b7a94b84fdd01220d8a777a71be5be0925bb0dbdf71c0a06a34d9/librt-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa1f1995789dca3698bc550aaceb09a51bd5df0a057ff84ff15296cd1975b801", size = 155194, upload-time = "2026-08-07T10:47:20.398Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/ed5071f9325845e670bd36012757419767fbf56af77ed483077b9e4db541/librt-0.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55456ea87d8df21808446d03817be2f65e20391c1c615d9187440dff28cd08dc", size = 502568, upload-time = "2026-08-07T10:47:21.652Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/6450c67c3615d87704bcbc21323fafc69c799b06a044c447529f725d4b01/librt-0.15.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5a86a5a08c2235316bdb359d5dbb6ce0abfca7fac06363103e2c5af571d92f95", size = 496153, upload-time = "2026-08-07T10:47:22.925Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d6/5f52b722bc75076954b3bfd49be15ea362df4d580c6fb315d0f617100d30/librt-0.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56b6a368529bed262da40ce13f8fef590db0479819cca84f16a1f01ac356d0b", size = 513336, upload-time = "2026-08-07T10:47:24.213Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e2/c08fd1d36ce63ea5a12b85c5d37f4550b5f86a692167e41e5a74222607ae/librt-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234d8d394721fa0d786af15ebf1f3fb7f3ed82fd1cd0cde45c2f247b5d4281d2", size = 531661, upload-time = "2026-08-07T10:47:25.507Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d8/d9482fcbeb177b9eb87bb3899eeb3b42be690313c652f9e146b1d0681fb2/librt-0.15.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8363d7accb0286ac3a0e633f396e93800dafb8150494505daf9515bbda591f3", size = 524487, upload-time = "2026-08-07T10:47:26.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/075171517b41f861753034fbb151b42cfc83bcc853849f24f5e66fd60ccf/librt-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0f0ee3644d951f31055ad07d77d92520e84505dd7a432cc4cd501dd70ee06785", size = 543201, upload-time = "2026-08-07T10:47:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/b0/03/42c2330f37eeb475b6affeedd06518f60035f323af3a839335e3fc9fef2d/librt-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cfd1a81a648806e6a7717be4cc4d1bb392fa229752bf8444ba365e381e984d6", size = 546467, upload-time = "2026-08-07T10:47:29.396Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/1ad4c5638f7e64d8560328bd25c54b409a661bdb6ff254b38ff90744288d/librt-0.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6cd22c9da0d866558e46a041f1cc0c2bbb26b61b137b2347fa834c332e1d101", size = 555139, upload-time = "2026-08-07T10:47:30.815Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/39fa7d15db1204cd1cbe6514680fbdc243adf754a0885061308f43afc013/librt-0.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6d5225ef8801e4ea5e482fa9b5dfb891dd9ef6f6d870f1f25d449ca2c70ac218", size = 536050, upload-time = "2026-08-07T10:47:32.222Z" }, + { url = "https://files.pythonhosted.org/packages/1e/88/c6dcf0dd8e26dc0c9a499a2abab8646c86dcaf9ecea9524cb46d3686331a/librt-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d28a05796b99f749bf8794f17ba9ba1612d0076b802e9cfc62c554634e9ce3b", size = 573700, upload-time = "2026-08-07T10:47:33.527Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9b/ab54c71a7918a7c34fa5327fb61390a77446a07a146fbfb1165250a61035/librt-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2067ff438048cead9d223ca5675bae2a25e520a7c3e6c1498bf9c6892d22caab", size = 82194, upload-time = "2026-08-07T10:47:34.835Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b2/4f9a243bb892395f3becb80789ade13771701091f9f07ab8230247953ba8/librt-0.15.0-cp313-cp313-win32.whl", hash = "sha256:1cd3b721f24c206398b9e26da3c3a9c011e6e89d06f318ba8ebefc30f1003890", size = 106231, upload-time = "2026-08-07T10:47:36.251Z" }, + { url = "https://files.pythonhosted.org/packages/bf/af/64aff4885a40b93132382f2c314647d722574605416504379184ef3045ea/librt-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:f395a4a9a03ac062dbe9a9f82e0c720502e590a38feee6a757bc82e9c63afbd8", size = 126996, upload-time = "2026-08-07T10:47:37.453Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/335bccf6c7cb9028cb0b54aead27d9ece3f01f83bc6baa2abace5da655c1/librt-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:0a15cb554761247d84a3ec0cbdf4078d70725384f0e4662c0fa3b26266eb60ad", size = 112188, upload-time = "2026-08-07T10:47:38.729Z" }, + { url = "https://files.pythonhosted.org/packages/a8/93/949053fb462eecc4a9a5ee770a81f4b40be7b79538b245545d4aebc6b58b/librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993", size = 149833, upload-time = "2026-08-07T10:47:39.86Z" }, + { url = "https://files.pythonhosted.org/packages/61/ca/8281aa6cd560a3420e4497729f6b704b53be3eeaaef82d5aeadddaf7441f/librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8", size = 154088, upload-time = "2026-08-07T10:47:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/02/1a1662dceaba6a086360891448d5ce9a7d3555976cae59a31a39d744b9c7/librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21", size = 494215, upload-time = "2026-08-07T10:47:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/99211619dc656370a3740c33d2b0b6d5a3fb1e73689314f6ed477a397dc4/librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953", size = 491173, upload-time = "2026-08-07T10:47:43.683Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/5448d0b05f4579b635d3899176817ebf561af0e57bacd425b5b1887264c1/librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa", size = 505512, upload-time = "2026-08-07T10:47:45.314Z" }, + { url = "https://files.pythonhosted.org/packages/95/82/01940e40b83c43a546c4a3c896cf34ca272a9690899d55914e4827b3dcce/librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879", size = 523073, upload-time = "2026-08-07T10:47:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/759c0030f3ee371439eb26de34fc745807caf0abb878af7af4b8b7c3dd3d/librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae", size = 515080, upload-time = "2026-08-07T10:47:48.319Z" }, + { url = "https://files.pythonhosted.org/packages/0b/27/894e072228fcb159703c655da69f8cd10dbed489c36e3df7dd032a2483be/librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd", size = 534164, upload-time = "2026-08-07T10:47:49.875Z" }, + { url = "https://files.pythonhosted.org/packages/98/a3/0078e91c1f36f8815db17827de15650b9a3fe56c55fbf998c854b34e40d3/librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285", size = 540616, upload-time = "2026-08-07T10:47:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/86/33/81a29b796dd52a45e9ef7974c7732926e8f10f15b8d2be505665979f896d/librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239", size = 545890, upload-time = "2026-08-07T10:47:52.818Z" }, + { url = "https://files.pythonhosted.org/packages/05/82/8be1baa1350e5d30cfd70ae79d0a6f4dc5862ef47f7bb2808aabc9bb86e5/librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60", size = 523287, upload-time = "2026-08-07T10:47:54.165Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4f/d1be6a01a35c20ef734e0e44113f87d4af756a9354a89dcfbe3b4f8af5e1/librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65", size = 565868, upload-time = "2026-08-07T10:47:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/67/88/649cfa33f5825927b160610f670bdab012a64d627eddb94fa795ea4292fd/librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622", size = 81619, upload-time = "2026-08-07T10:47:56.886Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/8e88a8d5e48fc8d1a817787fb6811dfff6499acd6c8683dd83934aa6ede0/librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15", size = 100138, upload-time = "2026-08-07T10:47:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/80/92/20fd6c4b6a1b1a564b076d55cd3d427d8428217d7638dc25a654cc4791d4/librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28", size = 121258, upload-time = "2026-08-07T10:47:59.564Z" }, + { url = "https://files.pythonhosted.org/packages/fc/28/6af430b44d9ebb897b865a3c363b6dcace51357be2347cc0f8f869656a86/librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95", size = 106467, upload-time = "2026-08-07T10:48:01.097Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/b42bb798942ced219f6d63b27e07f91237887a8d0bd0921666db79a13790/librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714", size = 159523, upload-time = "2026-08-07T10:48:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/75/03/1b53cd4ef904e73b1d828a5f90143bf94a2967d7cfff0b9ccf93e12aa9b4/librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3", size = 161638, upload-time = "2026-08-07T10:48:03.725Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c4/9f9c9fba097d49e9e694c2b4dc331df31884645ecbc58a93b4b5fc69d2c5/librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d", size = 701795, upload-time = "2026-08-07T10:48:05.135Z" }, + { url = "https://files.pythonhosted.org/packages/4c/05/0966840bda0380c8ae167b9043c6230202941cc90ea29c48e096964c765e/librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38", size = 682147, upload-time = "2026-08-07T10:48:06.555Z" }, + { url = "https://files.pythonhosted.org/packages/18/af/1c47ca573c30ea47d195aec26133af522fea1104afaace028d7b32247ea8/librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19", size = 696397, upload-time = "2026-08-07T10:48:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/1aed6223d4f9f9d1171a8596ff100ea4c3f7699fea7a4ba657c3e60daa6c/librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab", size = 722542, upload-time = "2026-08-07T10:48:09.569Z" }, + { url = "https://files.pythonhosted.org/packages/c6/22/9e3a929aea456c97d69e6ef3884efea56d4807f97399471cc946baebd8af/librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2", size = 729709, upload-time = "2026-08-07T10:48:11.129Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1b/c327ef6018e3a9ca0b8e7c5eddeeb331ba8f9b76c24e126d37d0f6d62faf/librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108", size = 752891, upload-time = "2026-08-07T10:48:12.558Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d1/d5f1ea02c56930087009e39db9b70660a663e76c730b27b925d786718457/librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08", size = 745301, upload-time = "2026-08-07T10:48:14.55Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3c/5f7c585d15ebb2250c73e7c0ee4e9e47be72c65d520c07ddbcdc62037674/librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47", size = 747921, upload-time = "2026-08-07T10:48:16.453Z" }, + { url = "https://files.pythonhosted.org/packages/7f/52/1443a446486eba966bcbca1696b472e4f210320ec42f490a47f48fbf0fdc/librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81", size = 727561, upload-time = "2026-08-07T10:48:18.089Z" }, + { url = "https://files.pythonhosted.org/packages/79/91/2270a9380f11725cf83ce1925a5e32dd1dde2be9bba597f25c10a38644e7/librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc", size = 774417, upload-time = "2026-08-07T10:48:19.611Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/f4b1548d4f5b99186737fe27aec238e9823e8d5d23bf4df007c030689dc5/librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf", size = 104381, upload-time = "2026-08-07T10:48:21.048Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/134afad262def1de04c0843c376d02135f1168af43f22e09a52bd8394727/librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915", size = 127034, upload-time = "2026-08-07T10:48:22.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/5f/1b6846b20572bd699c9e9ec321a5f781845bee477df2aa2a43b28bc40119/librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605", size = 110827, upload-time = "2026-08-07T10:48:23.804Z" }, + { url = "https://files.pythonhosted.org/packages/c6/44/4de9f4ddadb009a55c7758eb5736d62534a7daaf27bd71bc50e64b606b06/librt-0.15.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8443e38dcfcfdbcf5add5118c623efd788d65ac2e25756d6251a54a06a4d0aca", size = 149843, upload-time = "2026-08-07T10:48:25.148Z" }, + { url = "https://files.pythonhosted.org/packages/1f/eb/5d9ab71e30119c44094e0275f38b47dd327aea0f843a080396677029d508/librt-0.15.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6d15a29033c57490cfe2069097c6fc4049e4e65ffbb749be7dc453b7c4c68965", size = 154510, upload-time = "2026-08-07T10:48:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/8505d1b8f5e8c19587bd03f7429993b3e9ce5c06819d856bfb11d919374c/librt-0.15.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c05c729b589e734c09578bf5964be48a911765484840d017bbc84f49d4c4ad", size = 497543, upload-time = "2026-08-07T10:48:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9a/3a8390775cb095765aded027ac9c63e7c8ea74e731498607544c6505de0e/librt-0.15.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fa60887537e1d0cd2d9982269d33a709bf54b195cd2b9364fc0a758022af5bd9", size = 480452, upload-time = "2026-08-07T10:48:29.531Z" }, + { url = "https://files.pythonhosted.org/packages/e7/40/258a4a7117ee915d66de5cd9b8ade65a440993161107ce3a686f1859955c/librt-0.15.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8bc24219b24c0af375718942ab75e3544b2763085f40f965be4326734ae8328", size = 507768, upload-time = "2026-08-07T10:48:31.007Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c6/2f4dd296c97a0b85b98894519b279408ec9dd602d4f692b1ea0e25dee670/librt-0.15.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86a21a7bd3fe3a419512ef424cc1c020f6771d0b29cfddff36d1635a855e63f0", size = 525122, upload-time = "2026-08-07T10:48:32.7Z" }, + { url = "https://files.pythonhosted.org/packages/49/dd/29eab42be13b2bf0ea8cb227135a45d44693e30a7e8b92871981ff56b82b/librt-0.15.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbab647e88d90b3167b91efe7091e248653688ed4337e4f90907a722c7361bb9", size = 520371, upload-time = "2026-08-07T10:48:34.294Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/4bad71adeca8fe208b775c2a35417fa5a2584c8f4791daaf89a89450fea1/librt-0.15.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d8edcf6f550e918dca779c069b9e156385c60b406f99fc7641f32c52f7193659", size = 537258, upload-time = "2026-08-07T10:48:35.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/63/59dba6143fdcc7240c54458b629f3250000a61b8945890fc9efd451b19c5/librt-0.15.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:8b62076030baa2d8b1501a46bf0e19c27a489aa90671c55665bff7887f7660b0", size = 527432, upload-time = "2026-08-07T10:48:37.466Z" }, + { url = "https://files.pythonhosted.org/packages/ec/21/21a24c6a2327d8362580efebe77286bf47b0f4062ec5ea41766e609d3c7d/librt-0.15.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:d00d20d1818e82a07a0ee0aa89a98b17ed7916b92441090b683719cb20a59b6d", size = 548108, upload-time = "2026-08-07T10:48:39.384Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6d/fc68c89a7971418b41f9a873623ff935cb864097544c6a2f8ce491c8ef5d/librt-0.15.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4e6ee93fc3cf848dcbf0cce2eca73d8e7dcd0cc2b6df3a529d57750b30a4c55c", size = 529681, upload-time = "2026-08-07T10:48:41.392Z" }, + { url = "https://files.pythonhosted.org/packages/65/7e/c2d98766124400d722063a630b0fde38a9fc768705d37eecca15c47dc192/librt-0.15.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:32896a0af72508ea979e0acb4e4c04cbeeae04938167950d535c83c45597167d", size = 567736, upload-time = "2026-08-07T10:48:43.124Z" }, + { url = "https://files.pythonhosted.org/packages/55/6c/f8c34a95e3a515c6e1c192b89511e7253c89a7760c6b500d57ffdb8d2dc8/librt-0.15.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:ec3ba415afaf951f6951b1dd16d3c8e4f540065fc382d7e70b823a79567ca374", size = 81673, upload-time = "2026-08-07T10:48:44.645Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9e/e23fa8e78679ec45728188650b39e8ff476c83b691c96f749217df3b1b7c/librt-0.15.0-cp315-cp315-win32.whl", hash = "sha256:d2813ba2503764f0450680c533d13df7cff9b49df1411062eded5f67db4195b9", size = 100081, upload-time = "2026-08-07T10:48:46.171Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dc/3eb4c5e297343f0620a55532cd7c8d764d3001fa2159212dadf480464827/librt-0.15.0-cp315-cp315-win_amd64.whl", hash = "sha256:b87d67e33afaf265262f2a66db578284b88ee2e6fcd224579cb5c15518677ad8", size = 121228, upload-time = "2026-08-07T10:48:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/97/70/43abce19f04e49762f8ec834c8fafee13cc40fd6b94a72a24e534febfcd0/librt-0.15.0-cp315-cp315-win_arm64.whl", hash = "sha256:713bd7df21170b982e729e46870f31d6b437bd1a9b4648cffb529bd3c2ec5c4b", size = 106487, upload-time = "2026-08-07T10:48:49.095Z" }, + { url = "https://files.pythonhosted.org/packages/de/15/83f2deddb9368b8951ec8c9477269b5b9b8bd9bbf15e57402d0f38817dca/librt-0.15.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3de789c82752730f94782a5ee518baf9c05edf85733aeaf73bb6e518755cdf54", size = 159448, upload-time = "2026-08-07T10:48:50.649Z" }, + { url = "https://files.pythonhosted.org/packages/06/bf/043097353f9b3c73b583d07f6b8e552795463f4bfc8caf85e42eee50c26a/librt-0.15.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e0b5deec9a8664eb722c797241970fd4aa1894d25fda36a1ddac0f7407606bd6", size = 161686, upload-time = "2026-08-07T10:48:52.174Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2a/8ae77f9719d42ce71cd708560a3557b38ac3c17a0383e57f87084de45bbe/librt-0.15.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5563302a8359bc2295bb7084d1a8ed1519df96afb30eb2aa4e0bff7b54228988", size = 710668, upload-time = "2026-08-07T10:48:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/61/34/c0436ea134deb9a0d6da80a396a2739a81cb31e0418f7227239e23140898/librt-0.15.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:22d6263b9d39d7bbb286fa791945646e3218f1be2d693e36fb630f1d0e59cd13", size = 679396, upload-time = "2026-08-07T10:48:55.645Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/001e0d99aa9250d5cd5715a9081291a20656083459f9019cda15255329e1/librt-0.15.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39ffd14646190c454f0d86e0d256b33f00a87a26ab410e619773b841d0e41416", size = 704313, upload-time = "2026-08-07T10:48:57.46Z" }, + { url = "https://files.pythonhosted.org/packages/2d/53/b34fa9d0ff00f136f4d58ebb4c411ff634baed1eb412bb602a2bc8dcafcb/librt-0.15.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47318cd3a61401452de11282242937e3e057c4fd3dbaf601e269d0928a06c0a", size = 729847, upload-time = "2026-08-07T10:48:59.231Z" }, + { url = "https://files.pythonhosted.org/packages/86/ac/fa4d7a424665040e95baf480a6d523446057684b6758624c85338e8a23b2/librt-0.15.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a56a1d4f859a82ca5b99fc4b82c9b027b15e3c455c5cd99e7d0719f27bb20b6c", size = 742736, upload-time = "2026-08-07T10:49:01.151Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/e17a9bb5de6fb8c3186ed1a7d68d21618b027ac2d3633e03d3b6109c67ae/librt-0.15.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:077471b3182db4e17c36ae91555f36a4d2c00080b267f749bcad34a478a9a302", size = 763454, upload-time = "2026-08-07T10:49:03.039Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ec/ecd02cd30935b931b9cdbfed6ab5a099c51b280b4e7baa274da80978ed27/librt-0.15.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:411ca4d1b905b860ceba7570dd6717a71dedaddcc4b0f77ece710aa41ee11f8d", size = 743296, upload-time = "2026-08-07T10:49:04.941Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b5/b3c2b8353ce820a4854f78d19321344242f89fa71c975b71132ba9bf242a/librt-0.15.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1256589e0b0adb31751d685a68bce29d73407ddf4ef05d4188f49d5dcf9566d9", size = 756217, upload-time = "2026-08-07T10:49:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/3c/52/6cc22542ba59146b05cca2a656f9ff8bb67e38e63d12c3b0cc183d837bf1/librt-0.15.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:f42b74a53e5f26a0ba0007411a7455b66c67ce4022a39cc1f56fc4efd65bcbab", size = 741934, upload-time = "2026-08-07T10:49:08.839Z" }, + { url = "https://files.pythonhosted.org/packages/40/32/a04b72b1aa86e3be23b2ecff8c1aad2dcc955bd3956d6d26e7e34267e57a/librt-0.15.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:291bf73caf78b9e88d6fae9bfd693207ff7d832e2fdbe2cf8e746bc13f5f892b", size = 783763, upload-time = "2026-08-07T10:49:10.661Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f0/89eb11dffbe9279ff37144dec786927314502ae0b114f1449dc78c458aab/librt-0.15.0-cp315-cp315t-win32.whl", hash = "sha256:c16d15ee371643ab48dc8248a3e680ebbeca573a13af2c3dd0c985b142d77162", size = 104313, upload-time = "2026-08-07T10:49:12.305Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4a/1f1978c200f563beda63c36adff2d65bbecb81e365e8e69e572f5f70fbc6/librt-0.15.0-cp315-cp315t-win_amd64.whl", hash = "sha256:dbd605739f228912dc49027cb764456b9757750bdc2b6b7773164db7096c6fd1", size = 126889, upload-time = "2026-08-07T10:49:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/38/a6/800800bfed7b1fb10fc3f3d557785c3854e80d3f7a9800d784b176a1fc2d/librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc", size = 110700, upload-time = "2026-08-07T10:49:15.499Z" }, +] + [[package]] name = "lonboard" version = "0.16.0" @@ -1325,6 +1540,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/0e/b763d0a97c07e210ee30dd2cb335a7c0eb0cf0c9da3f5089b898bbdee290/lonboard-0.16.0-py3-none-any.whl", hash = "sha256:d720767d15b0fe31736b3ab190b0a89bc06d1dfac53b0a2763cf72a66a40dc05", size = 1160122, upload-time = "2026-04-02T16:33:39.398Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1454,6 +1681,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "mistune" version = "3.2.1" @@ -1463,6 +1699,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, ] +[[package]] +name = "my-schema" +version = "0.1.0" +source = { editable = "my-schema" } +dependencies = [ + { name = "overture-schema" }, + { name = "overture-schema-codegen" }, +] + +[package.metadata] +requires-dist = [ + { name = "overture-schema", specifier = ">=2.0.0" }, + { name = "overture-schema-codegen", git = "https://github.com/OvertureMaps/schema.git?subdirectory=packages%2Foverture-schema-codegen&branch=stac-table-columns" }, +] + +[[package]] +name = "mypy" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/6a/878cc1097d4035f82bd516658d0c528d2a9955bc7b363afcbd0b07fea11b/mypy-2.3.1.tar.gz", hash = "sha256:47c1b1207258513a9d93495f69c8be9de73916186f0e52703e8c461b7a623419", size = 3992554, upload-time = "2026-08-15T03:03:38.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/da/d6effc4f808a842d91edc22535dc9e799d2ff6e91449168b7f47a0771f54/mypy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a32bbbb940af990d3be0b8af321c7b6815bb1b3b48142fe7459b9cc5f58959ff", size = 14047547, upload-time = "2026-08-15T03:02:57.707Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e6/478229701dab76f26485fc8ff5d6f241f393da22447400bbc56f6946aebe/mypy-2.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff715e45b2231a8e85de1d163d1b42791e4d7aab8f5145f85fee1b710b735aff", size = 14216515, upload-time = "2026-08-15T03:01:26.496Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fe/7c42327a3b21e84681f691982cbfe43f334a3685f3b683b72c376476c4fa/mypy-2.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:858fc57d3d91fa728e33e7ad71def60fc6272694607b306cd3292db53ae39080", size = 15307789, upload-time = "2026-08-15T03:03:31.62Z" }, + { url = "https://files.pythonhosted.org/packages/59/f4/7e597edbe01b5a56fa958ce541302dcaabfed979966f1dffedbea0ea0fc2/mypy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:851833db876e7b650f93719c74b7879a08e338979c96054fdfc3bfd90a486355", size = 15548831, upload-time = "2026-08-15T03:03:15.55Z" }, + { url = "https://files.pythonhosted.org/packages/a3/52/cb31e084bc0314a1e384bdd677a4b80e55af04ccac077545e2238b9d320a/mypy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:4c5095a327483591c94e0c8d3ef9e50d4ab1369b541eae007c1f23bc2a41f6bb", size = 11226359, upload-time = "2026-08-15T03:03:29.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/47/88fcf6217b43fa2da81a8c2611370af18141536a4f0294bbf98b457d456d/mypy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfe022634a2a195406bd469e888d2eaf193b02ba7e607391cd7640374aaae3b", size = 10214707, upload-time = "2026-08-15T03:02:48.807Z" }, + { url = "https://files.pythonhosted.org/packages/de/cf/862010ee800ca9c2bd0c4c0dacf0f092e5411824a09b8f97ad4be8fe250e/mypy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:114dff494000f18bd10d5d95d84b8567b26da60279ecbe838131841df20e635d", size = 13964542, upload-time = "2026-08-15T03:02:21.43Z" }, + { url = "https://files.pythonhosted.org/packages/75/5a/3f3a2107b41e3e92e617e25daaee121413b91e9784bea733131ed4fecc5d/mypy-2.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8637731bb5eee3671eb2c3200827aa3564ed8a9309ecee4d1afe77e6d031bdb", size = 14168922, upload-time = "2026-08-15T03:03:00.351Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/04dc4fe7e63d7820fa4eff272e95157d30cbea921388f3ab3fe77794cd0b/mypy-2.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c80fbc405ed8020f5ff3802dc18cf060197bcdd3fbdd6a26ef2fd34dfdd5226", size = 15244791, upload-time = "2026-08-15T03:02:31.089Z" }, + { url = "https://files.pythonhosted.org/packages/96/fc/c3053b26b9054949285aa868cb6af8c10e7591541cacd79c5dcc06a1fcf9/mypy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84081f538ce27375045c02e3d7f81bd11d853400621ae245d87ce7b6c420ec74", size = 15501627, upload-time = "2026-08-15T03:03:34.128Z" }, + { url = "https://files.pythonhosted.org/packages/70/4e/d77daab008bbc4e5001374d7928f4a260d28f0e6747af444fc4763f7a310/mypy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:e9144ac16fde007096f9563eb2041b4433c2d705c4218edeb79e7e9d01035ee6", size = 11243961, upload-time = "2026-08-15T03:02:11.952Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f8/7eb68c136e4abd30569fe31ef2bfcb7eceae9952cab80017c04cd09f5d0c/mypy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:77ad9529e67dca28e511f5cd5671436584ce91f6d3bac159a353158187b986ac", size = 10213219, upload-time = "2026-08-15T03:02:26.361Z" }, + { url = "https://files.pythonhosted.org/packages/be/c4/42a49d44aeff804edf1b19acce0b49e8bd1a9c57dee9605dd8d980aa43d7/mypy-2.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:192abaedf75da1bc0b1cef104927e70ec49c1ef0031cc4825c7ee10a438ed24d", size = 13986778, upload-time = "2026-08-15T03:01:33.69Z" }, + { url = "https://files.pythonhosted.org/packages/45/13/9331fd2dfed7194d66c5304072894a8be3e51e9deda6863c1eceaa35a43d/mypy-2.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf678dffd16efcda2c15cbd30e9ecc0081388e29ea23687a88e686ed92638dc3", size = 14188467, upload-time = "2026-08-15T03:02:40.554Z" }, + { url = "https://files.pythonhosted.org/packages/78/f7/f4a34edab45667c5465855dc585a20e87978ffa8aee711445b7239d120c6/mypy-2.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e036f06b41630f4c8a1d48f9ac6aa26acc65f8be089973f5519da643318f03f", size = 15225538, upload-time = "2026-08-15T03:03:09.761Z" }, + { url = "https://files.pythonhosted.org/packages/40/05/534b3590757bd05794f73e07f6666c2a77b8597ffed795c94ce570096aa0/mypy-2.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71af9c8a894e862b58e92abb08e53b05a384a1e5e5d6dc7cda59126211a53d82", size = 15480805, upload-time = "2026-08-15T03:01:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/55/da/bdfba852e2562f599624af5bb7d29e36b0b4f526f2b8bac85efe0dd1803d/mypy-2.3.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3c80cd23d85368bdd9f37d5231dfd97d35bcbf5bf41af96ef3a9b078ad1957f9", size = 7761712, upload-time = "2026-08-15T03:02:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/60fc64a74cdba4f2a5d642d32317993e479163e1ac7d91b695e5d15e2264/mypy-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:4956f34d145e145562a0a0bf367f642bbc85c04ec2baf47ae015947c3169a85d", size = 11423968, upload-time = "2026-08-15T03:02:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/a9/23/eb5950b24cd26ba3b78f87707a275568d633c77dae8e61c9661be6055ca6/mypy-2.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:cfb12e360242d23d91f5e978d94f58ea66acf5804c4fb6f2f794a20d4cb1b595", size = 10399323, upload-time = "2026-08-15T03:02:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f80f4e46c0b9a00eb5f78a79d49dda8bdf56a5230f7257fb33e76be04da7/mypy-2.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5f1c50bb05b64e2026b52867e8d21106f01313c744a2c4ecc34c90d12e8d6e2", size = 15121308, upload-time = "2026-08-15T03:01:46.053Z" }, + { url = "https://files.pythonhosted.org/packages/5d/74/9b04f17c7074cc5188f02fb63a2ca1d43fedf479e84fe3091c39061a1d7f/mypy-2.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:667196b352f4cf304ded4c10f90cfc179263a1acfb3cdcfa984bdfd340d498bc", size = 15536590, upload-time = "2026-08-15T03:01:35.941Z" }, + { url = "https://files.pythonhosted.org/packages/26/04/c837ef6208e567774e2ed1f863f8ba6ec4817b1b6dd426315e5d559b6ec9/mypy-2.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9c53e395c12cad2c6d4b67d5da7c6057638a132d85c08b73646b18f802a0045", size = 16791074, upload-time = "2026-08-15T03:01:31.073Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/48730230afa45192d5bd429a6a2ff24a6f8dedda90fdf2b221792b54518f/mypy-2.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:18162b128c3f9c703cd35f5537446900b0d21a2549aa7a95d21380d2ef643fb0", size = 17069183, upload-time = "2026-08-15T03:02:28.566Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/ca23fc9c20eeda09a15c9cbcf50015d0e73f409f6ead059e42aa69a608ff/mypy-2.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:30c0477d4aab7b7f39c8397dc877f2c96b9fe5588ec379f372c56eb63d599f63", size = 12154679, upload-time = "2026-08-15T03:02:04.809Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/8d982126034990869466f73b8db80dcb2234a7ac39b4dad093e047a79835/mypy-2.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6941ab3619377bc3f32ca02876b07d27f216f5201604b664d3937ea0fdd23bb4", size = 10969159, upload-time = "2026-08-15T03:02:38.152Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f7/41e7f2d8117fbc7a7587286162ffe2f688984b69c46ed63cf5f2e4fc3bae/mypy-2.3.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6f041a6de52c9217ca125e78ba0a335cb7fd98a1c0580978e49ab2b126f70b57", size = 13990694, upload-time = "2026-08-15T03:03:21.919Z" }, + { url = "https://files.pythonhosted.org/packages/06/85/8f665811a0c8f3bf6fa1d9acd665ec2d97a2bcc453ae68dcd92340941cd6/mypy-2.3.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5159ae60f5dbc3a498af5ba8365505808ac8031bc63f9e00304ad545d40bdd9b", size = 14203518, upload-time = "2026-08-15T03:01:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/91b866c8546b120bff83b73a439d90d2d63ef3aff113599e6b8e4d566848/mypy-2.3.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a8a7a0a7f6f6e63995c0ac36fa0c07b127413fdc81f0439b7f3dccafd33561", size = 15220224, upload-time = "2026-08-15T03:01:23.577Z" }, + { url = "https://files.pythonhosted.org/packages/c8/78/c226c99208ee40de7c768369fa533f933afa003dfdc606ff021450724e91/mypy-2.3.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2329c0501293d4e1f33bc15d04d6304d65a1cdda967ee93a05c1e681a3923133", size = 15501512, upload-time = "2026-08-15T03:02:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e7/7cfb3f106c393979f4cc37ad6c0586044d50401e3c35b0c003e4f3ba6bc9/mypy-2.3.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bb26deed807bdb0457cf3e3f1cd7c4a1cf9d66864eaf1b4a61e06805d4c6b1f9", size = 7761913, upload-time = "2026-08-15T03:01:55.65Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/52affefa273b97939a1f474ae4a349c8718635c15b941112dfab4291b0c1/mypy-2.3.1-cp315-cp315-win_amd64.whl", hash = "sha256:375d7013876a8233b2d05be185bfa09f689696cd999ce8b1cfe6acac5c80e8a3", size = 11422533, upload-time = "2026-08-15T03:03:24.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b7/75643e70c72a5b346d8a9b1543c967ea8824df2ee3fb7ccba652c272b7bb/mypy-2.3.1-cp315-cp315-win_arm64.whl", hash = "sha256:586b3612214cceabb3c0f588c97e7d1e535393f06a60e912e994f6b3ace97523", size = 10397931, upload-time = "2026-08-15T03:02:55.265Z" }, + { url = "https://files.pythonhosted.org/packages/10/ce/53be21f2d4adfcd26f63f1184a13ed797015ab463853f117e2e11e4d726f/mypy-2.3.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ef0c6335cda9d807f8193d8ff6204a72bc909fa9882aacbca14f43cdb7188306", size = 15118669, upload-time = "2026-08-15T03:02:51.479Z" }, + { url = "https://files.pythonhosted.org/packages/62/43/20de757cd42989d291a17fad607742c4c74e875ce5cea00e5a5225020ac1/mypy-2.3.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e598c8c66401d26b150872154a286e6d484cf2789c3bb28a7556806298423021", size = 15545627, upload-time = "2026-08-15T03:03:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/7e/fc/092bdf77ad280eaf501422f0f3b966012b528076cc13e41a774861c907d1/mypy-2.3.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eda22fd4efa9dcd39331d1dede9b5b8b8a7fd69af07592e778433da98610d29e", size = 16764157, upload-time = "2026-08-15T03:02:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/5c/c94c4d62d909b07f552d0d9356d7acc943825558e602a64822ffa2231536/mypy-2.3.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a0ba2e57847849fb0d1fcdabb32786d223095ed8bc121dfe322bcdb3d9c46bc", size = 17073258, upload-time = "2026-08-15T03:02:14.573Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f7/511a88b89e478053c02d22039bb8f3ce4183efe8fd7a4f0a5910a8bb0a32/mypy-2.3.1-cp315-cp315t-win_amd64.whl", hash = "sha256:3f7e865dd51f235f60a2dbcd8728a1c095f5ca28f095d48a725b84cd935735c4", size = 12135505, upload-time = "2026-08-15T03:02:16.714Z" }, + { url = "https://files.pythonhosted.org/packages/71/bf/02573b56964ecb0f7c644f915f53c325ae15c3faec521c5adf11599a32df/mypy-2.3.1-cp315-cp315t-win_arm64.whl", hash = "sha256:8ad80807dc3ab8ea978b1b2b6e4a657194ace1d4ef03e0e731aff1abd517da29", size = 10962647, upload-time = "2026-08-15T03:01:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/8e/41/9675c7a1e78edecfba0b79e587a52594c56e189368261dc7b3a7fffb9527/mypy-2.3.1-py3-none-any.whl", hash = "sha256:6ed5c7e3419083268e5c9258bd1c1ef91af44a9e89374dbcaf37b775716e72eb", size = 2754338, upload-time = "2026-08-15T03:02:53.4Z" }, +] + +[[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.4" @@ -1669,6 +1983,170 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, ] +[[package]] +name = "overture-schema" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-cli" }, + { name = "overture-schema-theme-addresses" }, + { name = "overture-schema-theme-base" }, + { name = "overture-schema-theme-buildings" }, + { name = "overture-schema-theme-divisions" }, + { name = "overture-schema-theme-places" }, + { name = "overture-schema-theme-transportation" }, + { name = "overture-schema-validation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a5/58bc2592644ca1a45e90e7cd0550fd6c49b60d26079c718be770acedefe3/overture_schema-2.0.0.tar.gz", hash = "sha256:487976d74482b4d46826db23f9588f0ded6b2a0171d0b98ef638d2b1a544ca5e", size = 2644, upload-time = "2026-09-02T22:23:00.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/d9/d54b4c003eadeee590c979058714458279cebfa05d194d003a8cee0ccc91/overture_schema-2.0.0-py3-none-any.whl", hash = "sha256:b1371ba816bc07ecd7279c444d18bf41ad18ff7a03c6e99661edf2a9e2363b8e", size = 3154, upload-time = "2026-09-02T22:22:59.06Z" }, +] + +[[package]] +name = "overture-schema-cli" +version = "2.0.0" +source = { git = "https://github.com/OvertureMaps/schema.git?subdirectory=packages%2Foverture-schema-cli&branch=stac-table-columns#1422ae58fbc67009e6c220afdf1eeb393f41512d" } +dependencies = [ + { name = "click" }, + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "yamlcore" }, +] + +[[package]] +name = "overture-schema-codegen" +version = "2.0.0" +source = { git = "https://github.com/OvertureMaps/schema.git?subdirectory=packages%2Foverture-schema-codegen&branch=stac-table-columns#1422ae58fbc67009e6c220afdf1eeb393f41512d" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, + { name = "overture-schema-cli" }, + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "typing-extensions" }, +] + +[[package]] +name = "overture-schema-common" +version = "2.0.0" +source = { git = "https://github.com/OvertureMaps/schema.git?subdirectory=packages%2Foverture-schema-common&branch=stac-table-columns#1422ae58fbc67009e6c220afdf1eeb393f41512d" } +dependencies = [ + { name = "overture-schema-system" }, + { name = "pydantic" }, + { name = "shapely" }, +] + +[[package]] +name = "overture-schema-system" +version = "2.0.0" +source = { git = "https://github.com/OvertureMaps/schema.git?subdirectory=packages%2Foverture-schema-system&branch=stac-table-columns#1422ae58fbc67009e6c220afdf1eeb393f41512d" } +dependencies = [ + { name = "pydantic" }, + { name = "shapely" }, + { name = "typing-extensions" }, +] + +[[package]] +name = "overture-schema-theme-addresses" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/bf/7e0520d0d246e60e05f3aefb7387b6cbe60683713a6289918712381e2f5b/overture_schema_theme_addresses-2.0.0.tar.gz", hash = "sha256:c7a4d9b1d26a06bc875cd56156ccc7241deac3de34350e0f881d17b871dbfc35", size = 3632, upload-time = "2026-09-02T22:23:10.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/b5/763fefeb2f8eeb4f2d740ca5226397446d4391d01b7b93c9c5440c3860bc/overture_schema_theme_addresses-2.0.0-py3-none-any.whl", hash = "sha256:7708f40d3fc1f436edc922b0bb702121de77a34ff00f8e31dc65f8f2d412c6f2", size = 5010, upload-time = "2026-09-02T22:23:07.378Z" }, +] + +[[package]] +name = "overture-schema-theme-base" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/cc/2fc8560ebde820ca7a11f3f9c301e4b2dfe0b16fb51f8c4c5b9699f9306c/overture_schema_theme_base-2.0.0.tar.gz", hash = "sha256:ddc512ab096d7f45818603c3a71b9217c12194723f1d5e08a2d192d8633641a4", size = 18274, upload-time = "2026-09-02T22:23:00.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/38/3989246f1a8ccc3bd90c75198d3c85b672c2b6c162e62a437696bcabef45/overture_schema_theme_base-2.0.0-py3-none-any.whl", hash = "sha256:f69e84f1a19f36576e5938ed593d7d7a771ffe2b632b85672209d7efb9023af1", size = 14715, upload-time = "2026-09-02T22:22:59.182Z" }, +] + +[[package]] +name = "overture-schema-theme-buildings" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/f6/c5698e1a67d5e1001d30a77bc9856f272b83d850fecbbc871f8e9d703cc7/overture_schema_theme_buildings-2.0.0.tar.gz", hash = "sha256:a8accad5986958eb220b1a9a8d4ca5148d0fd6dc7ff65d1272cb53c44a1582c5", size = 6308, upload-time = "2026-09-02T22:22:52.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/81/2b37f367f246fa0357f4c0c84b73297f3fb08e38cb19b9933ccaf0d3fb7f/overture_schema_theme_buildings-2.0.0-py3-none-any.whl", hash = "sha256:8cbc40386e1eb086752b42013f3f54cccd853dca8d7a455c2a58ed1178355a9e", size = 8234, upload-time = "2026-09-02T22:22:51.597Z" }, +] + +[[package]] +name = "overture-schema-theme-divisions" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/de/36de77f84723fb90fe92768850c42c7d56df12d2a8078a65cba85a9bd70f/overture_schema_theme_divisions-2.0.0.tar.gz", hash = "sha256:34138aa63f88dff24ef78c5b0ef5505773dea1f8b47d18d9ac1d1165bdbacfc9", size = 10337, upload-time = "2026-09-02T22:23:10.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/2f/f91a9930a416dd433806ff0444b60cb9bb74f591fd3a9552e56e1c9a7f8c/overture_schema_theme_divisions-2.0.0-py3-none-any.whl", hash = "sha256:7b69a0ddd10a48f616ff0e533c0f2d9e149410ac7bd5ffadeaf4ea9b10dbc72c", size = 12996, upload-time = "2026-09-02T22:23:08.459Z" }, +] + +[[package]] +name = "overture-schema-theme-places" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic", extra = ["email"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/94/adc7ba96c5ce42672e7e515db2c98776f0c79060509f157a9e146f977387/overture_schema_theme_places-2.0.0.tar.gz", hash = "sha256:c9f86c205f8dd937d70166fb8c1193ad1f5dd2913551a8cbe0a4c934e9a3127a", size = 4322, upload-time = "2026-09-02T22:23:02.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/d0/9da784e724d89fbe2b67f4f441944f845b53bfdee05a7c352a424e2ecf4b/overture_schema_theme_places-2.0.0-py3-none-any.whl", hash = "sha256:86a87bcfddede8ac6cf5d0cd5d60174dcde9e6b6b4b00473b937d7a7ff0f8f96", size = 5351, upload-time = "2026-09-02T22:23:01.238Z" }, +] + +[[package]] +name = "overture-schema-theme-transportation" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/22/30cac65b41b33df6dec81c1c8a6309a5973ff46ae972d1edc2deebdebdf3/overture_schema_theme_transportation-2.0.0.tar.gz", hash = "sha256:bc9dba662a39fe12ba58eb3a0f200c3261845de2f8c10d0923b7bad39a1fbeab", size = 10311, upload-time = "2026-09-02T22:22:57.66Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/aa/d7573e27a6a0311044777999ca9ac34b489bbd18439b163e6b44762af6a1/overture_schema_theme_transportation-2.0.0-py3-none-any.whl", hash = "sha256:a181fa559d39aa05c8b5c26f3dc4df8afedfb2851fa0fc80668dd1072174bca6", size = 14057, upload-time = "2026-09-02T22:22:56.584Z" }, +] + +[[package]] +name = "overture-schema-validation" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overture-schema-common" }, + { name = "overture-schema-system" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/3f/f83496cc37fc4358e44074fb362849f93cc67a172ffacb871264fa3bfc00/overture_schema_validation-2.0.0.tar.gz", hash = "sha256:d60549bb9f72dcbd0fae11b2a8433fe089288dcdf55524a2a1e0f38df4892a22", size = 2952, upload-time = "2026-09-02T22:25:50.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/23/454fb1bc6e74f3002c848491a4832be2f08b53e37c5937ea957ff4b5e1e1/overture_schema_validation-2.0.0-py3-none-any.whl", hash = "sha256:0d08dbe9a38899600c63dc9c4d48305b152bbedc508d2541feb590c04d3351ab", size = 4043, upload-time = "2026-09-02T22:25:49.346Z" }, +] + [[package]] name = "overturemaps" version = "1.0.0" @@ -1767,12 +2245,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + [[package]] name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { 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 = [ @@ -1857,6 +2344,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] +[[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 = "prometheus-client" version = "0.25.0" @@ -2000,6 +2496,101 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] +[[package]] +name = "pydantic" +version = "2.13.5" +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/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, + { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" }, + { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" }, + { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" }, + { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, + { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, +] + [[package]] name = "pyfiglet" version = "1.0.4" @@ -2138,6 +2729,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/b4/a9430e72bfc3c458e1fcf8363890994e483052ab052ed93912be4e5b32c8/pystac-1.14.3-py3-none-any.whl", hash = "sha256:2f60005f521d541fb801428307098f223c14697b3faf4d2f0209afb6a43f39e5", size = 208506, upload-time = "2026-01-09T12:38:40.721Z" }, ] +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2328,6 +2935,19 @@ 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 = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + [[package]] name = "rpds-py" version = "0.30.0" @@ -2409,6 +3029,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, ] +[[package]] +name = "ruff" +version = "0.16.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" }, + { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" }, + { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" }, +] + +[[package]] +name = "schema-bootstrap" +version = "0.1.0" +source = { editable = "schema-bootstrap" } +dependencies = [ + { name = "duckdb" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "overture-schema" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [{ name = "duckdb", specifier = ">=1.5.5" }] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=2.3.1" }, + { name = "overture-schema", specifier = ">=2.0.0" }, + { name = "pytest", specifier = ">=9.1.1" }, + { name = "ruff", specifier = ">=0.16.6" }, +] + [[package]] name = "send2trash" version = "2.1.0" @@ -2576,11 +3248,23 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, ] [[package]] @@ -2666,12 +3350,14 @@ dependencies = [ { name = "jupyter" }, { name = "lonboard" }, { name = "matplotlib" }, + { name = "my-schema" }, { name = "notebook" }, { name = "numpy" }, { name = "overturemaps" }, { name = "pandas" }, { name = "pyarrow" }, { name = "pystac" }, + { name = "schema-bootstrap" }, { name = "shapely" }, ] @@ -2683,11 +3369,25 @@ requires-dist = [ { name = "jupyter", specifier = ">=1.0" }, { name = "lonboard", specifier = ">=0.16.0" }, { name = "matplotlib", specifier = ">=3.7" }, + { name = "my-schema", editable = "my-schema" }, { name = "notebook", specifier = ">=7.0" }, { name = "numpy", specifier = ">=1.24" }, { name = "overturemaps", specifier = ">=1.0.0" }, { name = "pandas", specifier = ">=2.0" }, { name = "pyarrow", specifier = ">=14.0" }, { name = "pystac", specifier = ">=1.14.3" }, + { name = "schema-bootstrap", editable = "schema-bootstrap" }, { name = "shapely", specifier = ">=2.0" }, ] + +[[package]] +name = "yamlcore" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/d4/113dfc4c85f945de13b045a20eca50be5f6efa33342d9a9bd6162d26b288/yamlcore-0.0.4.tar.gz", hash = "sha256:c2b1a08dd117cf76a70ee124e936b3c568b0b5c4e5131e69b513f4a877a44881", size = 10456, upload-time = "2024-10-09T17:26:02.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/47/632498fd9a3fe4f175e5de71162dd8cd9b2141180a98f0bef6af293aeaa6/yamlcore-0.0.4-py3-none-any.whl", hash = "sha256:f82a7f7d16baf6531a8c3266b0b63ece251ee2abaf2b9eee040a2f64c8fc618d", size = 5969, upload-time = "2024-10-09T17:26:01.507Z" }, +]