diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..84f1e41 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,52 @@ +# --- Python junk --- +__pycache__/ +*.pyc +*.pyo +*.pyd + +# --- Virtual environments --- +.venv/ +venv/ +env/ +.env/ + +logs + +# --- Build and dependency artifacts --- +dist/ +build/ +*.egg-info/ +.eggs/ +.cache/ +*.log + +.flake8 + +# --- Development / tooling directories --- +.idea/ +.vscode/ +tests/ +docs/ +.git/ +.gitignore + +# --- OS & editor clutter --- +.DS_Store +Thumbs.db + +# --- Node / frontend (if present) --- +node_modules/ +npm-debug.log +yarn.lock + +# --- Docker --- +Dockerfile* +compose*.yml +docker-compose*.yml + +# --- Local data --- +*.db +*.sqlite3 + +/docker +!/docker/entrypoint.sh \ No newline at end of file diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..bebf2f2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +extend-ignore = E501 E712 +exclude = .git,__pycache__,.venv,old,build,dist,trimet.asset.egg-info +max-complexity = 30 +max-line-length = 88 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8e8583c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: ["**"] # runs on any branch + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + env: + POETRY_VIRTUALENVS_CREATE: false + POETRY_NO_INTERACTION: 1 + PYTHONUNBUFFERED: 1 + PYTHONDONTWRITEBYTECODE: 1 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: | + poetry install --with dev --no-interaction --no-ansi + + - name: Run tests + run: | + poetry run black . --check + poetry run pytest --cov=com -v -s --log-cli-level=DEBUG + env: + PYTHONPATH: ${{ github.workspace }} + + build: + name: Build Docker Image + runs-on: ubuntu-latest + needs: test # only build if tests pass + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract branch name + id: extract_branch + run: | + # Strip refs/heads/ from the branch name + BRANCH_NAME=${GITHUB_REF#refs/heads/} + # Replace slashes with dashes for Docker tag safety + SAFE_BRANCH_NAME=${BRANCH_NAME//\//-} + echo "branch=$SAFE_BRANCH_NAME" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile + push: true + tags: | + ghcr.io/opentransittools/pelias-adapter:${{ steps.extract_branch.outputs.branch }} + build-args: | + POETRY_VERSION=2.2.1 diff --git a/.gitignore b/.gitignore index 4f9b63a..afc328f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,8 @@ develop-eggs lib lib64 +.venv + # Installer logs pip-log.txt diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index 626416e..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,3 +0,0 @@ -0.0.1 (2018-02-02) ------------------- -- Initial project setup diff --git a/README.md b/README.md index 3a5b713..22ac4d8 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,24 @@ -Pelias.Adapter -============== - -Python wrapper to make Pelias look like SOLR (geosearch instance) - - -build: ------- - 1. install python 3.x, along easy_install, zc.buildout ("zc.buildout==1.5.2") and git - 1. git clone https://github.com/OpenTransitTools/pelias.adapter.git - 1. cd pelias.adapter - 1. buildout - -run: ----- - 1. rm nohup.out; nohup bin/pserve config/development.ini --reload PELIAS_SOLR=1 & - 1. http://localhost:45454/solr/select?q=2 - 1. http://localhost:45454/solr/boundary/select?q=8 - -test: ------ - 1. run the server (see above) - 1. bin/test - -rules: ------ - 1. try 'autocomplete' .. if that fails, try 'search' - 1. (or should we look at length of string and try 'search' first on longer strings?) - 1. fix 'same string' problem: - - remove duplicate points (strings 99% similar and lat/lon very close by) - - clean up duplicate strings (e.g., Starbucks problem) - 1. call Pelias with configurable url (sources=oa,osm,transit, etc...) - 1. if we get WoF a city record(s), then strip city from query string and resubmit (e.g., bad city problem) - 1. ... - -urls: ------ - 1. Pelias TriMet-only: https://ws.trimet.org/peliaswrap/v1/autocomplete?text=6 - 1 Pelias Multi-Agency: https://ws.trimet.org/peliaswrap/v1/rtp/autocomplete?text=6 - 1. SOLR: https://ws-st.trimet.org/solrwrap/v1/select?start=0&limit=10&wt=json&qt=dismax&rows=10&q=834%20SE&fq=type:stop - 1. SOLR Stops Only: https://ws-st.trimet.org/solrwrap/v1/select?start=0&limit=10&wt=json&qt=dismax&rows=10&q=834%20SE&fq=type:stop \ No newline at end of file +# Pelias Adapter API + +A FastAPI-based web API for Open Transit Tools, providing Pelias geocoding services. + +## Features + +- FastAPI web server +- Pelias geocoding endpoints +- CORS support +- Configurable logging + +## Requirements + +- Python 3.12+ +- Dependencies listed in `pyproject.toml` + +## Installation + +Clone the repository and install dependencies using Poetry: + +```sh +git clone https://github.com/OpenTransitTools/pelias.adapter.git +cd pelias.adapter +poetry install diff --git a/buildout.cfg b/buildout.cfg deleted file mode 100644 index 2faee6f..0000000 --- a/buildout.cfg +++ /dev/null @@ -1,29 +0,0 @@ -[buildout] -parts = dev prod testrunner pydev -develop = . ../utils/ ../../utils/ ../boundary/ -app-egg-name = pelias.adapter -newest = false -include-site-packages = true -allowed-eggs-from-site-packages = psycopg2 PyCrypto distribute mercurial Setuptools zc.buildout -prefer-final = true - -[dev] -recipe = zc.recipe.egg -dependent-scripts = true -interpreter = python -eggs = pelias.adapter[dev] - -[prod] -recipe = zc.recipe.egg -dependent-scripts = true -interpreter = python -eggs = pelias.adapter - -[testrunner] -recipe = zc.recipe.testrunner -eggs = ${dev:eggs} -script = test - -[pydev] -recipe = pb.recipes.pydev -eggs = ${dev:eggs} diff --git a/pelias/adapter/__init__.py b/com/__init__.py similarity index 100% rename from pelias/adapter/__init__.py rename to com/__init__.py diff --git a/pelias/adapter/control/__init__.py b/com/github/__init__.py similarity index 100% rename from pelias/adapter/control/__init__.py rename to com/github/__init__.py diff --git a/pelias/adapter/model/__init__.py b/com/github/ott/__init__.py similarity index 100% rename from pelias/adapter/model/__init__.py rename to com/github/ott/__init__.py diff --git a/pelias/adapter/model/solr/__init__.py b/com/github/ott/pelias/__init__.py similarity index 100% rename from pelias/adapter/model/solr/__init__.py rename to com/github/ott/pelias/__init__.py diff --git a/com/github/ott/pelias/adapter/__init__.py b/com/github/ott/pelias/adapter/__init__.py new file mode 100644 index 0000000..fd1d37a --- /dev/null +++ b/com/github/ott/pelias/adapter/__init__.py @@ -0,0 +1 @@ +# FastAPI Oracle Template Application diff --git a/com/github/ott/pelias/adapter/core/__init__.py b/com/github/ott/pelias/adapter/core/__init__.py new file mode 100644 index 0000000..daa8109 --- /dev/null +++ b/com/github/ott/pelias/adapter/core/__init__.py @@ -0,0 +1 @@ +# Core configuration and settings diff --git a/com/github/ott/pelias/adapter/core/config.py b/com/github/ott/pelias/adapter/core/config.py new file mode 100644 index 0000000..2143671 --- /dev/null +++ b/com/github/ott/pelias/adapter/core/config.py @@ -0,0 +1,43 @@ +import os +from logging import getLogger +from zoneinfo import ZoneInfo + +from ott.utils.svr.pyramid.globals import CACHE_LONG +from pydantic import ConfigDict +from pydantic_settings import BaseSettings + +logger = getLogger(__name__) + +ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") # default to dev + +CACHE_LONG_STR = f"public, max-age={CACHE_LONG}" + +logger.info(f"Environment: {ENVIRONMENT}") + +ENV_FILE = f"{ENVIRONMENT}.env" + +TIME_ZONE: ZoneInfo = ZoneInfo("America/Los_Angeles") + +DATE_STRING_FORMAT = "%Y-%m-%d %H:%M:%S %Z%z" + + +class Settings(BaseSettings): + """ + Application settings with Oracle database configuration + """ + + # Application settings + app_name: str = "FastAPI Trimet Pelias Adapter" + debug: bool = False + + model_config = ConfigDict( + env_file=ENV_FILE, # or f"{ENVIRONMENT}.env" + case_sensitive=False, + extra="allow", + ) + + +# Global settings instance +settings = Settings() + +logger.info(f"Loaded settings for environment: {ENVIRONMENT}, {settings.debug}") diff --git a/com/github/ott/pelias/adapter/core/errors.py b/com/github/ott/pelias/adapter/core/errors.py new file mode 100644 index 0000000..72a79c4 --- /dev/null +++ b/com/github/ott/pelias/adapter/core/errors.py @@ -0,0 +1,9 @@ +class PeliasAdapterError(Exception): + """Base class for all Pelias Adapter exceptions.""" + + def __init__(self, message): + super().__init__(message) + self.message = message + + def __str__(self): + return self.message diff --git a/com/github/ott/pelias/adapter/models/__init__.py b/com/github/ott/pelias/adapter/models/__init__.py new file mode 100644 index 0000000..b584e21 --- /dev/null +++ b/com/github/ott/pelias/adapter/models/__init__.py @@ -0,0 +1 @@ +# SQLAlchemy models diff --git a/pelias/adapter/pyramid/__init__.py b/com/github/ott/pelias/adapter/models/solr/__init__.py similarity index 100% rename from pelias/adapter/pyramid/__init__.py rename to com/github/ott/pelias/adapter/models/solr/__init__.py diff --git a/pelias/adapter/model/solr/solr_record.py b/com/github/ott/pelias/adapter/models/solr/solr_record.py similarity index 71% rename from pelias/adapter/model/solr/solr_record.py rename to com/github/ott/pelias/adapter/models/solr/solr_record.py index 42ba22a..3090f98 100644 --- a/pelias/adapter/model/solr/solr_record.py +++ b/com/github/ott/pelias/adapter/models/solr/solr_record.py @@ -1,9 +1,13 @@ -from ott.utils.dao.base import MinimalDao +import logging + +from datetime import datetime + from ott.utils import geo_utils +from ott.utils.dao.base import MinimalDao -import logging -log = logging.getLogger(__file__) +from com.github.ott.pelias.adapter.core.config import TIME_ZONE, DATE_STRING_FORMAT +log = logging.getLogger(__file__) """ TODO: have an endpoint /solrwrap/boundary?text=834 SE that calculates the bounds @@ -13,29 +17,29 @@ https://maps.trimet.org/solr/select?_dc=1618955956672&start=0&limit=10&fq=(-type%3A26%20AND%20-type%3Aroute)&wt=json&qt=dismax&rows=10&q=44%20se """ + class SolrRecord(MinimalDao): """ :see: https://trimet.org/solr/select?q=3&rows=6&wt=json&fq= """ + def __init__(self): - super(SolrRecord, self).__init__() + super().__init__() self.id = "" self.type = "" self.type_name = "" self.vtype = "1" self.name = "" - self.city = "" self.county = "" self.neighborhood = "" self.zip_code = "" - self.x = 7645053.5 self.y = 684388.9 self.lon = -122.67371 self.lat = 45.523335 + self.timestamp = datetime.now(TIME_ZONE).strftime(DATE_STRING_FORMAT) - self.timestamp = "2018-02-03T07:47:45.045Z" self.score = 0.0 def set_value(self, name, val): @@ -44,19 +48,19 @@ def set_value(self, name, val): def parse_pelias(self, json): try: # step 1: parse props - properties = json.get('properties') - self.id = properties.get('id') - self.type = properties.get('layer') + properties = json.get("properties") + self.id = properties.get("id") + self.type = properties.get("layer") self.type_name = "Address" - self.name = properties.get('name', "") - self.city = properties.get('locality', "") - self.neighborhood = properties.get('neighborhood', "") - self.county = properties.get('county', "") - self.score = properties.get('confidence', 0.1) + self.name = properties.get("name", "") + self.city = properties.get("locality", "") + self.neighborhood = properties.get("neighborhood", "") + self.county = properties.get("county", "") + self.score = properties.get("confidence", 0.1) # step 2: parse / calculate geometry - geojson = json.get('geometry') + geojson = json.get("geometry") lon, lat = self.parse_geojson(geojson) self.lon = lon self.lat = lat diff --git a/pelias/adapter/model/solr/solr_response.py b/com/github/ott/pelias/adapter/models/solr/solr_response.py similarity index 80% rename from pelias/adapter/model/solr/solr_response.py rename to com/github/ott/pelias/adapter/models/solr/solr_response.py index 2efac02..8eb6832 100644 --- a/pelias/adapter/model/solr/solr_response.py +++ b/com/github/ott/pelias/adapter/models/solr/solr_response.py @@ -1,12 +1,13 @@ -import requests +import logging -from ott.utils.dao.base import MinimalDao +import requests from ott.utils.dao.base import BaseDao +from ott.utils.dao.base import MinimalDao -from .solr_record import SolrRecord +from com.github.ott.pelias.adapter.models.solr.solr_record import SolrRecord from .solr_stop_record import SolrStopRecord +from ...service.config import route_stop_str_url -import logging log = logging.getLogger(__file__) @@ -16,18 +17,14 @@ class RouteStopRecords(object): @classmethod def url(cls): - """ get route stops as string """ - if cls._url is None: - from pelias.adapter.pyramid.views import route_stop_str_url - if route_stop_str_url and len(route_stop_str_url) > 5: - cls._url = route_stop_str_url - else: - cls._url = "http://maps.trimet.org/ti/index/stops" + """get route stops as string""" + + cls._url = route_stop_str_url if not cls._url else cls._url return cls._url @classmethod def find_record(cls, id): - """ see requests_cache: https://requests-cache.readthedocs.io/en/latest/user_guide.html """ + """see requests_cache: https://requests-cache.readthedocs.io/en/latest/user_guide.html""" ret_val = None try: ret_val = cls.cache.get(id) @@ -37,7 +34,7 @@ def find_record(cls, id): # step 2: cache record if rs and len(rs.text): - #cls.cache.update() + # cls.cache.update() ret_val = rs.text except Exception as e: log.warning(e) @@ -62,17 +59,17 @@ def __init__(self): self.docs = [] def parse_pelias(self, json, add_routes=False): - #import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() try: # step 1: get pelias records - features = json.get('features', []) + features = json.get("features", []) # step 2: loop thru pelias records for f in features: # step 3: handle parsing of different layer types - layer = f.get('properties', {}).get('layer') + layer = f.get("properties", {}).get("layer") if layer: - if ':stops' in layer: + if "stops" in layer: solr_rec = SolrStopRecord.pelias_to_solr(f) """ if add_routes and hasattr(solr_rec, 'stop_id'): @@ -97,6 +94,7 @@ class SolrResponse(BaseDao): https://trimet.org/solr/select?q=4&rows=6&wt=json&fq=type%3Astop routes": "37:37:Lake Grove:;78:78:Denney/Kerr Pkwy:snow", """ + def __init__(self): super(SolrResponse, self).__init__() self.responseHeader = ResponseHeader() diff --git a/pelias/adapter/model/solr/solr_stop_record.py b/com/github/ott/pelias/adapter/models/solr/solr_stop_record.py similarity index 78% rename from pelias/adapter/model/solr/solr_stop_record.py rename to com/github/ott/pelias/adapter/models/solr/solr_stop_record.py index f365783..8a41175 100644 --- a/pelias/adapter/model/solr/solr_stop_record.py +++ b/com/github/ott/pelias/adapter/models/solr/solr_stop_record.py @@ -1,6 +1,7 @@ +import logging + from .solr_record import SolrRecord -import logging log = logging.getLogger(__file__) @@ -8,6 +9,7 @@ class SolrStopRecord(SolrRecord): """ :see: https://trimet.org/solr/select?q=3&rows=6&wt=json&fq=type:stop """ + def __init__(self): super(SolrStopRecord, self).__init__() @@ -52,13 +54,13 @@ def parse_pelias(self, json): self.begin_date = "2025-07-14" self.end_date = "9999-12-31" - try: - gtfs = json.get('properties').get('addendum').get('gtfs') - self.agency_id = gtfs.get('agency_id', "") - self.stop_id = gtfs.get('stop_id', "") - self.stop_code = gtfs.get('stop_code', "") - self.street_direction = gtfs.get('direction', "") - self.position = gtfs.get('position', "") - self.mode = gtfs.get('mode', "") - except Exception as e: - log.warning(e) + gtfs = json.get("properties", {}).get("addendum", {}).get("gtfs", {}) + if gtfs: + self.agency_id = gtfs.get("agency_id", "") + self.stop_id = gtfs.get("stop_id", "") + self.stop_code = gtfs.get("stop_code", "") + self.street_direction = gtfs.get("direction", "") + self.position = gtfs.get("position", "") + self.mode = gtfs.get("mode", "") + else: + log.warning("No GTFS data found in Pelias JSON") diff --git a/com/github/ott/pelias/adapter/routers/__init__.py b/com/github/ott/pelias/adapter/routers/__init__.py new file mode 100644 index 0000000..9eddcc0 --- /dev/null +++ b/com/github/ott/pelias/adapter/routers/__init__.py @@ -0,0 +1 @@ +# API routes and endpoints diff --git a/com/github/ott/pelias/adapter/routers/core.py b/com/github/ott/pelias/adapter/routers/core.py new file mode 100644 index 0000000..53632e7 --- /dev/null +++ b/com/github/ott/pelias/adapter/routers/core.py @@ -0,0 +1,43 @@ +from fastapi import APIRouter +from pydantic import BaseModel + +from com.github.ott.pelias.adapter.core.config import settings + +router = APIRouter() + + +class HealthCheck(BaseModel): + status: str + message: str + + +@router.get("/health", response_model=HealthCheck) +def health_check(): + """ + Health check endpoint that tests database connectivity + """ + return HealthCheck(status="ok", message="Service is healthy") + + +@router.get("/info") +async def info(): + """ + Application information endpoint + """ + return { + "app_name": settings.app_name, + "version": "1.0.0", + } + + +@router.get("/") +async def root(): + """ + Root endpoint + """ + return { + "message": f"Welcome to {settings.app_name}", + "description": "FastAPI Pelias Wrapper", + "docs": "/docs", + "redoc": "/redoc", + } diff --git a/com/github/ott/pelias/adapter/routers/pelias.py b/com/github/ott/pelias/adapter/routers/pelias.py new file mode 100644 index 0000000..160439c --- /dev/null +++ b/com/github/ott/pelias/adapter/routers/pelias.py @@ -0,0 +1,40 @@ +import logging + +from fastapi import APIRouter, Request +from fastapi.responses import JSONResponse + +from com.github.ott.pelias.adapter.core.config import CACHE_LONG_STR +from com.github.ott.pelias.adapter.service import pelias_service +from com.github.ott.pelias.adapter.service.config import SearchApiType + +log = logging.getLogger(__file__) + +router = APIRouter() + + +def get_json_response( + request: Request, + service: SearchApiType = SearchApiType.autocomplete, + is_rtp: bool = False, +) -> JSONResponse: + ret_val = pelias_service.get_pelias_response( + service=service, request=request, is_rtp=is_rtp + ) + + return JSONResponse(content=ret_val, headers={"Cache-Control": CACHE_LONG_STR}) + + +@router.get("/{api}") +def pelias( + request: Request, + api: SearchApiType = SearchApiType.autocomplete, +) -> JSONResponse: + return get_json_response(request=request, service=api) + + +@router.get("/rtp/{api}") +def pelias_from_rtp( + request: Request, + api: SearchApiType = SearchApiType.autocomplete, +) -> JSONResponse: + return get_json_response(request=request, service=api, is_rtp=True) diff --git a/com/github/ott/pelias/adapter/routers/solr.py b/com/github/ott/pelias/adapter/routers/solr.py new file mode 100644 index 0000000..c608834 --- /dev/null +++ b/com/github/ott/pelias/adapter/routers/solr.py @@ -0,0 +1,38 @@ +import logging + +from fastapi import APIRouter, Request, Depends + +from com.github.ott.pelias.adapter.models.solr.solr_response import SolrResponse +from com.github.ott.pelias.adapter.schema.solr_schema import SolrResponseSchema +from com.github.ott.pelias.adapter.service.solr_service import solr_api + +log = logging.getLogger(__file__) + +router = APIRouter() + + +@router.get("/") +def solr_json(request: Request) -> SolrResponseSchema: + """ + SOLR response wrapper... + + SOLR Queries: + https://trimet.org/solr/select?q=12&rows=6&wt=xml&fq=type%3Astop + + :param request: + :return: + """ + + solr_response: SolrResponse = solr_api(request) + response: SolrResponseSchema = SolrResponseSchema.to_schema(solr_response) + + return response + + +# todo Original api does have autocomlete, etc on endpoint but it is +# ignored. No autocomplete on solr so removing for now +@router.get("/select") +def solr_select( + request: Request, +): + return solr_json(request=request) diff --git a/pelias/adapter/tests/__init__.py b/com/github/ott/pelias/adapter/schema/__init__.py similarity index 100% rename from pelias/adapter/tests/__init__.py rename to com/github/ott/pelias/adapter/schema/__init__.py diff --git a/com/github/ott/pelias/adapter/schema/pelias_schema.py b/com/github/ott/pelias/adapter/schema/pelias_schema.py new file mode 100644 index 0000000..0295fe1 --- /dev/null +++ b/com/github/ott/pelias/adapter/schema/pelias_schema.py @@ -0,0 +1,53 @@ +from typing import List, Optional + +from pydantic import BaseModel, Field + + +class LangModel(BaseModel): + name: Optional[str] = None + iso6391: Optional[str] = Field(None, alias="iso6391") + + +class QueryModel(BaseModel): + text: str + size: Optional[int] = None + layers: Optional[List[str]] = None + lang: Optional[LangModel] = None + + +class GeocodingModel(BaseModel): + version: Optional[str] = None + attribution: Optional[str] = None + query: Optional[QueryModel] = None + + +class GeometryModel(BaseModel): + type: str + coordinates: List[float] + + +class PropertiesModel(BaseModel): + id: Optional[str] = None + gid: Optional[str] = None + layer: Optional[str] = None + source: Optional[str] = None + name: Optional[str] = None + confidence: Optional[float] = None + country: Optional[str] = None + country_a: Optional[str] = None + region: Optional[str] = None + locality: Optional[str] = None + label: Optional[str] = None + + +class FeatureModel(BaseModel): + type: str + geometry: GeometryModel + properties: PropertiesModel + + +class PeliasResponse(BaseModel): + geocoding: GeocodingModel + type: str = Field(..., description="Usually 'FeatureCollection'") + features: List[FeatureModel] + bbox: Optional[List[float]] = None diff --git a/com/github/ott/pelias/adapter/schema/solr_schema.py b/com/github/ott/pelias/adapter/schema/solr_schema.py new file mode 100644 index 0000000..e7c65d6 --- /dev/null +++ b/com/github/ott/pelias/adapter/schema/solr_schema.py @@ -0,0 +1,102 @@ +from typing import Optional, Any, Dict, List + +from pydantic import BaseModel, Field + +from com.github.ott.pelias.adapter.models.solr.solr_response import ( + Response as SResponse, +) +from com.github.ott.pelias.adapter.models.solr.solr_response import SolrResponse +from com.github.ott.pelias.adapter.models.solr.solr_stop_record import SolrStopRecord + + +class SolrParams(BaseModel): + q: Optional[str] = None + fl: Optional[str] = None + rows: Optional[int] = None + wt: Optional[str] = None + start: Optional[str] = None + sort: Optional[str] = None + fq: Optional[List[str]] = None # filter queries can be multiple + + +class SolrResponseHeader(BaseModel): + status: int + QTime: int + params: Optional[SolrParams] = None + + @staticmethod + def to_schema(header_dict: Dict[str, Any]) -> "SolrResponseHeader": + params = header_dict.get("params", {}) + solr_params = SolrParams(**params) if params else None + return SolrResponseHeader( + status=header_dict.get("status", 0), + QTime=header_dict.get("QTime", 0), + params=solr_params, + ) + + +class SolrDoc(BaseModel): + id: str + name: Optional[str] = "" + score: Optional[float] = None + + type: Optional[str] = "" + type_name: Optional[str] = "" + vtype: Optional[str] = "1" + city: Optional[str] = "" + county: Optional[str] = "" + neighborhood: Optional[str] = "" + zip_code: Optional[str] = "" + x: Optional[int | float] = None + y: Optional[int | float] = None + lon: Optional[float] = None + lat: Optional[float] = None + timestamp: Optional[int | str] = None + + # add dynamic fields Solr might include, e.g. geospatial, type, etc. + model_config = {"extra": "allow", "exclude_none": False, "populate_by_name": True} + + @staticmethod + def toSchema(record: SolrStopRecord): + record_dict = record.__dict__ + return SolrDoc(**record_dict) + + +class SolrResponseBody(BaseModel): + numFound: int + start: int + docs: List[SolrDoc] + + @staticmethod + def to_schema(response: SResponse) -> "SolrResponseBody": + docs = [SolrDoc.toSchema(doc) for doc in response.docs] + return SolrResponseBody( + numFound=response.numFound, start=response.start, docs=docs + ) + + +class SolrResponseSchema(BaseModel): + status_code: int = 200 + status_message: Optional[str] = None + has_errors: bool = False + has_alerts: bool = False + alerts: Optional[List[str]] = Field(default_factory=list) + date_info: Optional[Dict[str, Any]] = Field(default_factory=dict) + responseHeader: SolrResponseHeader + response: SolrResponseBody + + @staticmethod + def to_schema(solr_response: SolrResponse): + response: SolrResponseBody = SolrResponseBody.to_schema(solr_response.response) + response_header = solr_response.responseHeader.__dict__ + header = SolrResponseHeader.to_schema(response_header) + return SolrResponseSchema( + status_code=solr_response.status_code, + status_message=solr_response.status_message, + has_errors=solr_response.has_errors, + has_alerts=solr_response.has_alerts, + date_info=solr_response.date_info, + alerts=solr_response.alerts, + response=response, + responseHeader=header, + ) diff --git a/com/github/ott/pelias/adapter/service/__init__.py b/com/github/ott/pelias/adapter/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/com/github/ott/pelias/adapter/service/config.py b/com/github/ott/pelias/adapter/service/config.py new file mode 100644 index 0000000..c7d9148 --- /dev/null +++ b/com/github/ott/pelias/adapter/service/config.py @@ -0,0 +1,34 @@ +import os +from enum import Enum +from logging import getLogger + +logger = getLogger(__name__) + +PELIAS = os.getenv("PELIAS_URL", "https://ws-st.trimet.org") +MAPS_URL = os.getenv("MAPS_URL", "https://maps.trimet.org") + +pelias_search_url = f"{PELIAS}/pelias/v1/search" +pelias_autocomplete_url = f"{PELIAS}/pelias/v1/autocomplete" +pelias_reverse_url = f"{PELIAS}/pelias/v1/reverse" +pelias_place_url = f"{PELIAS}/pelias/v1/place" +route_stop_str_url = f"{MAPS_URL}/ti/index/stops" + + +class SearchApiType(str, Enum): + autocomplete = "autocomplete" + search = "search" + reverse = "reverse" + place = "place" + + +class SolrApiType(str, Enum): + select = "select" + search = "search" + autocomplete = "autocomplete" + + +logger.info(f"Pelias URL: {PELIAS}") +logger.info(f"Pelias Search URL: {pelias_search_url}") +logger.info(f"Pelias Autocomplete URL: {pelias_autocomplete_url}") +logger.info(f"Pelias Reverse URL: {pelias_reverse_url}") +logger.info(f"Pelias Place URL: {pelias_place_url}") diff --git a/pelias/adapter/control/pelias_json_queries.py b/com/github/ott/pelias/adapter/service/pelias_json_queries.py similarity index 71% rename from pelias/adapter/control/pelias_json_queries.py rename to com/github/ott/pelias/adapter/service/pelias_json_queries.py index 6f7a24a..501fb2a 100644 --- a/pelias/adapter/control/pelias_json_queries.py +++ b/com/github/ott/pelias/adapter/service/pelias_json_queries.py @@ -1,9 +1,12 @@ """ query the response json from Pelias for various elements """ + +import logging + from ott.utils import html_utils from ott.utils import web_utils -import logging + log = logging.getLogger(__file__) @@ -14,7 +17,7 @@ def has_features(rec): """ ret_val = False try: - if rec is not None and 'features' in rec and len(rec['features']) > 0: + if rec is not None and "features" in rec and len(rec["features"]) > 0: ret_val = True except Exception as e: log.debug(e) @@ -22,9 +25,9 @@ def has_features(rec): return ret_val -def get_addendum_value(dict, prop_name, base_name='gtfs', def_val=''): +def get_addendum_value(dict, prop_name, base_name="gtfs", def_val=""): try: - addendum = dict.get('addendum').get(base_name) + addendum = dict.get("addendum").get(base_name) ret_val = addendum.get(prop_name) except Exception as e: log.debug(e) @@ -33,7 +36,7 @@ def get_addendum_value(dict, prop_name, base_name='gtfs', def_val=''): def get_element_value(dict, *prop_names): - """ return value of first named element from a dictionary """ + """return value of first named element from a dictionary""" ret_val = None for n in prop_names: v = dict.get(n) @@ -43,11 +46,11 @@ def get_element_value(dict, *prop_names): return ret_val -def append(str1, str2, sep=', '): - """ append """ +def append(str1, str2, sep=", "): + """append""" if str1: if str2 and str2 not in str1: - ret_val = u"{}{}{}".format(str1, sep, str2) + ret_val = "{}{}{}".format(str1, sep, str2) else: ret_val = str1 else: @@ -56,7 +59,7 @@ def append(str1, str2, sep=', '): return ret_val -def append3(str1, str2, str3, sep1=', ', sep2=', '): +def append3(str1, str2, str3, sep1=", ", sep2=", "): ret_val = append(str1, str2, sep1) ret_val = append(ret_val, str3, sep2) return ret_val @@ -65,20 +68,22 @@ def append3(str1, str2, str3, sep1=', ', sep2=', '): def street_name(properties, include_number=True, def_val=None): ret_val = def_val - street = properties.get('street') + street = properties.get("street") if street: ret_val = street if include_number: - num = properties.get('housenumber') + num = properties.get("housenumber") if num: - ret_val = u"{} {}".format(num, street) + ret_val = "{} {}".format(num, street) return ret_val -def neighborhood_fix(neighborhood, def_val=None, ban_list=["Portland International Airport"]): - """ will filter certain neighborhoods """ +def neighborhood_fix( + neighborhood, def_val=None, ban_list=["Portland International Airport"] +): + """will filter certain neighborhoods""" ret_val = def_val if neighborhood not in ban_list: ret_val = neighborhood @@ -86,16 +91,16 @@ def neighborhood_fix(neighborhood, def_val=None, ban_list=["Portland Internation def get_neighborhood(properties, def_val=None): - return neighborhood_fix(properties.get('neighbourhood'), def_val) + return neighborhood_fix(properties.get("neighbourhood"), def_val) -def neighborhood_and_city(properties, sep=', ', def_val=None): +def neighborhood_and_city(properties, sep=", ", def_val=None): ret_val = def_val neighbourhood = get_neighborhood(properties) - city = properties.get('locality') + city = properties.get("locality") if neighbourhood and city and city not in neighbourhood: - ret_val = u"{}{}{}".format(neighbourhood, sep, city) + ret_val = "{}{}{}".format(neighbourhood, sep, city) elif neighbourhood: ret_val = neighbourhood elif city: @@ -106,11 +111,12 @@ def neighborhood_and_city(properties, sep=', ', def_val=None): def city_neighborhood_or_county(properties, def_val=None): ret_val = def_val - v = get_element_value(properties, 'locality', 'neighborhood', 'county') + v = get_element_value(properties, "locality", "neighborhood", "county") if v: ret_val = v return ret_val + """" TODO... @classmethod @@ -149,10 +155,11 @@ def fix_admin(cls, query_string, text_param): return ret_val """ + def spec_check(query_string): # step 1: break out text parameters - lat = html_utils.get_param_value_from_qs(query_string, 'point.lat') - lon = html_utils.get_param_value_from_qs(query_string, 'point.lon') + lat = html_utils.get_param_value_from_qs(query_string, "point.lat") + lon = html_utils.get_param_value_from_qs(query_string, "point.lon") if lon == "-111.111": if lat == "1.11": web_utils.flash("java") @@ -161,18 +168,19 @@ def spec_check(query_string): elif lat == "0.111": web_utils.flash("docker") + def is_region_record(rec): ret_val = False # step 1: find value from either string or (dict) object layer_value = rec - if 'properties' in rec: - rec = rec.get('properties') - if 'layer' in rec: - layer_value = rec.get('layer') + if "properties" in rec: + rec = rec.get("properties") + if "layer" in rec: + layer_value = rec.get("layer") # step 2: see if the value matchess a region record - if layer_value in ('locality', 'neighbourhood', 'region', 'county'): + if layer_value in ("locality", "neighbourhood", "region", "county"): ret_val = True return ret_val @@ -181,29 +189,31 @@ def is_region_record(rec): def find_feature(pelias_response, feature_index=0, def_val=None): ret_val = def_val try: - ret_val = pelias_response.get('features')[feature_index] - except: - pass + ret_val = pelias_response.get("features")[feature_index] + except Exception as e: + log.info(e) return ret_val -def find_feature_property(pelias_response, property_name, feature_index=0, def_val=None): +def find_feature_property( + pelias_response, property_name, feature_index=0, def_val=None +): ret_val = def_val try: f = find_feature(pelias_response, feature_index) - ret_val = f.get('properties').get(property_name) - except: - pass + ret_val = f.get("properties").get(property_name) + except Exception as e: + log.info(e) return ret_val def find_parsed_text(pelias_response, def_val=None): - """ return record of normalized input elements """ + """return record of normalized input elements""" ret_val = def_val try: - ret_val = pelias_response.get('geocoding').get('query').get('parsed_text') - except: - pass + ret_val = pelias_response.get("geocoding").get("query").get("parsed_text") + except Exception as e: + log.info(e) return ret_val @@ -214,7 +224,7 @@ def break_query_string_at_region(properties, query_string, min_len=3): """ ret_val = query_string if query_string and len(query_string) > min_len: - for n in ['name', 'locality', 'region_a', 'label', 'county_a', 'county']: + for n in ["name", "locality", "region_a", "label", "county_a", "county"]: val = properties.get(n) if val in query_string: s = query_string.split() diff --git a/com/github/ott/pelias/adapter/service/pelias_service.py b/com/github/ott/pelias/adapter/service/pelias_service.py new file mode 100644 index 0000000..7ee376a --- /dev/null +++ b/com/github/ott/pelias/adapter/service/pelias_service.py @@ -0,0 +1,65 @@ +from fastapi import Request +from ott.utils import json_utils + +from com.github.ott.pelias.adapter.core.errors import PeliasAdapterError +from com.github.ott.pelias.adapter.service.config import ( + pelias_autocomplete_url, + pelias_place_url, + SearchApiType, +) +from com.github.ott.pelias.adapter.service.config import pelias_reverse_url +from com.github.ott.pelias.adapter.service.config import pelias_search_url +from com.github.ott.pelias.adapter.service.pelias_wrapper import PeliasWrapper + + +def get_pelias_response( + request: Request, service: SearchApiType = SearchApiType.autocomplete, is_rtp=False +) -> dict: + """ + calls pellias wrapper based on specified service (autocomplete == default, search or reverse) + :return: json data from Pelias ... after fixing up the response in ways defined by 'PeliasWrapper' + """ + # import pdb; pdb.set_trace() + + # step 1: find the service based on pelias/{service}? ... default to autocomplete + + query = request.url.query + + match service: + case SearchApiType.autocomplete: + ret_val = PeliasWrapper.wrapp( + pelias_autocomplete_url, + pelias_search_url, + pelias_reverse_url, + query, + is_rtp=is_rtp, + ) + case SearchApiType.search: + ret_val = PeliasWrapper.wrapp( + pelias_search_url, + pelias_autocomplete_url, + pelias_reverse_url, + query, + is_rtp=is_rtp, + ) + case SearchApiType.reverse: + ret_val = PeliasWrapper.reverse(pelias_reverse_url, query) + case SearchApiType.place: + ret_val = PeliasWrapper.reverse(pelias_place_url, query) + case _: + raise PeliasAdapterError( + f"unknown pelias service requested: {service}, must be one of autocomplete, search or reverse" + ) + + # step 3: append the hostname to the response + json_utils.append_hostname_to_json(ret_val) + + """ + TODO: WIP find and add stops / in/out / etc... + x = object_utils.find_elements('properties', ret_val) + for z in x: + z["XXXX"] = 'MMMMMMMMMMM' + import pdb; pdb.set_trace() + """ + + return ret_val diff --git a/pelias/adapter/control/pelias_to_solr.py b/com/github/ott/pelias/adapter/service/pelias_to_solr.py similarity index 73% rename from pelias/adapter/control/pelias_to_solr.py rename to com/github/ott/pelias/adapter/service/pelias_to_solr.py index 0600301..790d81d 100644 --- a/pelias/adapter/control/pelias_to_solr.py +++ b/com/github/ott/pelias/adapter/service/pelias_to_solr.py @@ -1,14 +1,16 @@ -from future.standard_library import install_aliases -install_aliases() - +import logging from urllib.parse import urlencode -from ott.utils import json_utils +from future.standard_library import install_aliases from ott.utils import html_utils -from pelias.adapter.model.solr.solr_response import SolrResponse -from .pelias_wrapper import PeliasWrapper +from ott.utils import json_utils + +from com.github.ott.pelias.adapter.models.solr.solr_response import SolrResponse +from com.github.ott.pelias.adapter.service import pelias_json_queries +from com.github.ott.pelias.adapter.service.pelias_wrapper import PeliasWrapper + +install_aliases() -import logging log = logging.getLogger(__file__) @@ -25,17 +27,19 @@ def solr_to_pelias_param(cls, solr_params, is_rtp=False): """ ret_val = {} - text = html_utils.get_first_param(solr_params, 'q') - if text: ret_val['text'] = text + text = html_utils.get_first_param(solr_params, "q") + if text: + ret_val["text"] = text - size = html_utils.get_first_param(solr_params, 'rows') - if size: ret_val['size'] = size + size = html_utils.get_first_param(solr_params, "rows") + if size: + ret_val["size"] = size - format = html_utils.get_first_param(solr_params, 'wt') + format = html_utils.get_first_param(solr_params, "wt") if format and format == "xml": - ret_val['format'] = 'xml' + ret_val["format"] = "xml" - layers = html_utils.get_first_param(solr_params, 'fq') + _ = html_utils.get_first_param(solr_params, "fq") # TODO rtp """ if layers: @@ -57,12 +61,14 @@ def solr_to_pelias_param_str(cls, solr_params): convert SOLR dict params to string of params for calling Pelias via url """ pelias_params = cls.solr_to_pelias_param(solr_params) - ret_val = urlencode(pelias_params) # converts dict to a string after encoding each value in dict + ret_val = urlencode( + pelias_params + ) # converts dict to a string after encoding each value in dict return ret_val @classmethod def parse_json(cls, json, solr_params=None): - ret_val = SolrResponse() + ret_val: SolrResponse = SolrResponse() ret_val.parse_pelias(json, solr_params) return ret_val @@ -73,16 +79,16 @@ def fix_venues_in_pelias_response(cls, pelias_json): NOTE: 2-24-2020: this routine is only used in the SOLR wrapper the Pelias wrapper has a different rendering (see above) """ - if pelias_json.get('features', []): - for f in pelias_json.get('features', []): - p = f.get('properties') - if p and p.get('layer') == 'venue': - name = p.get('name') + if pelias_json.get("features", []): + for f in pelias_json.get("features", []): + p = f.get("properties") + if p and p.get("layer") == "venue": + name = p.get("name") if name: new_name = name - street = p.get('street') + street = p.get("street") if street: - num = p.get('housenumber') + num = p.get("housenumber") if num: new_name = "{} ({} {})".format(name, num, street) else: @@ -91,14 +97,13 @@ def fix_venues_in_pelias_response(cls, pelias_json): neighborhood = pelias_json_queries.get_neighborhood(p) if neighborhood: new_name = "{} ({})".format(name, neighborhood) - p['name'] = new_name - + p["name"] = new_name @classmethod def call_pelias_parse_results(cls, solr_params, url): param_str = cls.solr_to_pelias_param_str(solr_params) json = json_utils.stream_json(url, param_str) - #cls.fix_venues_in_pelias_response(pelias_json=json) + # cls.fix_venues_in_pelias_response(pelias_json=json) cls.fixup_response(json, is_calltaker=True, is_rtp=False) ret_val = cls.parse_json(json, solr_params) return ret_val @@ -123,5 +128,3 @@ def call_pelias(cls, solr_params, auto_url=None, search_url=None): pelias = cls.call_pelias_search(solr_params, search_url) return pelias - - diff --git a/pelias/adapter/control/pelias_wrapper.py b/com/github/ott/pelias/adapter/service/pelias_wrapper.py similarity index 64% rename from pelias/adapter/control/pelias_wrapper.py rename to com/github/ott/pelias/adapter/service/pelias_wrapper.py index 6420f51..c966c83 100644 --- a/pelias/adapter/control/pelias_wrapper.py +++ b/com/github/ott/pelias/adapter/service/pelias_wrapper.py @@ -1,15 +1,15 @@ -from ott.utils.svr.pyramid import response_utils -from ott.utils import html_utils +import logging + from ott.utils import geo_utils +from ott.utils import html_utils +from ott.utils.svr.pyramid import response_utils -from . import pelias_json_queries +from com.github.ott.pelias.adapter.service import pelias_json_queries -import logging log = logging.getLogger(__file__) class PeliasWrapper(object): - # TODO: configure this list rtp_agencies = [ "clackamas", @@ -18,7 +18,7 @@ class PeliasWrapper(object): "rideconnection", "sam", "smart", - "wapark" + "wapark", ] @classmethod @@ -30,33 +30,47 @@ def rtp_stop_filter(cls): """ if not cls._rtp_stop_filter: f = ["-{}:stops".format(a) for a in cls.rtp_agencies] - cls._rtp_stop_filter = ','.join(f) + cls._rtp_stop_filter = ",".join(f) return cls._rtp_stop_filter + _rtp_stop_filter = None @classmethod - def wrapp(cls, main_url, bkup_url, reverse_geo_url, query_string, def_size=10, in_recursion=False, is_calltaker=False, is_rtp=False): - """ will call either autocomplete or search """ + def wrapp( + cls, + main_url, + bkup_url, + reverse_geo_url, + query_string, + def_size=10, + in_recursion=False, + is_calltaker=False, + is_rtp=False, + ): + """will call either autocomplete or search""" ret_val = None # step 1: break out the size and text parameters - size = html_utils.get_numeric_value_from_qs(query_string, 'size', def_size) - text = html_utils.get_param_value_from_qs(query_string, 'text') + size = html_utils.get_numeric_value_from_qs(query_string, "size", def_size) + text = html_utils.get_param_value_from_qs(query_string, "text") # step 1b: filter agencies if we're in single-agency (TriMet) exclusive mode - #import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() if not is_rtp: - #query_string = "{}&layers={}".format(query_string, cls.rtp_stop_filter()) - pass # TODO - RTP - not yet + # query_string = "{}&layers={}".format(query_string, cls.rtp_stop_filter()) + pass # TODO - RTP - not yet # step 2 call reverse geocoder if we think text is a coord if geo_utils.is_coord(text): x, y = geo_utils.ll_from_str(text) - ll = geo_utils.xy_to_url_param_str(x, y, x_name="point.lon", y_name="point.lat", check_lat_lon=True) + ll = geo_utils.xy_to_url_param_str( + x, y, x_name="point.lon", y_name="point.lat", check_lat_lon=True + ) qs = "{}&{}".format(query_string, ll) ret_val = response_utils.proxy_json(reverse_geo_url, qs) - # step 3: call geocoder (if we didn't already reverse geocode, or if that result was null) + # step 3: call geocoder (if we didn't already reverse geocode, or if that + # result was null) if ret_val is None: # step 3a: special query string handling if text and len(text) > 1: @@ -71,17 +85,18 @@ def wrapp(cls, main_url, bkup_url, reverse_geo_url, query_string, def_size=10, i ret_val = response_utils.proxy_json(main_url, query_string) if not cls.has_features(ret_val): alt_resp = response_utils.proxy_json(bkup_url, query_string) - if alt_resp and 'features' in alt_resp: + if alt_resp and "features" in alt_resp: ret_val = alt_resp # step 4: check whether the query result has something usable... if not in_recursion: - # step 4a: if this is an admin record, let's see whether we can resub just street address + # step 4a: if this is an admin record, let's see whether we can resub just + # street address if cls.is_wrong_city_bug(ret_val): """ This code addresses the WRONG CITY bug, etc... - https://github.com/OpenTransitTools/trimet-mod-pelias/issues/23 - + https://github.com/OpenTransitTools/trimet-mod-pelias/issues/23 + Here's an address that brings up an admin record from Pelias due to the wrong city: .../pelias/autocomplete?text=11911%20SW%20TONQUIN%20RD,%20SHERWOOD,%20OR%2097140 """ @@ -89,10 +104,19 @@ def wrapp(cls, main_url, bkup_url, reverse_geo_url, query_string, def_size=10, i # step 4b: create a new query string of just the "text=NUMBER STREET" normalized_data = pelias_json_queries.find_parsed_text(ret_val) qs = "text={number} {street}".format(**normalized_data) - qs = qs.replace(' ', '%20') + qs = qs.replace(" ", "%20") # step 4c: re-call Pelias with our simple - r = cls.wrapp(main_url, bkup_url, reverse_geo_url, qs, def_size, is_calltaker=is_calltaker, is_rtp=is_rtp, in_recursion=True) + r = cls.wrapp( + main_url, + bkup_url, + reverse_geo_url, + qs, + def_size, + is_calltaker=is_calltaker, + is_rtp=is_rtp, + in_recursion=True, + ) if cls.has_features(r): ret_val = r @@ -123,10 +147,14 @@ def is_wrong_city_bug(cls, response): """ # import pdb; pdb.set_trace() ret_val = False - features = response.get('features') - if features and len(features) == 1 and pelias_json_queries.is_region_record(features[0]): + features = response.get("features") + if ( + features + and len(features) == 1 + and pelias_json_queries.is_region_record(features[0]) + ): normalized_data = pelias_json_queries.find_parsed_text(response) - if 'number' in normalized_data and 'street' in normalized_data: + if "number" in normalized_data and "street" in normalized_data: ret_val = True return ret_val @@ -144,13 +172,13 @@ def dedup_addresses(cls, pelias_json): TODO needs work and testing this mucks with the pelias_json to filter out (dedup) matching named 'features' - if filtering is applied, then the pelias_json['features'] array will be altered + if filtering is applied, then the pelias_json['features'] array will be altered examples: 1114 Cesar Chavez Blvd 1505 NW 118th Ct """ - features = pelias_json.get('features', []) + features = pelias_json.get("features", []) # step 0: make sure there are 2+ records (eg something to dedupe) if len(features) < 2: @@ -160,10 +188,10 @@ def dedup_addresses(cls, pelias_json): adds = [] new_other = [] for f in features: - p = f.get('properties') + p = f.get("properties") if p is None: continue - if p.get('layer') in ('address'): + if p.get("layer") in ("address"): adds.append(f) else: new_other.append(f) @@ -177,17 +205,39 @@ def dedup_addresses(cls, pelias_json): num_addresses = len(adds) if num_addresses > 0: new_adds.append(adds[0]) - if num_addresses >= 2: # make sure we have multiple addresses (eg duplicates to potentially dedup) - #import pdb; pdb.set_trace() - i=1 + if ( + num_addresses + >= 2 + # make sure we have multiple addresses (eg duplicates to potentially + # dedup) + ): + # import pdb; pdb.set_trace() + i = 1 while i < num_addresses: - n1 = adds[i].get('properties').get('name', "").lower().replace('.', '').replace('boulevard','blvd').replace('court','ct') - n2 = adds[i-1].get('properties').get('name', "").lower().replace('.', '').replace('boulevard','blvd').replace('court','ct') + n1 = ( + adds[i] + .get("properties") + .get("name", "") + .lower() + .replace(".", "") + .replace("boulevard", "blvd") + .replace("court", "ct") + ) + n2 = ( + adds[i - 1] + .get("properties") + .get("name", "") + .lower() + .replace(".", "") + .replace("boulevard", "blvd") + .replace("court", "ct") + ) l1 = len(n1) l2 = len(n2) - d = abs(l1 - l2) - #import pdb; pdb.set_trace() - # filter out if address names are similar (e.g., same or majority subset of one another) + d = abs(l1 - l2) + # import pdb; pdb.set_trace() + # filter out if address names are similar (e.g., same or majority + # subset of one another) if l1 > 5 and l2 > 5 and d <= 6 and (n1 in n2 or n2 in n1): i += 1 continue @@ -195,83 +245,88 @@ def dedup_addresses(cls, pelias_json): new_adds.append(features[i]) i += 1 - pelias_json['features'] = new_adds + new_other + pelias_json["features"] = new_adds + new_other return @classmethod - def fixup_response(cls, pelias_json, size=10, ele='label', is_calltaker=False, is_rtp=False): - """ will loop thru results, cleaning up / renaming / relabeling the specified element """ + def fixup_response( + cls, pelias_json, size=10, ele="label", is_calltaker=False, is_rtp=False + ): + """will loop thru results, cleaning up / renaming / relabeling the specified element""" # step 1: loop thru the records in the Pelias response if cls.has_features(pelias_json): - #cls.dedup_addresses(pelias_json) + # cls.dedup_addresses(pelias_json) - for i, f in enumerate(pelias_json.get('features')): + for i, f in enumerate(pelias_json.get("features")): if i >= size: break - p = f.get('properties') + p = f.get("properties") if p is None: continue rename = None # step 2: for venues, rename the venue with the neighborhood & city - if p.get('layer') in ('venue', 'major_employer', 'fare', 'fare_outlet'): - name = cls.get_property_value(p, 'name', 'label') + if p.get("layer") in ("venue", "major_employer", "fare", "fare_outlet"): + name = cls.get_property_value(p, "name", "label") street = pelias_json_queries.street_name(p, include_number=False) - city = pelias_json_queries.neighborhood_and_city(p, sep=' - ') + city = pelias_json_queries.neighborhood_and_city(p, sep=" - ") rename = pelias_json_queries.append3(name, street, city) # step 3: for stops, possibly reduce the size of the string - if "stops" in p.get('layer'): - name = cls.get_property_value(p, 'name', 'label') + if "stops" in p.get("layer"): + name = cls.get_property_value(p, "name", "label") - # 3a: if we're just a single agency (and TriMet), then strip off junk - if not is_rtp and "TRIMET" in p.get('id'): - name = name.replace("TriMet Stop ", "") + # 3a: if we're just a single agency (and TriMet), then strip off + # junk + if not is_rtp and "TRIMET" in p.get("id"): + name = name.replace("TriMet Stop ", "") # 3b: remove state and country from the label if name and len(name) > 10: - city = pelias_json_queries.neighborhood_and_city(p, sep=' - ') + city = pelias_json_queries.neighborhood_and_city(p, sep=" - ") rename = pelias_json_queries.append(name, city) # step 4: rename routes - elif p.get('layer') == 'routes': - name = cls.get_property_value(p, 'name', 'label') + elif p.get("layer") == "routes": + name = cls.get_property_value(p, "name", "label") route_lbl = "Transit Route" - if "TRIMET" in p.get('id'): - route_lbl = "TriMet Route" + if "TRIMET" in p.get("id"): + route_lbl = "TriMet Route" rename = "{} ({})".format(name, route_lbl) - + # step 5: Post Office ... add zipcode to label - elif p.get('layer') == 'post_office': - name = cls.get_property_value(p, 'name', 'label') - zipcode = cls.get_property_value(p, 'postalcode') - rename = pelias_json_queries.append3(name, 'Post Office', zipcode, sep1=' ') + elif p.get("layer") == "post_office": + name = cls.get_property_value(p, "name", "label") + zipcode = cls.get_property_value(p, "postalcode") + rename = pelias_json_queries.append3( + name, "Post Office", zipcode, sep1=" " + ) # step 6: default rename is to add city or region, etc... else: - name = cls.get_property_value(p, 'name', 'label') + name = cls.get_property_value(p, "name", "label") city = pelias_json_queries.city_neighborhood_or_county(p) rename = pelias_json_queries.append(name, city) - # step 6: append '*' to any calltaker response when dealing with interpolated recs - if is_calltaker and p.get('match_type') == "interpolated": - rename = "*" + rename + # step 6: append '*' to any calltaker response when dealing with + # interpolated recs + if is_calltaker and p.get("match_type") == "interpolated": + rename = "*" + rename # step 7: apply the rename to this record's properties dict if rename: p[ele] = rename - - #### TODO -- replace the routines below with 'fixup_response' above ??? + # TODO -- replace the routines below with 'fixup_response' above ??? @classmethod def rename(cls, pelias_json, def_val=None): ret_val = def_val try: - name = pelias_json.get('name') + name = pelias_json.get("name") if name: new_name = name if name: @@ -282,6 +337,6 @@ def rename(cls, pelias_json, def_val=None): new_name = "{} ({})".format(name, neighborhood) if new_name: ret_val = new_name - except: - pass + except Exception as e: + log.info(e) return ret_val diff --git a/com/github/ott/pelias/adapter/service/solr_service.py b/com/github/ott/pelias/adapter/service/solr_service.py new file mode 100644 index 0000000..01f82a7 --- /dev/null +++ b/com/github/ott/pelias/adapter/service/solr_service.py @@ -0,0 +1,35 @@ +from ott.utils import object_utils + +from com.github.ott.pelias.adapter.models.solr.solr_response import SolrResponse +from com.github.ott.pelias.adapter.service.config import ( + pelias_autocomplete_url, + pelias_search_url, +) +from com.github.ott.pelias.adapter.service.pelias_to_solr import PeliasToSolr + + +def solr_api(request, def_rows=10) -> SolrResponse: + """will handle SOLR routers params, then call pelias""" + solr_params = {} + + query_params = dict(request.query_params) + + solr_params["q"] = ( + query_params["q"] if "q" in query_params else query_params["text"] + ) + + rows = query_params["rows"] if "rows" in query_params else None + solr_params["rows"] = object_utils.safe_int(rows, def_rows) + + fq = query_params.get("fq") + if fq: + solr_params["fq"] = fq + + wt = query_params.get("wt", "json") + solr_params["wt"] = wt + + # step 2: wrap call to Pelias and get SOLR response + ret_val = PeliasToSolr.call_pelias( + solr_params, pelias_autocomplete_url, pelias_search_url + ) + return ret_val diff --git a/config/base.ini b/config/base.ini deleted file mode 100644 index c6f4a15..0000000 --- a/config/base.ini +++ /dev/null @@ -1,38 +0,0 @@ -### -# app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html -# https://pastedeploy.readthedocs.io/en/latest/index.html?highlight=config%3A#applications -### -[DEFAULT] -def_agency = TriMet - -[server:main] -use = egg:waitress#main -host = 0.0.0.0 -port = 45554 -threads = 200 -connection_limit = 200 -channel_timeout = 30 -ident = %(def_agency)s - -[app:main] -use = egg:pelias.adapter - -enable_cors_headers = true -pyramid.reload_templates = false -pyramid.debug_all = false -pyramid.reload_all = false -pyramid.debug_authorization = false -pyramid.debug_notfound = false -pyramid.debug_routematch = false -pyramid.default_locale_name = en -pyramid.includes = pyramid_exclog pyramid_tm - -pelias_search_url = %(pelias_instance)s/v1/search -pelias_autocomplete_url = %(pelias_instance)s/v1/autocomplete -pelias_reverse_url = %(pelias_instance)s/v1/reverse - -route_stop_str_url = http://maps.trimet.org/ti/index/stops -show_route_stops = via_param # never, always, via_param -- currently unused - -timeout_mins = 60 diff --git a/config/local_pelias.ini b/config/local_pelias.ini deleted file mode 100644 index 7a1432f..0000000 --- a/config/local_pelias.ini +++ /dev/null @@ -1,50 +0,0 @@ -[DEFAULT] -pelias_instance = http://localhost:4000 - -[server:main] -use = config:base.ini - -[app:main] -use = config:base.ini - - -### -# logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html -### -logging_config_file = %(__file__)s - - -[loggers] -keys = root, exc_logger - -[handlers] -keys = logfile, exc_handler - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = logfile - -[handler_logfile] -class = handlers.TimedRotatingFileHandler -args = ('logs/app.log', 'midnight', 1, 30, 'utf-8') -level = INFO -formatter = generic - -[logger_exc_logger] -level = ERROR -handlers = exc_handler -qualname = exc_logger - -[handler_exc_handler] -class = handlers.TimedRotatingFileHandler -args = ('logs/error.log', 'midnight', 1, 30, 'utf-8') -level = ERROR -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s -datefmt = %H:%M:%S diff --git a/config/pelias.adapter.ini b/config/pelias.adapter.ini deleted file mode 120000 index 3e786f7..0000000 --- a/config/pelias.adapter.ini +++ /dev/null @@ -1 +0,0 @@ -production.ini \ No newline at end of file diff --git a/config/production.ini b/config/production.ini deleted file mode 100644 index 6771df8..0000000 --- a/config/production.ini +++ /dev/null @@ -1,49 +0,0 @@ -[DEFAULT] -pelias_instance = https://ws.trimet.org/pelias - -[server:main] -use = config:base.ini - -[app:main] -use = config:base.ini - - -### -# logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html -### -logging_config_file = %(__file__)s - -[loggers] -keys = root, exc_logger - -[handlers] -keys = logfile, exc_handler - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = logfile - -[handler_logfile] -class = handlers.TimedRotatingFileHandler -args = ('logs/app.log', 'midnight', 1, 30, 'utf-8') -level = INFO -formatter = generic - -[logger_exc_logger] -level = ERROR -handlers = exc_handler -qualname = exc_logger - -[handler_exc_handler] -class = handlers.TimedRotatingFileHandler -args = ('logs/error.log', 'midnight', 1, 30, 'utf-8') -level = ERROR -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s -datefmt = %H:%M:%S diff --git a/config/staging.ini b/config/staging.ini deleted file mode 100644 index 3932a27..0000000 --- a/config/staging.ini +++ /dev/null @@ -1,49 +0,0 @@ -[DEFAULT] -pelias_instance = https://ws-st.trimet.org/pelias - -[server:main] -use = config:base.ini - -[app:main] -use = config:base.ini - - -### -# logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html -### -logging_config_file = %(__file__)s - -[loggers] -keys = root, exc_logger - -[handlers] -keys = logfile, exc_handler - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = logfile - -[handler_logfile] -class = handlers.TimedRotatingFileHandler -args = ('logs/app.log', 'midnight', 1, 30, 'utf-8') -level = INFO -formatter = generic - -[logger_exc_logger] -level = INFO -handlers = exc_handler -qualname = exc_logger - -[handler_exc_handler] -class = handlers.TimedRotatingFileHandler -args = ('logs/error.log', 'midnight', 1, 30, 'utf-8') -level = INFO -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s -datefmt = %H:%M:%S diff --git a/dev.env b/dev.env new file mode 100644 index 0000000..338af27 --- /dev/null +++ b/dev.env @@ -0,0 +1,7 @@ +APP_NAME=FastAPI Oracle Template +DEBUG=True +ENVIRONMENT=dev + + +PELIAS_URL=https://ws.trimet.org +MAPS_URL=https://maps.trimet.org \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..748125a --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,64 @@ +# ============================ +# 1. Builder stage +# ============================ +FROM python:3.12-slim AS builder + +ENV POETRY_VERSION=2.2.1 \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + POETRY_NO_INTERACTION=1 \ + POETRY_HOME="/opt/poetry" \ + POETRY_VIRTUALENVS_CREATE=false + +ENV PATH="/root/.local/bin:${POETRY_HOME}/bin:${PATH}" + +WORKDIR /pelias_adapter + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential curl && rm -rf /var/lib/apt/lists/* + +RUN curl -sSL https://install.python-poetry.org | python3 - + +COPY ./ /pelias_adapter/ + +COPY ./docker/entrypoint.sh /pelias_adapter/entrypoint.sh + +# Make sure entrypoint is executable while still root +RUN chmod +x /pelias_adapter/entrypoint.sh + + +RUN poetry install --no-interaction --no-ansi --without dev + + +# ============================ +# 2. Runtime stage +# ============================ +FROM python:3.12-slim AS runtime + +WORKDIR /pelias_adapter +ENV POETRY_VERSION=2.2.1 \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + POETRY_NO_INTERACTION=1 \ + POETRY_HOME="/opt/poetry" \ + POETRY_VIRTUALENVS_CREATE=false + +ENV PATH="/root/.local/bin:${POETRY_HOME}/bin:${PATH}" + +# Copy dependencies and app from builder +COPY --from=builder /usr/local /usr/local +COPY --from=builder /pelias_adapter /pelias_adapter + +RUN chmod +x /pelias_adapter/entrypoint.sh +RUN ls -la /pelias_adapter + +RUN useradd -m pelias_user && chown -R pelias_user:pelias_user /pelias_adapter +USER pelias_user + + +RUN rm -rf /pelias_adapter/docker + +# Expose FastAPI port +EXPOSE 8000 + +ENTRYPOINT ["/pelias_adapter/entrypoint.sh"] diff --git a/docker/buildDocker.sh b/docker/buildDocker.sh new file mode 100755 index 0000000..564245d --- /dev/null +++ b/docker/buildDocker.sh @@ -0,0 +1,2 @@ +docker build --debug --rm --progress=plain -f docker/Dockerfile -t pelias_adapter:latest . + diff --git a/docker/compose.yml b/docker/compose.yml new file mode 100644 index 0000000..afa87da --- /dev/null +++ b/docker/compose.yml @@ -0,0 +1,17 @@ +services: + pelias_adapter: + build: + context: ../ + dockerfile: docker/Dockerfile + container_name: pelias_adapter + image: pelias_adapter:latest + ports: + - "8000:8000" + environment: + PYTHONUNBUFFERED: "1" + PYTHONDONTWRITEBYTECODE: "1" + PORT: "8000" + ENVIRONMENT: "dev" + working_dir: /pelias_adapter + entrypoint: /pelias_adapter/entrypoint.sh + restart: unless-stopped diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..4723097 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +# Default values +PORT=${PORT:-8000} +ENVIRONMENT=${ENVIRONMENT:-prod} + +export PYTHONPATH="/pelias_adapter:${PYTHONPATH:-}" + + +# Configure workers and log level based on environment +case "$ENVIRONMENT" in + dev) + WORKERS=1 + LOG_LEVEL=debug + echo "🧩 Environment: DEVELOPMENT" + ;; + stage) + WORKERS=2 + LOG_LEVEL=info + echo "🧪 Environment: STAGING" + ;; + prod|*) + CORES=$(nproc) + WORKERS=$((CORES * 2 + 1)) + LOG_LEVEL=warning + echo "🚀 Environment: PRODUCTION (Detected ${CORES} cores → ${WORKERS} workers)" + ;; +esac + +# Print runtime info +echo "Starting FastAPI app with the following settings:" +echo " PORT: ${PORT}" +echo " WORKERS: ${WORKERS}" +echo " LOG_LEVEL: ${LOG_LEVEL}" +echo "--------------------------------------------" + +# Launch Gunicorn with Uvicorn workers +exec gunicorn main:app \ + -k uvicorn.workers.UvicornWorker \ + --bind 0.0.0.0:${PORT} \ + --workers "${WORKERS}" \ + --timeout 60 \ + --log-level "${LOG_LEVEL}" \ + --access-logfile - \ + --error-logfile - diff --git a/config/development.ini b/logging.ini similarity index 54% rename from config/development.ini rename to logging.ini index 5d4afc6..20c4116 100644 --- a/config/development.ini +++ b/logging.ini @@ -1,26 +1,3 @@ -[DEFAULT] -pelias_instance = http://rj-dv-mapgeo01:4000 - -[filter:linesman] -use = egg:linesman#profiler - -[server:main] -use = config:base.ini - -[app:main] -use = config:base.ini - -pyramid.reload_templates = true -pyramid.debug_authorization = true -pyramid.debug_all = true -pyramid.reload_all = true -pyramid.default_locale_name = en -pyramid.includes = pyramid_tm - -### -# logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html -### [loggers] keys = root, sqlalchemy diff --git a/main.py b/main.py new file mode 100644 index 0000000..9ab5c0f --- /dev/null +++ b/main.py @@ -0,0 +1,47 @@ +import logging +import logging.config + +from fastapi import FastAPI, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from com.github.ott.pelias.adapter.core.errors import PeliasAdapterError +from com.github.ott.pelias.adapter.core.config import settings + +from com.github.ott.pelias.adapter.routers import core, pelias, solr + +# Configure logging +logging.config.fileConfig("logging.ini", disable_existing_loggers=False) +logger = logging.getLogger(__name__) + + +# Create FastAPI application +app = FastAPI( + title=settings.app_name, + description="FastAPI template with Oracle database using Instant Client 21 and SQLAlchemy 2.x", + version="1.0.0", + debug=settings.debug, +) + +# Add CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # TODO Configure appropriately for production + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include API routes +app.include_router(pelias.router, prefix="/pelias/v1", tags=["pelias"]) +app.include_router(solr.router, prefix="/solr/v1", tags=["solr"]) +app.include_router(core.router, prefix="/core/v1", tags=["core"]) + + +def add_exception_handlers(app): + @app.exception_handler(PeliasAdapterError) + async def pelias_wrapper_error_handler(request: Request, exc: PeliasAdapterError): + return JSONResponse(status_code=400, content={"error": exc.message}) + + +add_exception_handlers(app) diff --git a/pelias/__init__.py b/pelias/__init__.py deleted file mode 100644 index de40ea7..0000000 --- a/pelias/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) diff --git a/pelias/adapter/pyramid/app.py b/pelias/adapter/pyramid/app.py deleted file mode 100644 index 73841e4..0000000 --- a/pelias/adapter/pyramid/app.py +++ /dev/null @@ -1,30 +0,0 @@ -from pyramid.config import Configurator -from pyramid.events import NewRequest - -from ott.utils.svr.pyramid import app_utils - -import logging -log = logging.getLogger(__file__) - - - -def main(global_config, **settings): - """ This function returns a Pyramid WSGI application. - """ - #import pdb; pdb.set_trace() - config = Configurator(settings=settings) - - # logging config for pserve / wsgi - if settings and 'logging_config_file' in settings: - from pyramid.paster import setup_logging - setup_logging(settings['logging_config_file']) - - from . import views - config.include(views.do_view_config) - config.scan('pelias.adapter.pyramid') - - # CORS -- might not make this call in production (eliminate a bit of overheads, as CORS is handled by Apache) - if settings and settings.get('enable_cors_headers') == 'true': - config.add_subscriber(app_utils.add_cors_headers_response_callback, NewRequest) - - return config.make_wsgi_app() diff --git a/pelias/adapter/pyramid/views.py b/pelias/adapter/pyramid/views.py deleted file mode 100644 index cc76776..0000000 --- a/pelias/adapter/pyramid/views.py +++ /dev/null @@ -1,147 +0,0 @@ -from pyramid.view import view_config - -from ott.utils import json_utils -from ott.utils import object_utils -from ott.utils.svr.pyramid import response_utils -from ott.utils.svr.pyramid import globals - -from pelias.adapter.control.pelias_to_solr import PeliasToSolr -from pelias.adapter.control.pelias_wrapper import PeliasWrapper - -import logging -log = logging.getLogger(__file__) - - -# urls -pelias_autocomplete_url = None -pelias_search_url = None -pelias_reverse_url = None -route_stop_str_url = None - - -def config_globals(cfg): - """ - :see: config/base.ini for configured url strings - """ - global pelias_autocomplete_url - global pelias_search_url - global pelias_reverse_url - global route_stop_str_url - - pelias_autocomplete_url = cfg.registry.settings.get('pelias_autocomplete_url') - pelias_search_url = cfg.registry.settings.get('pelias_search_url') - pelias_reverse_url = cfg.registry.settings.get('pelias_reverse_url') - route_stop_str_url = cfg.registry.settings.get('route_stop_str_url') - - -def do_view_config(cfg): - config_globals(cfg) - cfg.add_route('pelias', '/pelias') - cfg.add_route('pelias_proxy', '/proxy') - cfg.add_route('pelias_services', '/pelias/{service}') - cfg.add_route('pelias_rtp', '/pelias/rtp/{service}') - cfg.add_route('solr', '/solr') - cfg.add_route('solr_select', '/solr/{select}') - - -@view_config(route_name='solr', renderer='json', http_cache=globals.CACHE_LONG) -def solr_json(request): - """ - SOLR response wrapper... - - SOLR Queries: - https://trimet.org/solr/select?q=12&rows=6&wt=xml&fq=type%3Astop - - :param request: - :return: - """ - - def solr_api(request, def_rows=10): - """ will handle SOLR api params, then call pelias """ - solr_params = {} - - # step 1: SOLR params - query = request.params.get('q') - if not query: - query = request.params.get('text') - solr_params['q'] = query - - rows = request.params.get('rows') - solr_params['rows'] = object_utils.safe_int(rows, def_rows) - - fq = request.params.get('fq') - if fq: - solr_params['fq'] = fq - - wt = request.params.get('wt', 'json') - solr_params['wt'] = wt - - # step 2: wrap call to Pelias and get SOLR response - ret_val = PeliasToSolr.call_pelias(solr_params, pelias_autocomplete_url, pelias_search_url) - return ret_val - - # import pdb; pdb.set_trace() - ret_val = None - - try: - json = solr_api(request) - ret_val = response_utils.dao_response(json) - except Exception as e: - log.warning(e) - ret_val = response_utils.sys_error_response() - return ret_val - - -@view_config(route_name='solr_select', renderer='json', http_cache=globals.CACHE_LONG) -def solr_select(request): - """ catches ../solr/select (or ../solr/search, ../solr/autocomplete, ../solr/etc...) paths """ - return solr_json(request) - - -@view_config(route_name='pelias_services', renderer='json', http_cache=globals.CACHE_LONG) -def pelias_services(request, is_rtp=False): - """ - calls pellias wrapper based on specified service (autocomplete == default, search or reverse) - :return: json data from Pelias ... after fixing up the response in ways defined by 'PeliasWrapper' - """ - # import pdb; pdb.set_trace() - - # step 1: find the service based on pelias/{service}? ... default to autocomplete - try: - service = request.matchdict['service'] - except: - service = "autocomplete" - - # step 2: call the wrapper - if service == "autocomplete": - ret_val = PeliasWrapper.wrapp(pelias_autocomplete_url, pelias_search_url, pelias_reverse_url, request.query_string, is_rtp=is_rtp) - elif service == "search": - ret_val = PeliasWrapper.wrapp(pelias_search_url, pelias_autocomplete_url, pelias_reverse_url, request.query_string, is_rtp=is_rtp) - elif service == "reverse": - ret_val = PeliasWrapper.reverse(pelias_reverse_url, request.query_string) - else: - ret_val = response_utils.sys_error_response() - - # step 3: append the hostname to the response - json_utils.append_hostname_to_json(ret_val) - - """ - TODO: WIP find and add stops / in/out / etc... - x = object_utils.find_elements('properties', ret_val) - for z in x: - z["XXXX"] = 'MMMMMMMMMMM' - import pdb; pdb.set_trace() - """ - return ret_val - - -@view_config(route_name='pelias', renderer='json', http_cache=globals.CACHE_LONG) -@view_config(route_name='pelias_proxy', renderer='json', http_cache=globals.CACHE_LONG) -def pelias(request): - """ call pelias_services() above w/out specifying a service ... so will default to autocomplete """ - return pelias_services(request) - - -@view_config(route_name='pelias_rtp', renderer='json', http_cache=globals.CACHE_LONG) -def pelias_rtp(request): - return pelias_services(request, is_rtp=True) diff --git a/pelias/adapter/tests/data/autocomplete_no_results.json b/pelias/adapter/tests/data/autocomplete_no_results.json deleted file mode 100644 index abe63af..0000000 --- a/pelias/adapter/tests/data/autocomplete_no_results.json +++ /dev/null @@ -1 +0,0 @@ -{"geocoding":{"version":"0.2","attribution":"http://pelias.mapzen.com/v1/attribution","query":{"text":"7-11 ticket","parser":"addressit","tokens":["7-11","ticket"],"size":10,"private":false,"focus.point.lat":45.52,"focus.point.lon":-122.67,"lang":{"name":"English","iso6391":"en","iso6393":"eng","defaulted":false}},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1518989052715},"type":"FeatureCollection","features":[]} \ No newline at end of file diff --git a/pelias/adapter/tests/data/search_7-11_ticket.json b/pelias/adapter/tests/data/search_7-11_ticket.json deleted file mode 100644 index e075a4c..0000000 --- a/pelias/adapter/tests/data/search_7-11_ticket.json +++ /dev/null @@ -1,2 +0,0 @@ -{"geocoding":{"version":"0.2","attribution":"http://pelias.mapzen.com/v1/attribution","query":{"text":"7-11 ticket","size":10,"private":false,"focus.point.lat":45.52,"focus.point.lon":-122.67,"lang":{"name":"English","iso6391":"en","iso6393":"eng","defaulted":false},"querySize":20,"parser":"addressit"},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1518988873092},"type":"FeatureCollection", - "features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.601504,45.555694]},"properties":{"id":"node:2694265256","gid":"openstreetmap:venue:node:2694265256","layer":"venue","source":"openstreetmap","source_id":"node:2694265256","name":"7-11","confidence":0.879,"distance":6.66,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"Oregon","region_gid":"whosonfirst:region:85688513","region_a":"OR","county":"Multnomah County","county_gid":"whosonfirst:county:102081631","locality":"Portland","locality_gid":"whosonfirst:locality:101715829","neighbourhood":"Cully","neighbourhood_gid":"whosonfirst:neighbourhood:85893177","label":"7-11, Portland, OR, USA"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.715904,45.510118]},"properties":{"id":"way:119243635","gid":"openstreetmap:venue:way:119243635","layer":"venue","source":"openstreetmap","source_id":"way:119243635","name":"TIcket Booths","confidence":0.668,"distance":3.751,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"Oregon","region_gid":"whosonfirst:region:85688513","region_a":"OR","county":"Multnomah County","county_gid":"whosonfirst:county:102081631","locality":"Portland","locality_gid":"whosonfirst:locality:101715829","neighbourhood":"Sylvan-Highlands","neighbourhood_gid":"whosonfirst:neighbourhood:85893251","label":"TIcket Booths, Portland, OR, USA"},"bbox":[-122.715949,45.510062,-122.715836,45.510197]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.679567,45.518951]},"properties":{"id":"1640::TRIMET::fare_outlet","gid":"transit:fare_outlet:1640::TRIMET::fare_outlet","layer":"fare_outlet","source":"transit","source_id":"1640::trimet::fare_outlet","name":"TriMet Ticket Office","region":"Oregon","locality":"Portland","neighbourhood_gid":"whosonfirst:neighbourhood:85867131","housenumber":"701","distance":0.757,"accuracy":"centroid","region_gid":"whosonfirst:region:85688513","county_gid":"whosonfirst:county:102081631","continent":"North America","country":"United States","country_a":"USA","locality_gid":"whosonfirst:locality:101715829","postalcode":"97204","region_a":"OR","street":"SW 6th Ave -- Pioneer Courthouse Square","confidence":0.668,"country_gid":"whosonfirst:country:85633793","continent_gid":"whosonfirst:continent:102191575","label":"TriMet Ticket Office, Portland, OR, USA","county":"Multnomah County","neighbourhood":"Downtown"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.679567,45.518948]},"properties":{"id":"1640::TRIMET::fare","gid":"transit:fare:1640::TRIMET::fare","layer":"fare","source":"transit","source_id":"1640::trimet::fare","name":"TriMet Ticket Office (HOP Fastpass)","street":"701 SW 6th Ave","confidence":0.645,"distance":0.757,"accuracy":"centroid","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"Oregon","region_gid":"whosonfirst:region:85688513","region_a":"OR","county":"Multnomah County","county_gid":"whosonfirst:county:102081631","locality":"Portland","locality_gid":"whosonfirst:locality:101715829","neighbourhood":"Downtown","neighbourhood_gid":"whosonfirst:neighbourhood:85867131","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"TriMet Ticket Office (HOP Fastpass), Portland, OR, USA"}}],"bbox":[-122.715949,45.510062,-122.601504,45.555694]} \ No newline at end of file diff --git a/pelias/adapter/tests/tests.py b/pelias/adapter/tests/tests.py deleted file mode 100644 index bcfd2f2..0000000 --- a/pelias/adapter/tests/tests.py +++ /dev/null @@ -1,93 +0,0 @@ -import unittest -from pyramid import testing - -from ott.utils import json_utils -from ott.utils import file_utils -from pelias.adapter.control.pelias_to_solr import PeliasToSolr -from pelias.adapter.model.solr.solr_stop_record import SolrStopRecord - -PORT="45554" - - -class TestPeliasToSolr(unittest.TestCase): - def setUp(self): - self.base_path = file_utils.get_file_dir(__file__) - self.auto_url = "https://ws-st.trimet.org/pelias/v1/autocomplete" - self.search_url = "https://ws-st.trimet.org/pelias/v1/search" - - def tearDown(self): - pass - - def test_pelias_to_solr(self): - p = self.parse('./data/search13135.json') - self.assertTrue(len(p.response.docs) > 0) - - def test_stops(self): - p = self.parse('./data/search13135.json') - num_stops = 0 - for d in p.response.docs: - if isinstance(d, SolrStopRecord): - num_stops += 1 - self.assertTrue(len(d.stop_id) > 0) - self.assertTrue(len(d.agency_id) > 0) - # print d.stop_id - self.assertTrue(num_stops > 0) - - def test_unique_coords(self): - p = self.parse('./data/autocomplete-hop-fastpass.json') - coords = [] - for d in p.response.docs: - self.assertTrue(d.lat != 0.0) - self.assertTrue(d.lon != 0.0) - self.assertTrue(d.lon not in coords) - self.assertTrue(d.lat not in coords) - coords.append(d.lat) - coords.append(d.lon) - - def test_names_labels(self): - p = self.parse('./data/autocomplete-hop-fastpass.json') - for d in p.response.docs: - self.assertTrue("HOP Fastpass" in d.name) - - def test_solr_to_pelias_params(self): - solr_params = {} - solr_params['q'] = 'val' - solr_params['rows'] = '6' - pelias_params_str = PeliasToSolr.solr_to_pelias_param_str(solr_params) - self.assertTrue("text=val" in pelias_params_str) - self.assertTrue("size=6" in pelias_params_str) - - def test_call_live_pelias_server(self): - """ - https://ws-st.trimet.org/pelias/v1/autocomplete?text=888%20SE%20Lambert%20St - :return: - """ - solr_params = {} - solr_params['q'] = 'val' - solr_params['rows'] = '6' - pelias_params_str = PeliasToSolr.solr_to_pelias_param_str(solr_params) - self.assertTrue("text=val" in pelias_params_str) - self.assertTrue("size=6" in pelias_params_str) - - def test_switch_autocomplete_to_search_service(self): - """ - send an interpolated address to the query, and expect that - - https://ws-st.trimet.org/pelias/v1/autocomplete?text=888%20SE%20Lambert%20St - https://ws-st.trimet.org/pelias/v1/search?text=888%20SE%20Lambert%20St - """ - solr_params = {} - solr_params['q'] = '888 SE Lambert St' - res = PeliasToSolr.call_pelias(solr_params, self.auto_url, self.search_url) - self.assertTrue(res.num_records() > 0) - self.assertTrue("888 SE Lambert" in res.response.docs[0].name) - - def test_no_results(self): - p = self.parse('./data/autocomplete_no_results.json') - self.assertTrue(len(p.response.docs) == 0) - - def parse(self, file_name): - file_path = file_utils.path_join(self.base_path, file_name) - json = json_utils.file_to_json(file_path) - p = PeliasToSolr.parse_json(json) - return p diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..b22a2a2 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1742 @@ +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.11.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, + {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +trio = ["trio (>=0.31.0)"] + +[[package]] +name = "asgi-lifespan" +version = "2.1.0" +description = "Programmatic startup/shutdown of ASGI apps." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "asgi-lifespan-2.1.0.tar.gz", hash = "sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308"}, + {file = "asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f"}, +] + +[package.dependencies] +sniffio = "*" + +[[package]] +name = "autoflake" +version = "2.3.1" +description = "Removes unused imports and unused variables" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, + {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" + +[[package]] +name = "autopep8" +version = "2.3.2" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, +] + +[package.dependencies] +pycodestyle = ">=2.12.0" + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["dev"] +files = [ + {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, + {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "25.9.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, + {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, + {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, + {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, + {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, + {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, + {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, + {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, + {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, + {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, + {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, + {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, + {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, + {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, + {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, + {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, + {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, + {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, + {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, + {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, + {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, + {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +pytokens = ">=0.1.10" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2025.10.5" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, +] + +[[package]] +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, +] + +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + +[[package]] +name = "click" +version = "8.3.0" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +files = [ + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} + +[[package]] +name = "coverage" +version = "7.11.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, + {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, + {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, + {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, + {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, + {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, + {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, + {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, + {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, + {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, + {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, + {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, + {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, + {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, + {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, + {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, + {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, + {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, + {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, + {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, + {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, + {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, + {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, + {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, + {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, + {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, + {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, + {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, + {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, + {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, + {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, + {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, + {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, + {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, + {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, + {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "cryptography" +version = "46.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main"] +files = [ + {file = "cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b"}, + {file = "cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1"}, + {file = "cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b"}, + {file = "cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee"}, + {file = "cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb"}, + {file = "cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470"}, + {file = "cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36"}, + {file = "cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a"}, + {file = "cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c"}, + {file = "cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1"}, + {file = "cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9"}, + {file = "cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0"}, + {file = "cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e"}, + {file = "cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90"}, + {file = "cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be"}, + {file = "cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c"}, + {file = "cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62"}, + {file = "cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1"}, + {file = "cryptography-46.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f25a41f5b34b371a06dad3f01799706631331adc7d6c05253f5bca22068c7a34"}, + {file = "cryptography-46.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e12b61e0b86611e3f4c1756686d9086c1d36e6fd15326f5658112ad1f1cc8807"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1d3b3edd145953832e09607986f2bd86f85d1dc9c48ced41808b18009d9f30e5"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fe245cf4a73c20592f0f48da39748b3513db114465be78f0a36da847221bd1b4"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2b9cad9cf71d0c45566624ff76654e9bae5f8a25970c250a26ccfc73f8553e2d"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9bd26f2f75a925fdf5e0a446c0de2714f17819bf560b44b7480e4dd632ad6c46"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7282d8f092b5be7172d6472f29b0631f39f18512a3642aefe52c3c0e0ccfad5a"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c4b93af7920cdf80f71650769464ccf1fb49a4b56ae0024173c24c48eb6b1612"}, + {file = "cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe"}, +] + +[package.dependencies] +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "fastapi" +version = "0.119.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi-0.119.0-py3-none-any.whl", hash = "sha256:90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2"}, + {file = "fastapi-0.119.0.tar.gz", hash = "sha256:451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.49.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "flake8" +version = "7.3.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" + +[[package]] +name = "future" +version = "1.0.0" +description = "Clean single-source support for Python 3 and 2" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] +files = [ + {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, + {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"" +files = [ + {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, + {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, + {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, + {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, + {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, + {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, + {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, + {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil", "setuptools"] + +[[package]] +name = "gunicorn" +version = "23.0.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, + {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "legacy-cgi" +version = "2.6.3" +description = "Fork of the standard library cgi and cgitb modules removed in Python 3.13" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "legacy_cgi-2.6.3-py3-none-any.whl", hash = "sha256:6df2ea5ae14c71ef6f097f8b6372b44f6685283dc018535a75c924564183cdab"}, + {file = "legacy_cgi-2.6.3.tar.gz", hash = "sha256:4c119d6cb8e9d8b6ad7cc0ddad880552c62df4029622835d06dfd18f438a8154"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "oracledb" +version = "1.4.2" +description = "Python interface to Oracle Database" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "oracledb-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fba71bbc0ec8fc806236a30d1212d2f516519c0658f0022f3d5fb593cb4e9b2"}, + {file = "oracledb-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f1d7c4d3d68572fa0ef3d3973dd0905c4f30e31d91de6a8da771223937fefa9"}, + {file = "oracledb-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5b9542d77b86c80dcc0314458cb15619831c06f232bbdbd77863f8ab5921b3"}, + {file = "oracledb-1.4.2-cp310-cp310-win32.whl", hash = "sha256:db99de736ed7996bd428ad0a4eb154543d6dd4add2a2b67b46a51cbf761e01ec"}, + {file = "oracledb-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:facf47d6787a0d2ab7cf8dfa68572ea4a3f1cb022843a8ff3b9f87d75ba7c815"}, + {file = "oracledb-1.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:56667754b7d4add421edebb624e772f64f3a192e9ff5a7d3ed32d79ed1d37a01"}, + {file = "oracledb-1.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abc152418518fb8e433403afe39b6d2a71faa8653316d6a8e1853bb152584e84"}, + {file = "oracledb-1.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2152dffc586fd7544bd0dd883db4ab93cf17a0bfece8f4d305353f4bdebc627c"}, + {file = "oracledb-1.4.2-cp311-cp311-win32.whl", hash = "sha256:99d08c406752c38c3f3e863db372d1f74adb4d23dd6a34437ddea9fe3f437a37"}, + {file = "oracledb-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5ab88775074b6a758951fea09e53a824343ab53cb84e5a662a52ef5c5799a2b"}, + {file = "oracledb-1.4.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e482cd62cf2c1eb9d655f95d5e87e7903ad83fd759b3c2b2b634d4480ff9d086"}, + {file = "oracledb-1.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69302569a08a9bc136c1032d72222545271100460d1dcde7f9bb8fa928efb11f"}, + {file = "oracledb-1.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21f74ed6de46465020b23f8fe765f246d835e121482009f7389cb693686f3151"}, + {file = "oracledb-1.4.2-cp312-cp312-win32.whl", hash = "sha256:7e13acc1dab125506b87e536d0e3ed2e1482b61cadae7ba140471c4d2b9315e3"}, + {file = "oracledb-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:dea6a0c5150ebb52c0ba617b636d2f5af40fef1d0901e3df3f3f0bcc03359ac7"}, + {file = "oracledb-1.4.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecdebcca26e9964903dd36d01bb95bbf64cd4403a741f8ec7ad9122073fb30a8"}, + {file = "oracledb-1.4.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52a8b99e00cda8de6ca8e887e00dfb60753eab2b98cf1812c7fb7033e757b5fc"}, + {file = "oracledb-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d77808966802c36995ac81107ee8e3ec188030fc81f753d813edbb604c8bf584"}, + {file = "oracledb-1.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420fd1571426bd4d645849977c4660dcb953387166684d55e5032abf25aac2ed"}, + {file = "oracledb-1.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6028c8edf53fa13781cdd9324c02e41d6072f5b753e6c6a37c1e1081587e6b2"}, + {file = "oracledb-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:2154d4c71f421d2be8fbcbd51174e6fc7194346e74e903882c6be43e6ac03fed"}, + {file = "oracledb-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c99e5ade66a04e57b8ff98dd9e1a3068426835446c76d7fa6b8d2100f1d32a52"}, + {file = "oracledb-1.4.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:34fabe0b96a4ca3369bfd3edf570455aad16d64e62563bb493cd4870c51724e3"}, + {file = "oracledb-1.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf56e92ad93b3822918dec26f6c0948904a3ac5ab49a8246c1e48c65933bbcaa"}, + {file = "oracledb-1.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5050aff415b9ef516b271f52e667f52dde9f236bd36ffb6c2e080b9011e6d19d"}, + {file = "oracledb-1.4.2-cp38-cp38-win32.whl", hash = "sha256:1d21b92854b8ea7fd6b216743e7e8585b666031ca2d2b3700c23b62710648c03"}, + {file = "oracledb-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:87243baec69b71b3bdb2b9e3962812d66f75953ae1773251c8db510426470761"}, + {file = "oracledb-1.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8e8b306d8032436d0619191021379f298215cc90ae1d9cc0e064529d5cfbb61"}, + {file = "oracledb-1.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31bfbbfec42e9c4f518c9fb4c0a70dda7154ae01539a697688107a75047297f"}, + {file = "oracledb-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29b0188a0ecf4951114c5bdd5a252725f9637fe2a7786e454036720d5f6aa9bf"}, + {file = "oracledb-1.4.2-cp39-cp39-win32.whl", hash = "sha256:035432c89f92ad1b36cd9ce8cd70ddcbe7a2b8e281d8951e77136a977c82a135"}, + {file = "oracledb-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:c839cf0f3f90b953243dc65debcfc66dfbd3c885ff9fd2e68d16de986d25e01c"}, + {file = "oracledb-1.4.2.tar.gz", hash = "sha256:e28ed9046f2735dc2dd5bbcdf3667f284e384e0ec7eed3eeb3798fa8a7d47e36"}, +] + +[package.dependencies] +cryptography = ">=3.2.1" + +[[package]] +name = "ott.utils" +version = "0.1.0" +description = "Open Transit Tools - OTT Utils" +optional = false +python-versions = "*" +groups = ["main"] +files = [] +develop = false + +[package.dependencies] +future = "*" +requests = "*" +simplejson = "*" +validators = "*" + +[package.source] +type = "git" +url = "https://github.com/OpenTransitTools/utils.git" +reference = "master" +resolved_reference = "bc55e87c43db1588e1c1699d3df90e68dfcdc3eb" + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, + {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, +] + +[package.extras] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + +[[package]] +name = "pycparser" +version = "2.23" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" +files = [ + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, +] + +[[package]] +name = "pydantic" +version = "2.12.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae"}, + {file = "pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.41.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, + {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pydantic-settings" +version = "2.11.0" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, + {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" + +[package.extras] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pyflakes" +version = "3.4.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99"}, + {file = "pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, + {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, +] + +[package.dependencies] +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pytokens" +version = "0.2.0" +description = "A Fast, spec compliant Python 3.13+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8"}, + {file = "pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "simplejson" +version = "3.20.2" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.5" +groups = ["main"] +files = [ + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:11847093fd36e3f5a4f595ff0506286c54885f8ad2d921dfb64a85bce67f72c4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d291911d23b1ab8eb3241204dd54e3ec60ddcd74dfcb576939d3df327205865"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:da6d16d7108d366bbbf1c1f3274662294859c03266e80dd899fc432598115ea4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9ddf9a07694c5bbb4856271cbc4247cc6cf48f224a7d128a280482a2f78bae3d"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3a0d2337e490e6ab42d65a082e69473717f5cc75c3c3fb530504d3681c4cb40c"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8ba88696351ed26a8648f8378a1431223f02438f8036f006d23b4f5b572778fa"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:00bcd408a4430af99d1f8b2b103bb2f5133bb688596a511fcfa7db865fbb845e"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4fc62feb76f590ccaff6f903f52a01c58ba6423171aa117b96508afda9c210f0"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7"}, + {file = "simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53"}, + {file = "simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413"}, + {file = "simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961"}, + {file = "simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544"}, + {file = "simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54"}, + {file = "simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769"}, + {file = "simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661"}, + {file = "simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608"}, + {file = "simplejson-3.20.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a135941a50795c934bdc9acc74e172b126e3694fe26de3c0c1bc0b33ea17e6ce"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ba488decb18738f5d6bd082018409689ed8e74bc6c4d33a0b81af6edf1c9f4"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81f8e982923d5e9841622ff6568be89756428f98a82c16e4158ac32b92a3787"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdad497ccb1edc5020bef209e9c3e062a923e8e6fca5b8a39f0fb34380c8a66c"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a3f1db97bcd9fb592928159af7a405b18df7e847cbcc5682a209c5b2ad5d6b1"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:215b65b0dc2c432ab79c430aa4f1e595f37b07a83c1e4c4928d7e22e6b49a748"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:ece4863171ba53f086a3bfd87f02ec3d6abc586f413babfc6cf4de4d84894620"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:4a76d7c47d959afe6c41c88005f3041f583a4b9a1783cf341887a3628a77baa0"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e9b0523582a57d9ea74f83ecefdffe18b2b0a907df1a9cef06955883341930d8"}, + {file = "simplejson-3.20.2-cp36-cp36m-win32.whl", hash = "sha256:16366591c8e08a4ac76b81d76a3fc97bf2bcc234c9c097b48d32ea6bfe2be2fe"}, + {file = "simplejson-3.20.2-cp36-cp36m-win_amd64.whl", hash = "sha256:732cf4c4ac1a258b4e9334e1e40a38303689f432497d3caeb491428b7547e782"}, + {file = "simplejson-3.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6c3a98e21e5f098e4f982ef302ebb1e681ff16a5d530cfce36296bea58fe2396"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cf9ca1363dc3711c72f4ec7c1caed2bbd9aaa29a8d9122e31106022dc175c6"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:106762f8aedf3fc3364649bfe8dc9a40bf5104f872a4d2d86bae001b1af30d30"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b21659898b7496322e99674739193f81052e588afa8b31b6a1c7733d8829b925"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fa1db6a02bca88829f2b2057c76a1d2dc2fccb8c5ff1199e352f213e9ec719"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:156139d94b660448ec8a4ea89f77ec476597f752c2ff66432d3656704c66b40e"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:b2620ac40be04dff08854baf6f4df10272f67079f61ed1b6274c0e840f2e2ae1"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:9ccef5b5d3e3ac5d9da0a0ca1d2de8cf2b0fb56b06aa0ab79325fa4bcc5a1d60"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f526304c2cc9fd8b8d18afacb75bc171650f83a7097b2c92ad6a431b5d7c1b72"}, + {file = "simplejson-3.20.2-cp37-cp37m-win32.whl", hash = "sha256:e0f661105398121dd48d9987a2a8f7825b8297b3b2a7fe5b0d247370396119d5"}, + {file = "simplejson-3.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dab98625b3d6821e77ea59c4d0e71059f8063825a0885b50ed410e5c8bd5cb66"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b8205f113082e7d8f667d6cd37d019a7ee5ef30b48463f9de48e1853726c6127"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fc8da64929ef0ff16448b602394a76fd9968a39afff0692e5ab53669df1f047f"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfe704864b5fead4f21c8d448a89ee101c9b0fc92a5f40b674111da9272b3a90"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ca7cbe7d2f423b97ed4e70989ef357f027a7e487606628c11b79667639dc84"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cec1868b237fe9fb2d466d6ce0c7b772e005aadeeda582d867f6f1ec9710cad"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:792debfba68d8dd61085ffb332d72b9f5b38269cda0c99f92c7a054382f55246"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e022b2c4c54cb4855e555f64aa3377e3e5ca912c372fa9e3edcc90ebbad93dce"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5de26f11d5aca575d3825dddc65f69fdcba18f6ca2b4db5cef16f41f969cef15"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e2162b2a43614727ec3df75baeda8881ab129824aa1b49410d4b6c64f55a45b4"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e11a1d6b2f7e72ca546bdb4e6374b237ebae9220e764051b867111df83acbd13"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:daf7cd18fe99eb427fa6ddb6b437cfde65125a96dc27b93a8969b6fe90a1dbea"}, + {file = "simplejson-3.20.2-cp38-cp38-win32.whl", hash = "sha256:da795ea5f440052f4f497b496010e2c4e05940d449ea7b5c417794ec1be55d01"}, + {file = "simplejson-3.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:6a4b5e7864f952fcce4244a70166797d7b8fd6069b4286d3e8403c14b88656b6"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bf76512ccb07d47944ebdca44c65b781612d38b9098566b4bb40f713fc4047"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:214e26acf2dfb9ff3314e65c4e168a6b125bced0e2d99a65ea7b0f169db1e562"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fb1259ca9c385b0395bad59cdbf79535a5a84fb1988f339a49bfbc57455a35a"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34e028a2ba8553a208ded1da5fa8501833875078c4c00a50dffc33622057881"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b538f9d9e503b0dd43af60496780cb50755e4d8e5b34e5647b887675c1ae9fee"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab998e416ded6c58f549a22b6a8847e75a9e1ef98eb9fbb2863e1f9e61a4105b"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a8f1c307edf5fbf0c6db3396c5d3471409c4a40c7a2a466fbc762f20d46601a"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a7bbac80bdb82a44303f5630baee140aee208e5a4618e8b9fde3fc400a42671"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5ef70ec8fe1569872e5a3e4720c1e1dcb823879a3c78bc02589eb88fab920b1f"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11c09c99253a74c36925d461c86ea25f0140f3b98ff678322734ddc0f038d7"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66f7c78c6ef776f8bd9afaad455e88b8197a51e95617bcc44b50dd974a7825ba"}, + {file = "simplejson-3.20.2-cp39-cp39-win32.whl", hash = "sha256:619ada86bfe3a5aa02b8222ca6bfc5aa3e1075c1fb5b3263d24ba579382df472"}, + {file = "simplejson-3.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:44a6235e09ca5cc41aa5870a952489c06aa4aee3361ae46daa947d8398e57502"}, + {file = "simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017"}, + {file = "simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "soupsieve" +version = "2.8" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, + {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.44" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "SQLAlchemy-2.0.44-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:471733aabb2e4848d609141a9e9d56a427c0a038f4abf65dd19d7a21fd563632"}, + {file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48bf7d383a35e668b984c805470518b635d48b95a3c57cb03f37eaa3551b5f9f"}, + {file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf4bb6b3d6228fcf3a71b50231199fb94d2dd2611b66d33be0578ea3e6c2726"}, + {file = "SQLAlchemy-2.0.44-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:e998cf7c29473bd077704cea3577d23123094311f59bdc4af551923b168332b1"}, + {file = "SQLAlchemy-2.0.44-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ebac3f0b5732014a126b43c2b7567f2f0e0afea7d9119a3378bde46d3dcad88e"}, + {file = "SQLAlchemy-2.0.44-cp37-cp37m-win32.whl", hash = "sha256:3255d821ee91bdf824795e936642bbf43a4c7cedf5d1aed8d24524e66843aa74"}, + {file = "SQLAlchemy-2.0.44-cp37-cp37m-win_amd64.whl", hash = "sha256:78e6c137ba35476adb5432103ae1534f2f5295605201d946a4198a0dea4b38e7"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c77f3080674fc529b1bd99489378c7f63fcb4ba7f8322b79732e0258f0ea3ce"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26ef74ba842d61635b0152763d057c8d48215d5be9bb8b7604116a059e9985"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a172b31785e2f00780eccab00bc240ccdbfdb8345f1e6063175b3ff12ad1b0"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9480c0740aabd8cb29c329b422fb65358049840b34aba0adf63162371d2a96e"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17835885016b9e4d0135720160db3095dc78c583e7b902b6be799fb21035e749"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cbe4f85f50c656d753890f39468fcd8190c5f08282caf19219f684225bfd5fd2"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-win32.whl", hash = "sha256:2fcc4901a86ed81dc76703f3b93ff881e08761c63263c46991081fd7f034b165"}, + {file = "sqlalchemy-2.0.44-cp310-cp310-win_amd64.whl", hash = "sha256:9919e77403a483ab81e3423151e8ffc9dd992c20d2603bf17e4a8161111e55f5"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3"}, + {file = "sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4"}, + {file = "sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73"}, + {file = "sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2fc44e5965ea46909a416fff0af48a219faefd5773ab79e5f8a5fcd5d62b2667"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dc8b3850d2a601ca2320d081874033684e246d28e1c5e89db0864077cfc8f5a9"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d733dec0614bb8f4bcb7c8af88172b974f685a31dc3a65cca0527e3120de5606"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22be14009339b8bc16d6b9dc8780bacaba3402aa7581658e246114abbd2236e3"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:357bade0e46064f88f2c3a99808233e67b0051cdddf82992379559322dfeb183"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4848395d932e93c1595e59a8672aa7400e8922c39bb9b0668ed99ac6fa867822"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-win32.whl", hash = "sha256:2f19644f27c76f07e10603580a47278abb2a70311136a7f8fd27dc2e096b9013"}, + {file = "sqlalchemy-2.0.44-cp38-cp38-win_amd64.whl", hash = "sha256:1df4763760d1de0dfc8192cc96d8aa293eb1a44f8f7a5fbe74caf1b551905c5e"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7027414f2b88992877573ab780c19ecb54d3a536bef3397933573d6b5068be4"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fe166c7d00912e8c10d3a9a0ce105569a31a3d0db1a6e82c4e0f4bf16d5eca9"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3caef1ff89b1caefc28f0368b3bde21a7e3e630c2eddac16abd9e47bd27cc36a"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc2856d24afa44295735e72f3c75d6ee7fdd4336d8d3a8f3d44de7aa6b766df2"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:11bac86b0deada30b6b5f93382712ff0e911fe8d31cb9bf46e6b149ae175eff0"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d18cd0e9a0f37c9f4088e50e3839fcb69a380a0ec957408e0b57cff08ee0a26"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-win32.whl", hash = "sha256:9e9018544ab07614d591a26c1bd4293ddf40752cc435caf69196740516af7100"}, + {file = "sqlalchemy-2.0.44-cp39-cp39-win_amd64.whl", hash = "sha256:8e0e4e66fd80f277a8c3de016a81a554e76ccf6b8d881ee0b53200305a8433f6"}, + {file = "sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05"}, + {file = "sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22"}, +] + +[package.dependencies] +greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] +aioodbc = ["aioodbc", "greenlet (>=1)"] +aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (>=1)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "starlette" +version = "0.48.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, + {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, +] + +[package.dependencies] +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} + +[package.extras] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.29.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, + {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "validators" +version = "0.35.0" +description = "Python Data Validation for Humans™" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd"}, + {file = "validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a"}, +] + +[package.extras] +crypto-eth-addresses = ["eth-hash[pycryptodome] (>=0.7.0)"] + +[[package]] +name = "waitress" +version = "3.0.2" +description = "Waitress WSGI server" +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"}, + {file = "waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"}, +] + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["coverage (>=7.6.0)", "pytest", "pytest-cov"] + +[[package]] +name = "webob" +version = "1.8.9" +description = "WSGI request and response object" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "WebOb-1.8.9-py2.py3-none-any.whl", hash = "sha256:45e34c58ed0c7e2ecd238ffd34432487ff13d9ad459ddfd77895e67abba7c1f9"}, + {file = "webob-1.8.9.tar.gz", hash = "sha256:ad6078e2edb6766d1334ec3dee072ac6a7f95b1e32ce10def8ff7f0f02d56589"}, +] + +[package.dependencies] +legacy-cgi = {version = ">=2.6", markers = "python_version >= \"3.13\""} + +[package.extras] +docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] +testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "webtest" +version = "3.0.7" +description = "Helper to test WSGI applications" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "webtest-3.0.7-py3-none-any.whl", hash = "sha256:2f51a0844f3a8beaef89bc23d225fe05ad816f7e429ffcc655a13013a799ac6c"}, + {file = "webtest-3.0.7.tar.gz", hash = "sha256:7aeab50f970d46c068e7a36dd162cb242591edf72a1d04efd21374772b931741"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +waitress = ">=3.0.2" +WebOb = ">=1.2" + +[package.extras] +docs = ["Sphinx (>=3.0.0)", "docutils", "pylons-sphinx-themes (>=1.0.8)"] +tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-cov"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.12" +content-hash = "e0b2e5302c54fcc05a211f51e1b9fa2a1f7b53d02b1a183405c6c86ff2ddf6d6" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..65edf52 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,62 @@ +[tool.poetry] +name = "pelias.adapter" +version = "0.1.0" +description = "Open Transit Tools - Web API / Controller" +authors = ["Open Transit Tools "] +readme = "README.md" +license = "Mozilla-derived (http://opentransittools.com)" +keywords = ["ott", "otp", "services", "transit"] +homepage = "http://opentransittools.com" + +classifiers = [ + "Programming Language :: Python", + "Framework :: FastAPI", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application" +] + +[[tool.poetry.packages]] +include = "com" +from = "." + +[tool.poetry.dependencies] +python = "^3.12" +fastapi = "^0.119.0" +uvicorn = "^0.29.0" +sqlalchemy = "^2.0.0" +oracledb = "^1.3.0" +python-dotenv = "^1.0.0" +ott-utils = { git = "https://github.com/OpenTransitTools/utils.git", branch = "master" } +setuptools = "^80.9.0" +pydantic-settings = "^2.11.0" +httpx = "^0.28.1" +gunicorn = "^23.0.0" + + +[tool.poetry.group.dev.dependencies] +black = "^25.9.0" +flake8 = "^7.3.0" +pytest = "^8.4.2" +coverage = "^7.11.0" +webtest = "^3.0.7" +autoflake = "^2.3.1" +autopep8 = "^2.3.2" +pytest-asyncio = "^1.2.0" +asgi-lifespan = "^2.1.0" +pytest-cov = "^7.0.0" + +[tool.black] +line-length = 88 +target-version = ['py312'] +include = '\.pyi?$' +exclude = ''' +/( + \.venv + | \.yml$ +)/ +''' + +[build-system] +requires = ["poetry-core>=1.7.0"] +build-backend = "poetry.core.masonry.api" + diff --git a/restart_wrapper.sh b/restart_wrapper.sh deleted file mode 100755 index 4396e0f..0000000 --- a/restart_wrapper.sh +++ /dev/null @@ -1,22 +0,0 @@ -# step 1: get into the wrapper directory -BASEDIR=$(dirname "$0") -cd $BASEDIR - -# step 2: force get latest and greatest code -git reset --hard HEAD -git pull - -# step 3: remove (potentially) outdated dependencies and buildout to get latest / greatest -RM_DEPS="rm -rf *egg*/ott.utils* nohup.out *~" -echo $RM_DEPS -eval $RM_DEPS -buildout - -# step 4: stop & restart wrapper -pkill -9 -f -c local_pelias -cmd="bin/pserve config/local_pelias.ini" -eval "${cmd}" &>/dev/null & disown; - -# step 5: really done hopefully... -echo -echo "DONE" diff --git a/setup.py b/setup.py deleted file mode 100644 index 6259692..0000000 --- a/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -from setuptools import setup, find_packages - -here = os.path.abspath(os.path.dirname(__file__)) -README = open(os.path.join(here, 'README.md')).read() -CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() - -requires = [ - 'ott.utils', - - 'requests', - 'venusian==1.1.0', - 'pyramid', - 'pyramid_tm', - 'pyramid_exclog', - 'waitress', -] - -# TODO: if py < 3.x add 'venusian==1.1.0', - -extras_require = dict( - dev=[ - '' if os.name == 'nt' or os.name == 'posix' else 'linesman' - ], -) - - -setup( - name='pelias.adapter', - version='0.1.0', - description='Open Transit Tools - Web API / Controller', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author="Open Transit Tools", - author_email="info@opentransittools.org", - - dependency_links = [ - 'git+https://github.com/OpenTransitTools/utils.git#egg=ott.utils-0.1.0', - ], - - license="Mozilla-derived (http://opentransittools.com)", - url='http://opentransittools.com', - keywords='ott, otp, services, transit', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=requires, - extras_require=extras_require, - tests_require=requires, - test_suite="pelias.adapter.tests", - entry_points="""\ - [paste.app_factory] - main = pelias.adapter.pyramid.app:main - - [console_scripts] - pr = pelias.adapter.tests.pr:main - """, -) diff --git a/pelias/adapter/tests/README.md b/tests/README.md similarity index 100% rename from pelias/adapter/tests/README.md rename to tests/README.md diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/comparison_testing/test_pelias.py b/tests/comparison_testing/test_pelias.py new file mode 100644 index 0000000..bf64e17 --- /dev/null +++ b/tests/comparison_testing/test_pelias.py @@ -0,0 +1,116 @@ +import logging +import os +import urllib.parse +from time import sleep + +import pytest +import requests +from starlette.testclient import TestClient + +from main import app +from tests.comparison_testing.util import assert_builtins + +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s" +) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +logger.propagate = True + +LOCAL_URL = "http://localhost:8000/pelias" +STAGE_URL = "https://ws-st.trimet.org/pelias" + +# List of full endpoint URLs to query (autocomplete and search for Portland) +queries = [ + "/v1/autocomplete?text=1600%20penn", + "/v1/autocomplete?text=main%20st&focus.point.lat=45.5152&focus.point.lon=-122.6784", + "/v1/autocomplete?text=broadway&boundary.circle.lat=45.5152&boundary.circle.lon=-122.6784&boundary.circle.radius=50", + "/v1/autocomplete?text=market&boundary.rect.min_lat=45.2&boundary.rect.max_lat=45.7&boundary.rect.min_lon=-123.1&boundary.rect.max_lon=-122.3", + "/v1/autocomplete?text=salmon%20st&boundary.country=US", + "/v1/autocomplete?text=oswego&layers=locality", + "/v1/autocomplete?text=taylor&layers=venue,address,street", + "/v1/autocomplete?text=powell&sources=wof", + "/v1/autocomplete?text=central%20library&layers=venue&sources=openstreetmap", + "/v1/autocomplete?text=king&focus.point.lat=45.5152&focus.point.lon=-122.6784&boundary.country=US&layers=street", + "/v1/search?text=1600%20pennsylvania%20ave%20nw", + "/v1/search?text=soup%20st&focus.point.lat=45.5152&focus.point.lon=-122.6784", + "/v1/search?text=portland&boundary.country=US", + "/v1/search?text=station&boundary.circle.lat=45.5152&boundary.circle.lon=-122.6784&boundary.circle.radius=50", + "/v1/search?text=overton%20st&boundary.rect.min_lat=45.2&boundary.rect.max_lat=45.7&boundary.rect.min_lon=-123.1&boundary.rect.max_lon=-122.3", + "/v1/search?text=beaverton&layers=locality", + "/v1/search?text=oregon%20zoo&layers=venue,address&sources=openstreetmap,wof", + "/v1/search?text=hawthorne&focus.point.lat=45.5152&focus.point.lon=-122.6784&boundary.country=US", + "/v1/search?text=97209", + "/v1/search?text=1120%20SW%205th%20Ave&boundary.country=US&layers=address&sources=openstreetmap", +] + + +def get_query_text(q): + parsed = urllib.parse.urlparse(q) + params = urllib.parse.parse_qs(parsed.query) + text_value = params.get("text", [None])[0] + return text_value + + +QUERY_MAP = {get_query_text(q): q for q in queries} + + +@pytest.mark.parametrize( + "query_id, query", list(QUERY_MAP.items()), ids=list(QUERY_MAP.keys()) +) +@pytest.mark.skipif( + os.getenv("RUN_INTEGRATION") != "1", + reason="This test calls external staging services and is skipped unless RUN_INTEGRATION=1", +) +def test_compare_with_stage(query_id, query): + print(f"Running {query_id} query against {LOCAL_URL} and {STAGE_URL}") + # Run through all queries and save results + stage_url = f"{STAGE_URL}{query}" + local_url = f"/pelias{query}" + + stage_response = requests.get(stage_url, timeout=10) + stage_response.raise_for_status() + stage_data = stage_response.json() + + local_data = None + + with TestClient(app) as client: + local_response = client.get(local_url) + local_data = local_response.json() + client.close() + + sleep(0.2) # Slight delay to be kind to the server + + loca_attr, stage_attr = local_data["geocoding"].get("attribution"), stage_data[ + "geocoding" + ].get("attribution") + + assert loca_attr == stage_attr, f"attribution mismatch: {loca_attr} != {stage_attr}" + + for local_feature in local_data["features"]: + local_props = local_feature.get("properties", {}) + local_id = local_props.get("id") + stage_props = next( + ( + f.get("properties", {}) + for f in stage_data["features"] + if f.get("properties", {}).get("id") == local_id + ), + None, + ) + if stage_props: + # todo label varies here. Not sure why. attribution is the same + l1, l2 = ( + local_props.get("label", "").split("(")[0], + stage_props.get("label", "").split("(")[0], + ) + assert l1 in l2, f"label mismatch for {local_id}: {l1} not in {l2}" + + assert_builtins( + local_data=local_props, stage_data=stage_props, skip=["label"] + ) + + else: + print(f"no stage props for {local_id}") + + print("✅ All done.") diff --git a/tests/comparison_testing/test_solr.py b/tests/comparison_testing/test_solr.py new file mode 100644 index 0000000..f9db424 --- /dev/null +++ b/tests/comparison_testing/test_solr.py @@ -0,0 +1,124 @@ +import logging +import urllib.parse +from time import sleep + +import pytest +import requests +from starlette.testclient import TestClient + +from main import app +from tests.comparison_testing.util import assert_builtins + +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s" +) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +logger.propagate = True + +STAGE_URL = "https://ws-st.trimet.org/solrwrap" + +# List of full endpoint URLs to query (autocomplete and search for Portland) +queries = [ + "/v1/autocomplete?q=1600&rows=11", + "/v1/autocomplete?q=main&rows=11", + "/v1/autocomplete?q=broadway&rows=11", + "/v1/autocomplete?q=market&rows=11", + "/v1/autocomplete?q=salmon&rows=11", + "/v1/autocomplete?q=oswego&rows=11", + "/v1/autocomplete?q=taylor&rows=11", + "/v1/autocomplete?q=powell&rows=11", + "/v1/autocomplete?q=central&rows=11", + "/v1/autocomplete?q=king&rows=11", + "/v1/search?q=1600&rows=11", + "/v1/search?q=soup&rows=11", + "/v1/search?q=portland&rows=11", + "/v1/search?q=station&rows=11", + "/v1/search?q=overton&rows=11", + "/v1/search?q=beaverton&rows=11", + "/v1/search?q=oregon&rows=11", + "/v1/search?q=hawthorne&rows=11", + "/v1/search?q=97209&rows=11", + "/v1/search?q=1120&rows=11", + "/v1/search?q=1600&rows=11", + "/v1?q=soup&rows=11", + "/v1?q=portland&rows=11", + "/v1?q=station&rows=11", + "/v1?q=overton&rows=11", + "/v1?q=beaverton&rows=11", + "/v1?q=oregon&rows=11", + "/v1?q=hawthorne&rows=11", + "/v1?q=97209&rows=11", + "/v1?q=1120&rows=11", + "/v1/select?q=1600&rows=11", + "/v1/select?q=soup&rows=11", + "/v1/select?q=portland&rows=11", + "/v1/select?q=station&rows=11", + "/v1/select?q=overton&rows=11", + "/v1/select?q=beaverton&rows=11", + "/v1/select?q=oregon&rows=11", + "/v1/select?q=hawthorne&rows=11", + "/v1/select?q=97209&rows=11", + "/v1/select?q=1120&rows=11", +] + + +def get_query_text(q): + parsed = urllib.parse.urlparse(q) + params = urllib.parse.parse_qs(parsed.query) + text_value = params.get("q", [None])[0] + return text_value + + +QUERY_MAP = {get_query_text(q): q for q in queries} + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "query_id, query", list(QUERY_MAP.items()), ids=list(QUERY_MAP.keys()) +) +@pytest.mark.skip( + "Run these to compare with stage. Ensure the results attributes are the same" +) +async def test_compare_with_stage(query_id, query): + print(f"Running {query_id} query against local solr/{query} and {STAGE_URL}") + stage_url = f"{STAGE_URL}{query}" + local_url = f"/solr{query}" # Needs to be a full path for ASGI client + + stage_response = requests.get(stage_url, timeout=10) + stage_response.raise_for_status() + stage_data = stage_response.json() + + with TestClient(app) as client: + local_response = client.get(local_url) + local_data = local_response.json() + client.close() + + assert_builtins(local_data=local_data, stage_data=stage_data) + + local_response_data = local_data.get("response", {}) + stage_response_data = stage_data.get("response", {}) + local_docs = local_response_data.get("docs", []) + stage_docs = stage_response_data.get("docs", []) + + docs_map = {} + for doc in local_docs: + doc_id = doc.get("id") + if doc_id: + stage_doc = next( + (item for item in stage_docs if item.get("id") == doc_id), None + ) + docs_map[doc_id] = {"local": doc, "stage": stage_doc} + + for doc_id, doc_pair in docs_map.items(): + local_doc = doc_pair["local"] + stage_doc = doc_pair["stage"] + if stage_doc: + assert_builtins( + local_data=local_doc, stage_data=stage_doc, skip=["timestamp"] + ) + else: + print(f"Warning: Document with id {doc_id} not found in stage response.") + + sleep(0.2) + print("✅ All done.") diff --git a/tests/comparison_testing/util.py b/tests/comparison_testing/util.py new file mode 100644 index 0000000..ba14067 --- /dev/null +++ b/tests/comparison_testing/util.py @@ -0,0 +1,14 @@ +def assert_builtins(local_data, stage_data, skip: list[str] = []): + if skip: + for s in skip: + if s in local_data: + del local_data[s] + if s in stage_data: + del stage_data[s] + + for k, v in local_data.items(): + if k in stage_data and isinstance(v, str | int | float | bool): + print(f"Comparing key '{k}': local={v}, stage={stage_data[k]}") + assert ( + v == stage_data[k] + ), f"Key {k} differs: local={v}, stage={stage_data[k]}" diff --git a/pelias/adapter/tests/data/alias.csv b/tests/data/alias.csv similarity index 100% rename from pelias/adapter/tests/data/alias.csv rename to tests/data/alias.csv diff --git a/pelias/adapter/tests/data/autocomplete-hop-fastpass.json b/tests/data/autocomplete-hop-fastpass.json similarity index 100% rename from pelias/adapter/tests/data/autocomplete-hop-fastpass.json rename to tests/data/autocomplete-hop-fastpass.json diff --git a/tests/data/autocomplete_no_results.json b/tests/data/autocomplete_no_results.json new file mode 100644 index 0000000..8055401 --- /dev/null +++ b/tests/data/autocomplete_no_results.json @@ -0,0 +1,32 @@ +{ + "geocoding": { + "version": "0.2", + "attribution": "http://pelias.mapzen.com/v1/attribution", + "query": { + "text": "7-11 ticket", + "parser": "addressit", + "tokens": [ + "7-11", + "ticket" + ], + "size": 10, + "private": false, + "focus.point.lat": 45.52, + "focus.point.lon": -122.67, + "lang": { + "name": "English", + "iso6391": "en", + "iso6393": "eng", + "defaulted": false + } + }, + "engine": { + "name": "Pelias", + "author": "Mapzen", + "version": "1.0" + }, + "timestamp": 1518989052715 + }, + "type": "FeatureCollection", + "features": [] +} \ No newline at end of file diff --git a/pelias/adapter/tests/data/landmarks.csv b/tests/data/landmarks.csv similarity index 100% rename from pelias/adapter/tests/data/landmarks.csv rename to tests/data/landmarks.csv diff --git a/pelias/adapter/tests/data/search13135.json b/tests/data/search13135.json similarity index 100% rename from pelias/adapter/tests/data/search13135.json rename to tests/data/search13135.json diff --git a/tests/data/search_7-11_ticket.json b/tests/data/search_7-11_ticket.json new file mode 100644 index 0000000..33ec781 --- /dev/null +++ b/tests/data/search_7-11_ticket.json @@ -0,0 +1,186 @@ +{ + "geocoding": { + "version": "0.2", + "attribution": "http://pelias.mapzen.com/v1/attribution", + "query": { + "text": "7-11 ticket", + "size": 10, + "private": false, + "focus.point.lat": 45.52, + "focus.point.lon": -122.67, + "lang": { + "name": "English", + "iso6391": "en", + "iso6393": "eng", + "defaulted": false + }, + "querySize": 20, + "parser": "addressit" + }, + "engine": { + "name": "Pelias", + "author": "Mapzen", + "version": "1.0" + }, + "timestamp": 1518988873092 + }, + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.601504, + 45.555694 + ] + }, + "properties": { + "id": "node:2694265256", + "gid": "openstreetmap:venue:node:2694265256", + "layer": "venue", + "source": "openstreetmap", + "source_id": "node:2694265256", + "name": "7-11", + "confidence": 0.879, + "distance": 6.66, + "accuracy": "point", + "country": "United States", + "country_gid": "whosonfirst:country:85633793", + "country_a": "USA", + "region": "Oregon", + "region_gid": "whosonfirst:region:85688513", + "region_a": "OR", + "county": "Multnomah County", + "county_gid": "whosonfirst:county:102081631", + "locality": "Portland", + "locality_gid": "whosonfirst:locality:101715829", + "neighbourhood": "Cully", + "neighbourhood_gid": "whosonfirst:neighbourhood:85893177", + "label": "7-11, Portland, OR, USA" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.715904, + 45.510118 + ] + }, + "properties": { + "id": "way:119243635", + "gid": "openstreetmap:venue:way:119243635", + "layer": "venue", + "source": "openstreetmap", + "source_id": "way:119243635", + "name": "TIcket Booths", + "confidence": 0.668, + "distance": 3.751, + "accuracy": "point", + "country": "United States", + "country_gid": "whosonfirst:country:85633793", + "country_a": "USA", + "region": "Oregon", + "region_gid": "whosonfirst:region:85688513", + "region_a": "OR", + "county": "Multnomah County", + "county_gid": "whosonfirst:county:102081631", + "locality": "Portland", + "locality_gid": "whosonfirst:locality:101715829", + "neighbourhood": "Sylvan-Highlands", + "neighbourhood_gid": "whosonfirst:neighbourhood:85893251", + "label": "TIcket Booths, Portland, OR, USA" + }, + "bbox": [ + -122.715949, + 45.510062, + -122.715836, + 45.510197 + ] + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.679567, + 45.518951 + ] + }, + "properties": { + "id": "1640::TRIMET::fare_outlet", + "gid": "transit:fare_outlet:1640::TRIMET::fare_outlet", + "layer": "fare_outlet", + "source": "transit", + "source_id": "1640::trimet::fare_outlet", + "name": "TriMet Ticket Office", + "region": "Oregon", + "locality": "Portland", + "neighbourhood_gid": "whosonfirst:neighbourhood:85867131", + "housenumber": "701", + "distance": 0.757, + "accuracy": "centroid", + "region_gid": "whosonfirst:region:85688513", + "county_gid": "whosonfirst:county:102081631", + "continent": "North America", + "country": "United States", + "country_a": "USA", + "locality_gid": "whosonfirst:locality:101715829", + "postalcode": "97204", + "region_a": "OR", + "street": "SW 6th Ave -- Pioneer Courthouse Square", + "confidence": 0.668, + "country_gid": "whosonfirst:country:85633793", + "continent_gid": "whosonfirst:continent:102191575", + "label": "TriMet Ticket Office, Portland, OR, USA", + "county": "Multnomah County", + "neighbourhood": "Downtown" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -122.679567, + 45.518948 + ] + }, + "properties": { + "id": "1640::TRIMET::fare", + "gid": "transit:fare:1640::TRIMET::fare", + "layer": "fare", + "source": "transit", + "source_id": "1640::trimet::fare", + "name": "TriMet Ticket Office (HOP Fastpass)", + "street": "701 SW 6th Ave", + "confidence": 0.645, + "distance": 0.757, + "accuracy": "centroid", + "country": "United States", + "country_gid": "whosonfirst:country:85633793", + "country_a": "USA", + "region": "Oregon", + "region_gid": "whosonfirst:region:85688513", + "region_a": "OR", + "county": "Multnomah County", + "county_gid": "whosonfirst:county:102081631", + "locality": "Portland", + "locality_gid": "whosonfirst:locality:101715829", + "neighbourhood": "Downtown", + "neighbourhood_gid": "whosonfirst:neighbourhood:85867131", + "continent": "North America", + "continent_gid": "whosonfirst:continent:102191575", + "label": "TriMet Ticket Office (HOP Fastpass), Portland, OR, USA" + } + } + ], + "bbox": [ + -122.715949, + 45.510062, + -122.601504, + 45.555694 + ] +} \ No newline at end of file diff --git a/pelias/adapter/tests/data/stops.csv b/tests/data/stops.csv similarity index 100% rename from pelias/adapter/tests/data/stops.csv rename to tests/data/stops.csv diff --git a/pelias/adapter/tests/pr.py b/tests/pr.py similarity index 57% rename from pelias/adapter/tests/pr.py rename to tests/pr.py index ca59c5a..357bf3d 100644 --- a/pelias/adapter/tests/pr.py +++ b/tests/pr.py @@ -1,48 +1,58 @@ -import requests +from logging import getLogger from urllib.parse import quote +import requests + +logger = getLogger(__name__) + def autocomplete(search_term, def_val=None): ret_val = def_val ret_index = -1 try: - url = "https://ws-st.trimet.org/peliaswrap/v1/autocomplete?text={}".format(quote(search_term)) + url = "https://ws-st.trimet.org/peliaswrap/v1/autocomplete?text={}".format( + quote(search_term) + ) response = requests.get(url) if response.status_code == 200: data = response.json() - features = data.get('features') + features = data.get("features") if features and len(features) > 0: for i, f in enumerate(features): - n = f.get('properties').get('name') + n = f.get("properties").get("name") if search_term == n: ret_val = f - ret_index = i+1 + ret_index = i + 1 break else: - #print("nope") + # print("nope") pass - except: - pass + except Exception as e: + logger.info(e) return ret_val, ret_index - + def main(): url = "https://ws-st.trimet.org/rtp/routers/default/park_and_ride?maxTransitDistance=100000" response = requests.get(url) if response.status_code == 200: data = response.json() - data = sorted(data, key=lambda d: d['name']) - #print(data, len(data)) + data = sorted(data, key=lambda d: d["name"]) + # print(data, len(data)) for d in data: - n = d.get('name') - r,i = autocomplete(n) + n = d.get("name") + r, i = autocomplete(n) if r: if i >= 4: print("* ", end="") - print("{} was found at result #{} ({})".format(n, i, r.get('properties').get('label'))) + print( + "{} was found at result #{} ({})".format( + n, i, r.get("properties").get("label") + ) + ) else: print("! {} was NOT found in Pelias".format(d)) - #break + # break else: - print("Can't pull {}".format(url)) \ No newline at end of file + print("Can't pull {}".format(url)) diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..04428b6 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,32 @@ +from fastapi.testclient import TestClient + +from main import app + +client = TestClient(app) + + +def test_read_root(): + """Test root endpoint""" + response = client.get("/core/v1/") + assert response.status_code == 200 + assert "message" in response.json() + + +def test_read_info(): + """Test info endpoint""" + response = client.get("/core/v1/info") + assert response.status_code == 200 + data = response.json() + assert "app_name" in data + assert "version" in data + + +def test_health_check(): + """Test health check endpoint""" + response = client.get("/core/v1/health") + assert response.status_code == 200 + data = response.json() + assert "status" in data + assert data["status"] == "ok" + assert "message" in data + assert data["message"] == "Service is healthy" diff --git a/pelias/adapter/tests/test_stops.py b/tests/test_stops.py similarity index 56% rename from pelias/adapter/tests/test_stops.py rename to tests/test_stops.py index 9c21b15..d9053a3 100644 --- a/pelias/adapter/tests/test_stops.py +++ b/tests/test_stops.py @@ -1,13 +1,12 @@ -from cProfile import run -from ott.utils.tests.base_unit import BaseUnit -from ott.utils import file_utils import requests +from ott.utils import file_utils +from ott.utils.tests.base_unit import BaseUnit class TestStops(BaseUnit): def setUp(self): self.base_path = file_utils.get_file_dir(__file__) - self.csv = self.csv('./data/stops.csv', "stop_code") + self.csv = self.csv("data/stops.csv", "stop_code") def tearDown(self): pass @@ -15,28 +14,29 @@ def tearDown(self): def runz(self, url_tmpl, within=3): ret_val = True for c in self.csv: - id = c.get("stop_code") - url = url_tmpl + id + stop_id = c.get("stop_code") + url = url_tmpl + stop_id res = requests.get(url).json() - features = res.get('features') + features = res.get("features") seen = False index = 0 - feat = None - for i, f in enumerate(features): - fid = f.get('properties').get('id') - if fid and fid.startswith(id + '::'): + # feat = None + expected_id = f"stops:TRIMET:{stop_id}" + for i, feature in enumerate(features): + fid = feature.get("properties").get("id") + if fid and fid == expected_id: seen = True - index = i+1 - feat = f.get('properties').get('id') + index = i + 1 + # feat = feature.get("properties").get("id") break if not seen: - print("ERROR: {} not seen".format(id)) + print("ERROR: {} not seen".format(stop_id)) ret_val = False elif index > within: - print("WARN: {} seen in record {}".format(id, index)) - #else: - #print("INFO: {} seen in record {}".format(id, index, feat)) + print("WARN: {} seen in record {}".format(stop_id, index)) + # else: + # print("INFO: {} seen in record {}".format(id, index, feat)) return ret_val def test_autocomplete(self): diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000..572a780 --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,98 @@ +import pytest +from ott.utils import file_utils +from ott.utils import json_utils + +from com.github.ott.pelias.adapter.models.solr.solr_stop_record import SolrStopRecord +from com.github.ott.pelias.adapter.service.pelias_to_solr import PeliasToSolr + +PORT = "45554" + + +@pytest.fixture(scope="module") +def base_path(): + return file_utils.get_file_dir(__file__) + + +@pytest.fixture +def urls(): + return { + "auto": "https://ws-st.trimet.org/pelias/v1/autocomplete", + "search": "https://ws-st.trimet.org/pelias/v1/search", + } + + +def parse(file_name, base_path): + file_path = file_utils.path_join(base_path, file_name) + json_data = json_utils.file_to_json(file_path) + return PeliasToSolr.parse_json(json_data) + + +def test_pelias_to_solr(base_path): + p = parse("data/search13135.json", base_path) + assert len(p.response.docs) > 0 + + +@pytest.mark.skip( + reason="this is looking for properties.addendum.gtfs which is not in the passed json at all" +) +def test_stops(base_path): + p = parse("data/search13135.json", base_path) + num_stops = 0 + for d in p.response.docs: + if isinstance(d, SolrStopRecord): + num_stops += 1 + assert len(d.stop_id) > 0 + assert len(d.agency_id) > 0 + assert num_stops > 0 + + +def test_unique_coords(base_path): + p = parse("data/autocomplete-hop-fastpass.json", base_path) + coords = set() + for d in p.response.docs: + assert d.lat != 0.0 + assert d.lon != 0.0 + assert d.lon not in coords + assert d.lat not in coords + coords.add(d.lat) + coords.add(d.lon) + + +def test_names_labels(base_path): + p = parse("data/autocomplete-hop-fastpass.json", base_path) + for d in p.response.docs: + assert "HOP Fastpass" in d.name + + +def test_solr_to_pelias_params(): + solr_params = {"q": "val", "rows": "6"} + pelias_params_str = PeliasToSolr.solr_to_pelias_param_str(solr_params) + assert "text=val" in pelias_params_str + assert "size=6" in pelias_params_str + + +def test_call_live_pelias_server(): + """ + https://ws-st.trimet.org/pelias/v1/autocomplete?text=888%20SE%20Lambert%20St + """ + solr_params = {"q": "val", "rows": "6"} + pelias_params_str = PeliasToSolr.solr_to_pelias_param_str(solr_params) + assert "text=val" in pelias_params_str + assert "size=6" in pelias_params_str + + +def test_switch_autocomplete_to_search_service(urls): + """ + send an interpolated address to the query, and expect that: + https://ws-st.trimet.org/pelias/v1/autocomplete?text=888%20SE%20Lambert%20St + https://ws-st.trimet.org/pelias/v1/search?text=888%20SE%20Lambert%20St + """ + solr_params = {"q": "888 SE Lambert St"} + res = PeliasToSolr.call_pelias(solr_params, urls["auto"], urls["search"]) + assert res.num_records() > 0 + assert "888 SE Lambert" in res.response.docs[0].name + + +def test_no_results(base_path): + p = parse("data/autocomplete_no_results.json", base_path) + assert len(p.response.docs) == 0 diff --git a/pelias/adapter/tests/tests_strings_crash.py b/tests/tests_strings_crash.py similarity index 74% rename from pelias/adapter/tests/tests_strings_crash.py rename to tests/tests_strings_crash.py index 3133b58..d538b6e 100644 --- a/pelias/adapter/tests/tests_strings_crash.py +++ b/tests/tests_strings_crash.py @@ -1,9 +1,6 @@ from ott.utils.tests.base_unit import BaseUnit -from pelias.adapter.control.pelias_to_solr import PeliasToSolr -from pelias.adapter.model.solr.solr_stop_record import SolrStopRecord - -PORT="45554" +PORT = "45554" class TestStringsCrash(BaseUnit): @@ -15,7 +12,7 @@ def tearDown(self): pass def test_landmarks(self): - csv = self.csv('./data/landmarks.csv', "name") + csv = self.csv("data/landmarks.csv", "name") for c in csv: # import pdb; pdb.set_trace() url = self.url_tmpl + c.get("name") @@ -23,14 +20,14 @@ def test_landmarks(self): self.assertTrue(res) def test_alias(self): - csv = self.csv('./data/alias.csv', "NAME") + csv = self.csv("data/alias.csv", "NAME") for c in csv: url = self.url_tmpl + c.get("NAME") res = self.call_test_json(url, find_attribute="features") self.assertTrue(res) def test_stops(self): - csv = self.csv('./data/stops.csv', "stop_code") + csv = self.csv("data/stops.csv", "stop_code") for c in csv: url = self.url_tmpl + c.get("stop_code") res = self.call_test_json(url, find_attribute="features")