From 0dbf2bcf5451c2b7bc33a445433eb8dc16676448 Mon Sep 17 00:00:00 2001 From: Eric Tendian Date: Thu, 2 Jan 2025 00:35:57 -0600 Subject: [PATCH 1/2] Switch database logic to use SQLModel, fix type issues --- .vscode/settings.json | 2 +- app/api.py | 80 ++-- app/bin/autoscale-vast.py | 2 +- app/bin/import-to-db.py | 4 +- .../migrate-from-meilisearch-to-typesense.py | 6 +- app/bin/reindex.py | 21 +- app/geocoding/geocoding.py | 22 +- app/geocoding/llm.py | 15 +- app/geocoding/types.py | 21 + app/models/base.py | 5 - app/models/call.py | 89 ----- app/models/database.py | 4 +- app/models/metadata.py | 4 +- app/models/models.py | 83 ++++ app/models/transcript.py | 4 +- app/notifications/config.py | 4 +- app/search/adapters.py | 32 +- app/utils/api_client.py | 5 +- app/utils/storage.py | 4 +- app/whisper/config.py | 4 +- app/whisper/deepgram.py | 2 +- app/whisper/faster_whisper.py | 4 +- app/whisper/openai.py | 2 +- app/whisper/transcribe.py | 2 +- app/whisper/whisper_s2t.py | 2 +- app/worker.py | 22 +- poetry.lock | 374 ++++++++++++------ pyproject.toml | 6 +- tests/geocoding/test_geocoding.py | 8 +- tests/notification/test_notification.py | 8 +- tests/radio/test_analog.py | 2 +- tests/search/test_adapters.py | 5 +- tests/test_e2e.py | 2 - tests/utils/test_conversion.py | 2 +- tests/whisper/test_whisper.py | 2 +- 35 files changed, 492 insertions(+), 362 deletions(-) create mode 100644 app/geocoding/types.py delete mode 100644 app/models/base.py delete mode 100644 app/models/call.py create mode 100644 app/models/models.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 1b59914..ec380fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "python.analysis.typeCheckingMode": "basic", + "python.analysis.typeCheckingMode": "off", "python.analysis.autoImportCompletions": true } diff --git a/app/api.py b/app/api.py index c94e267..409efbd 100644 --- a/app/api.py +++ b/app/api.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from typing import Annotated +from typing import Annotated, Generator import json import logging import os @@ -14,18 +14,18 @@ from fastapi.exception_handlers import request_validation_exception_handler from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse -from sqlalchemy.orm import Session +from sqlmodel import Session, select, func import sentry_sdk load_dotenv() from app.search.helpers import get_default_index_name from app.utils.exceptions import before_send -from app.models.database import SessionLocal, engine +from app.models.database import engine from app.models.metadata import Metadata from app.utils import storage from app import worker -from app.models import call as call_model, base as base_model +from app.models import models sentry_dsn = os.getenv("SENTRY_DSN") if sentry_dsn: @@ -44,9 +44,6 @@ before_send=before_send, ) -if os.getenv("POSTGRES_DB") is not None: - base_model.Base.metadata.create_all(bind=engine) - app = FastAPI() logger = logging.getLogger() @@ -59,17 +56,13 @@ logger.addHandler(stream_handler) -# Dependency -def get_db(): # type: ignore - db = SessionLocal() - try: - yield db - finally: - db.close() +def get_db() -> Generator[Session, None, None]: + with Session(engine) as session: + yield session @app.middleware("http") -async def authenticate(request: Request, call_next) -> Response: # type: ignore +async def authenticate(request: Request, call_next) -> Response: api_key = os.getenv("API_KEY", "") if ( @@ -190,23 +183,25 @@ def create_call_from_sdrtrunk( finally: os.unlink(raw_audio.name) - call = call_model.CallCreateSchema( - raw_metadata=dict(metadata), raw_audio_url=audio_url - ) + call = models.CallCreate(raw_metadata=metadata, raw_audio_url=audio_url) - db_call = call_model.create_call(db=db, call=call) + db_call = models.create_call(db=db, call=call) if "digital" in metadata["audio_type"]: from app.radio.digital import build_transcribe_options elif metadata["audio_type"] == "analog": from app.radio.analog import build_transcribe_options + else: + raise HTTPException( + status_code=400, detail=f"Audio type {metadata['audio_type']} not supported" + ) worker.queue_task( audio_url, metadata, build_transcribe_options(metadata), whisper_implementation=None, - id=db_call.id, # type: ignore + id=db_call.id, ) return Response("Call imported successfully.", status_code=200) @@ -251,12 +246,12 @@ def queue_for_transcription( audio_url, metadata, build_transcribe_options(metadata), whisper_implementation ) - return JSONResponse({"task_id": task.id}, status_code=201) # type: ignore + return JSONResponse({"task_id": task.id}, status_code=201) @app.get("/tasks/{task_id}") def get_status(task_id: str) -> JSONResponse: - task_result = AsyncResult(task_id, app=worker.celery) # type: ignore + task_result: AsyncResult = AsyncResult(task_id, app=worker.celery) result = { "task_id": task_id, "task_status": task_result.status, @@ -269,17 +264,22 @@ def get_status(task_id: str) -> JSONResponse: return JSONResponse(result) -@app.get("/calls/", response_model=list[call_model.CallSchema]) +@app.get("/calls/", response_model=models.CallsPublic) def read_calls( skip: int = 0, limit: int = 100, db: Session = Depends(get_db) -) -> list[call_model.Call]: - calls = call_model.get_calls(db, skip=skip, limit=limit) - return calls +) -> models.CallsPublic: + count_statement = select(func.count()).select_from(models.Call) + count = db.exec(count_statement).one() + + statement = select(models.Call).offset(skip).limit(limit) + calls = db.exec(statement).all() + return models.CallsPublic(data=calls, count=count) -@app.get("/calls/{call_id}", response_model=call_model.CallSchema) -def read_call(call_id: int, db: Session = Depends(get_db)) -> call_model.Call: - db_call = call_model.get_call(db, call_id=call_id) + +@app.get("/calls/{call_id}", response_model=models.CallPublic) +def read_call(call_id: int, db: Session = Depends(get_db)) -> models.Call: + db_call = db.get(models.Call, call_id) if db_call is None: raise HTTPException(status_code=404, detail="Call not found") return db_call @@ -328,40 +328,38 @@ def create_call( else: raise HTTPException(status_code=400, detail="No audio provided") - call = call_model.CallCreateSchema(raw_metadata=metadata, raw_audio_url=audio_url) + call = models.CallCreate(raw_metadata=metadata, raw_audio_url=audio_url) - db_call = call_model.create_call(db=db, call=call) + db_call = models.create_call(db=db, call=call) task = worker.queue_task( audio_url, metadata, build_transcribe_options(metadata), whisper_implementation, - db_call.id, # type: ignore + db_call.id, ) return JSONResponse( - { - "task_id": task.id # type: ignore - }, + {"task_id": task.id}, status_code=201, ) -@app.patch("/calls/{call_id}", response_model=call_model.CallSchema) +@app.patch("/calls/{call_id}", response_model=models.CallPublic) def update_call( - call_id: int, call: call_model.CallUpdateSchema, db: Session = Depends(get_db) -) -> call_model.Call: - db_call = call_model.get_call(db, call_id=call_id) + call_id: int, call: models.CallUpdate, db: Session = Depends(get_db) +) -> models.Call: + db_call = db.get(models.Call, call_id) if db_call is None: raise HTTPException(status_code=404, detail="Call not found") - return call_model.update_call(db=db, call=call, db_call=db_call) + return models.update_call(db=db, call=call, db_call=db_call) @app.get("/talkgroups") def talkgroups(db: Session = Depends(get_db)) -> JSONResponse: - tgs = call_model.get_talkgroups(db, get_default_index_name()) + tgs = models.get_talkgroups(db, get_default_index_name()) return JSONResponse({"talkgroups": tgs}) diff --git a/app/bin/autoscale-vast.py b/app/bin/autoscale-vast.py index 4c8945c..da16fe1 100755 --- a/app/bin/autoscale-vast.py +++ b/app/bin/autoscale-vast.py @@ -166,7 +166,7 @@ def _update_pending_instances(self, instances: list[dict]): def get_worker_status(self) -> list[dict]: workers = [] - result = worker.celery.control.inspect(timeout=10).stats() # type: ignore + result = worker.celery.control.inspect(timeout=10).stats() if result: for name, stats in result.items(): workers.append({"name": name, "stats": stats}) diff --git a/app/bin/import-to-db.py b/app/bin/import-to-db.py index 597dbf7..339a7f1 100755 --- a/app/bin/import-to-db.py +++ b/app/bin/import-to-db.py @@ -36,8 +36,8 @@ def get_documents( MeiliDocument(hit) for hit in results["hits"] ] else: - results = index.get_documents(pagination) # type: ignore - return results.total, results.results # type: ignore + results = index.get_documents(pagination) + return results.total, results.results if __name__ == "__main__": diff --git a/app/bin/migrate-from-meilisearch-to-typesense.py b/app/bin/migrate-from-meilisearch-to-typesense.py index 2b60adb..f014afb 100755 --- a/app/bin/migrate-from-meilisearch-to-typesense.py +++ b/app/bin/migrate-from-meilisearch-to-typesense.py @@ -14,7 +14,7 @@ # Load the .env file of our choice if specified before the regular .env can load load_dotenv(os.getenv("ENV")) -from app.geocoding.geocoding import GeoResponse +from app.geocoding.types import GeoResponse from app.models.metadata import Metadata from app.models.transcript import Transcript from app.search import helpers, adapters @@ -26,7 +26,7 @@ def convert_document(document: MeiliDocument) -> helpers.Document: if hasattr(document, "_geo") and hasattr(document, "geo_formatted_address"): geo = GeoResponse( - geo=document._geo, # type: ignore + geo=document._geo, geo_formatted_address=document.geo_formatted_address, ) else: @@ -109,7 +109,7 @@ def get_documents(index: Index, pagination: dict) -> Tuple[int, list[MeiliDocume # Create collection in typesense typesense_adapter.upsert_index(index) - collection_docs = typesense_adapter.client.collections[index].documents # type: ignore + collection_docs = typesense_adapter.client.collections[index].documents total, _ = get_documents(meili_index, {"limit": 1}) logging.info(f"Found {total} total documents") diff --git a/app/bin/reindex.py b/app/bin/reindex.py index c9b5c8d..afebdc8 100755 --- a/app/bin/reindex.py +++ b/app/bin/reindex.py @@ -14,7 +14,8 @@ # Load the .env file of our choice if specified before the regular .env can load load_dotenv(os.getenv("ENV")) -from app.geocoding.geocoding import GeoResponse, lookup_geo +from app.geocoding.geocoding import lookup_geo +from app.geocoding.types import GeoResponse from app.models.metadata import Metadata from app.models.transcript import Transcript from app.search import helpers @@ -68,10 +69,10 @@ def update_document( if TALKGROUPS.get(metadata["short_name"]): try: talkgroup = TALKGROUPS[metadata["short_name"]][metadata["talkgroup"]] - metadata["talkgroup_tag"] = talkgroup["Alpha Tag"].strip() # type: ignore - metadata["talkgroup_description"] = talkgroup["Description"].strip() # type: ignore - metadata["talkgroup_group"] = talkgroup["Category"].strip() # type: ignore - metadata["talkgroup_group_tag"] = talkgroup["Tag"].strip() # type: ignore + metadata["talkgroup_tag"] = talkgroup["Alpha Tag"].strip() + metadata["talkgroup_description"] = talkgroup["Description"].strip() + metadata["talkgroup_group"] = talkgroup["Category"].strip() + metadata["talkgroup_group_tag"] = talkgroup["Tag"].strip() except KeyError: logging.warning( f"Could not find talkgroup {metadata['talkgroup']} in {metadata['short_name']} CSV file" @@ -84,7 +85,7 @@ def update_document( and document["geo_formatted_address"] ): geo = GeoResponse( - geo=document["_geo"], # type: ignore + geo=document["_geo"], geo_formatted_address=document["geo_formatted_address"], ) elif should_lookup_geo: @@ -115,7 +116,7 @@ def update_document( # build_transcribe_options(metadata), # id=doc["id"], # index_name=index.uid, -# ) # type: ignore +# ) def load_csvs( @@ -126,7 +127,7 @@ def load_csvs( for system, file in unit_tags: tags = [] with open(file, newline="") as csvfile: - unit_reader = csv.reader(csvfile, escapechar="\\") # type: ignore + unit_reader = csv.reader(csvfile, escapechar="\\") for row in unit_reader: tags.append(row) UNIT_TAGS[system] = tags @@ -137,8 +138,8 @@ def load_csvs( tgs = {} with open(file, newline="") as csvfile: tg_reader = csv.DictReader(csvfile) - for row in tg_reader: # type: ignore - tgs[int(row["Decimal"])] = row # type: ignore + for row in tg_reader: + tgs[int(row["Decimal"])] = row TALKGROUPS[system] = tgs return UNIT_TAGS, TALKGROUPS diff --git a/app/geocoding/geocoding.py b/app/geocoding/geocoding.py index eb09fd4..9e9a6a3 100644 --- a/app/geocoding/geocoding.py +++ b/app/geocoding/geocoding.py @@ -1,7 +1,7 @@ import logging import re import os -from typing import Any, TypedDict +from typing import Any import sentry_sdk from geopy.location import Location @@ -13,16 +13,7 @@ from app.models.metadata import Metadata from app.models.transcript import Transcript from app.geocoding import llm - - -class Geo(TypedDict): - lat: float - lng: float - - -class GeoResponse(TypedDict): - geo: Geo - geo_formatted_address: str +from app.geocoding.types import AddressParts, GeoResponse def build_address_regex(include_intersections: bool = True) -> str: @@ -95,7 +86,7 @@ def contains_address(transcript: str) -> bool: def geocode( - address_parts: dict[str, str | None], geocoder: str | None = None + address_parts: AddressParts, geocoder: str | None = None ) -> GeoResponse | None: # pragma: no cover query: dict[str, Any] = { "query": f"{address_parts['address']}, {address_parts['city']}, {address_parts['state']}, {address_parts['country']}" @@ -243,7 +234,7 @@ def lookup_geo( if geocoding_systems == "*" or metadata["short_name"] in filter( lambda name: len(name), geocoding_systems.split(",") ): - default_address_parts = { + default_address_parts: AddressParts = { "city": os.getenv("GEOCODING_CITY"), "state": os.getenv("GEOCODING_STATE"), "country": os.getenv("GEOCODING_COUNTRY", "US"), @@ -251,8 +242,7 @@ def lookup_geo( bounds_raw = os.getenv("GEOCODING_BOUNDS") if bounds_raw: default_address_parts["bounds"] = [ - Point(bound) - for bound in bounds_raw.split("|") # type: ignore + Point(bound) for bound in bounds_raw.split("|") ] else: default_address_parts["bounds"] = None @@ -261,7 +251,7 @@ def lookup_geo( transcript_txt = transcript.txt_nosrc - address_parts = default_address_parts.copy() + address_parts: AddressParts = default_address_parts.copy() # TODO: how can we extract the city and state from the metadata? address_parts["address"] = extract_address(transcript_txt) if address_parts["address"]: diff --git a/app/geocoding/llm.py b/app/geocoding/llm.py index 5f8b304..dbbeebc 100644 --- a/app/geocoding/llm.py +++ b/app/geocoding/llm.py @@ -5,6 +5,7 @@ import google.generativeai as genai import google.ai.generativelanguage as generativelanguage +from app.geocoding.types import AddressParts from app.models.metadata import Metadata @@ -54,7 +55,7 @@ def generate_content(model: genai.GenerativeModel, prompt: str | list[str]) -> s def extract_address( model: genai.GenerativeModel, transcript: str, metadata: Metadata -) -> dict[str, str] | None: +) -> AddressParts | None: prompt = [ "You are a 911 dispatch transcript analyzer. You respond only in JSON. You return the address, city, and state found in a given transcript, if an address is present. If no address is present, you return null. Use the additional talkgroup data in determining the city and state.\n", f"Department: {metadata['talkgroup_group']}", @@ -70,11 +71,13 @@ def extract_address( logging.debug("Generated content: " + output) result = json.loads(output[output.index("{") : output.rindex("}") + 1]) if result["address"] and result["city"] and result["state"]: - return { - "address": result["address"], - "city": result["city"], - "state": result["state"], - } + return AddressParts( + { + "address": result["address"], + "city": result["city"], + "state": result["state"], + } + ) except Exception as e: logging.debug(e) diff --git a/app/geocoding/types.py b/app/geocoding/types.py new file mode 100644 index 0000000..e25fbee --- /dev/null +++ b/app/geocoding/types.py @@ -0,0 +1,21 @@ +from typing import NotRequired, TypedDict + +from geopy.point import Point + + +class Geo(TypedDict): + lat: float + lng: float + + +class GeoResponse(TypedDict): + geo: Geo + geo_formatted_address: str + + +class AddressParts(TypedDict): + address: NotRequired[str | None] + city: str | None + state: str | None + country: NotRequired[str | None] + bounds: NotRequired[list[Point] | None] diff --git a/app/models/base.py b/app/models/base.py deleted file mode 100644 index fa2b68a..0000000 --- a/app/models/base.py +++ /dev/null @@ -1,5 +0,0 @@ -from sqlalchemy.orm import DeclarativeBase - - -class Base(DeclarativeBase): - pass diff --git a/app/models/call.py b/app/models/call.py deleted file mode 100644 index 3686663..0000000 --- a/app/models/call.py +++ /dev/null @@ -1,89 +0,0 @@ -from typing import List, Optional, Tuple - -from pydantic import BaseModel -from sqlalchemy import Column, Integer, String, text -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.ext.mutable import MutableDict, MutableList -from sqlalchemy.orm import Session - -from app.geocoding.geocoding import GeoResponse - -from .base import Base - - -class Call(Base): - __tablename__ = "calls" - - id = Column(Integer, primary_key=True, index=True) - raw_metadata = Column(MutableDict.as_mutable(JSONB)) # type: ignore - raw_audio_url = Column(String) - raw_transcript = Column(MutableList.as_mutable(JSONB), nullable=True) # type: ignore - geo = Column(MutableDict.as_mutable(JSONB), nullable=True) # type: ignore - - -class CallBaseSchema(BaseModel): - pass - - -class CallCreateSchema(CallBaseSchema): - raw_metadata: dict - raw_audio_url: str - - -class CallUpdateSchema(CallBaseSchema): - raw_transcript: List[Tuple] - geo: Optional[GeoResponse] = None - - -class CallSchema(CallBaseSchema): - id: int - raw_metadata: dict - raw_audio_url: str - raw_transcript: Optional[List[Tuple]] = None - geo: Optional[GeoResponse] = None - - class Config: - from_attributes = True - - -def get_call(db: Session, call_id: int) -> Call | None: - return db.query(Call).filter(Call.id == call_id).first() - - -def get_calls(db: Session, skip: int = 0, limit: int = 100) -> List[Call]: - return db.query(Call).offset(skip).limit(limit).all() - - -def create_call(db: Session, call: CallCreateSchema) -> Call: - db_call = Call(raw_metadata=call.raw_metadata, raw_audio_url=call.raw_audio_url) - db.add(db_call) - db.commit() - db.refresh(db_call) - return db_call - - -def update_call(db: Session, call: CallUpdateSchema, db_call: Call) -> Call: - db_call.raw_transcript = call.raw_transcript # type: ignore - db_call.geo = call.geo # type: ignore - db.commit() - db.refresh(db_call) - return db_call - - -def get_talkgroups(db: Session, table_name: str) -> List[dict]: - query = f""" - SELECT - raw_metadata::jsonb ->> 'short_name' AS short_name, - raw_metadata::jsonb ->> 'talkgroup_group' AS talkgroup_group, - raw_metadata::jsonb ->> 'talkgroup_tag' AS talkgroup_tag, - raw_metadata::jsonb ->> 'talkgroup' AS talkgroup - FROM - {table_name} - WHERE - raw_metadata::jsonb ->> 'talkgroup_tag' != '' - GROUP BY - short_name, talkgroup_group, talkgroup_tag, talkgroup - """ - - result = db.execute(text(query)).fetchall() - return [dict(row._mapping) for row in result] diff --git a/app/models/database.py b/app/models/database.py index 48517e4..385f978 100644 --- a/app/models/database.py +++ b/app/models/database.py @@ -1,7 +1,6 @@ import os -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker +from sqlmodel import create_engine SQLALCHEMY_DATABASE_URL = f"postgresql+psycopg://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}/{os.getenv('POSTGRES_DB')}" @@ -9,4 +8,3 @@ SQLALCHEMY_DATABASE_URL, max_overflow=int(os.getenv("DB_CONNECTION_POOL_MAX_OVERFLOW", 100)), ) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/app/models/metadata.py b/app/models/metadata.py index 6593dfd..1deae82 100644 --- a/app/models/metadata.py +++ b/app/models/metadata.py @@ -1,4 +1,4 @@ -from typing import Literal, TypedDict +from typing import Literal, NotRequired, TypedDict class FreqListItem(TypedDict): @@ -15,7 +15,7 @@ class SrcListItem(TypedDict): emergency: Literal[0, 1] signal_system: str tag: str - transcript_prompt: str + transcript_prompt: NotRequired[str] class SearchableMetadata(TypedDict): diff --git a/app/models/models.py b/app/models/models.py new file mode 100644 index 0000000..2f81263 --- /dev/null +++ b/app/models/models.py @@ -0,0 +1,83 @@ +from sqlalchemy import Column, text +from sqlalchemy.dialects.postgresql import JSONB +from sqlmodel import SQLModel, Session, Field + +from app.geocoding.types import GeoResponse +from .metadata import Metadata +from .transcript import RawTranscript + + +class Base(SQLModel): + pass + + +class CallBase(Base): + raw_metadata: Metadata = Field(sa_column=Column(JSONB)) + raw_audio_url: str + raw_transcript: RawTranscript | None = Field( + default=None, sa_column=Column(JSONB, nullable=True) + ) + geo: GeoResponse | None = Field( + default=None, sa_column=Column(JSONB, nullable=True) + ) + + +class Call(CallBase, table=True): + __tablename__ = "calls" + id: int | None = Field(default=None, primary_key=True) + + +class CallCreate(CallBase): + pass + + +class CallUpdate(Base): + raw_metadata: Metadata | None = None + raw_audio_url: str | None = None + raw_transcript: RawTranscript | None = None + geo: GeoResponse | None = None + + +class CallPublic(CallBase): + id: int + + +class CallsPublic(Base): + data: list[CallPublic] + count: int + + +def create_call(db: Session, call: CallCreate) -> Call: + db_call = Call.model_validate(call) + db.add(db_call) + db.commit() + db.refresh(db_call) + return db_call + + +def update_call(db: Session, call: CallUpdate, db_call: Call) -> Call: + call_data = call.model_dump(exclude_unset=True) + db_call.sqlmodel_update(call_data) + db.add(db_call) + db.commit() + db.refresh(db_call) + return db_call + + +def get_talkgroups(db: Session, table_name: str) -> list[dict]: + query = f""" + SELECT + raw_metadata::jsonb ->> 'short_name' AS short_name, + raw_metadata::jsonb ->> 'talkgroup_group' AS talkgroup_group, + raw_metadata::jsonb ->> 'talkgroup_tag' AS talkgroup_tag, + raw_metadata::jsonb ->> 'talkgroup' AS talkgroup + FROM + {table_name} + WHERE + raw_metadata::jsonb ->> 'talkgroup_tag' != '' + GROUP BY + short_name, talkgroup_group, talkgroup_tag, talkgroup + """ + + result = db.execute(text(query)).fetchall() + return [dict(row._mapping) for row in result] diff --git a/app/models/transcript.py b/app/models/transcript.py index f8faaa3..2201cb3 100644 --- a/app/models/transcript.py +++ b/app/models/transcript.py @@ -1,10 +1,10 @@ import json -from typing import Tuple, TypeAlias, Union +from typing import Tuple, TypeAlias from app.whisper.exceptions import WhisperException from .metadata import SrcListItem -RawTranscript: TypeAlias = list[Tuple[Union[None, SrcListItem], str]] +RawTranscript: TypeAlias = list[Tuple[None | SrcListItem, str]] # TODO: write tests diff --git a/app/notifications/config.py b/app/notifications/config.py index f9dff46..996eadd 100644 --- a/app/notifications/config.py +++ b/app/notifications/config.py @@ -4,7 +4,7 @@ from typing import Optional, TypedDict from app.utils import api_client -from app.geocoding.geocoding import Geo +from app.geocoding.types import Geo class LocationAlertConfig(TypedDict): @@ -30,7 +30,7 @@ class NotificationConfig(TypedDict): @lru_cache() def get_notifications_config( ttl_hash: Optional[int] = None, -) -> dict[str, NotificationConfig]: # type: ignore +) -> dict[str, NotificationConfig]: del ttl_hash path = "config/notifications.json" diff --git a/app/search/adapters.py b/app/search/adapters.py index b4e25bd..c84a4e8 100644 --- a/app/search/adapters.py +++ b/app/search/adapters.py @@ -15,7 +15,7 @@ from meilisearch.index import Index from typesense.exceptions import ObjectNotFound, TypesenseClientError -from app.geocoding.geocoding import GeoResponse +from app.geocoding.types import GeoResponse from app.models.metadata import Metadata from app.models.transcript import Transcript from app.search.helpers import ( @@ -240,7 +240,7 @@ def index_call( self.created_indexes.add(index_name) try: - self.client.index(index_name).add_documents([doc]) # type: ignore + self.client.index(index_name).add_documents([doc]) except MeilisearchApiError as err: # Raise a different exception because of https://github.com/celery/celery/issues/6990 raise MeilisearchError(str(err)) @@ -429,7 +429,7 @@ def __init__( self.set_index(index_name) def set_index(self, index_name: str) -> None: - self.index: Collection = self.client.collections[index_name] # type: ignore + self.index: Collection = self.client.collections[index_name] def build_document( self, @@ -542,10 +542,10 @@ def index_call( index_name = get_default_index_name(call_time) try: - self.client.collections[index_name].documents.upsert(doc) # type: ignore + self.client.collections[index_name].documents.upsert(doc) except ObjectNotFound: self.upsert_index(index_name) - self.client.collections[index_name].documents.upsert(doc) # type: ignore + self.client.collections[index_name].documents.upsert(doc) except TypesenseClientError as err: raise Exception(str(err)) @@ -592,7 +592,7 @@ def upsert_index( } if enable_embeddings: - schema["fields"].append( # type: ignore + schema["fields"].append( { "name": "embedding", "type": "float[]", @@ -616,10 +616,10 @@ def upsert_index( } ) - collection: Collection = self.client.collections[index_name] # type: ignore + collection: Collection = self.client.collections[index_name] try: - collection_details = collection.retrieve() # type: ignore + collection_details = collection.retrieve() except ObjectNotFound: return self.client.collections.create(schema) @@ -630,9 +630,13 @@ def upsert_index( fields_to_update: list[TypesenseField] = [] def assert_equal(expected: TypesenseField, actual: TypesenseField) -> bool: - return all( - expected[key] == actual[key] # type: ignore - for key in expected + return ( + expected["name"] == actual["name"] + and expected.get("type") == actual.get("type") + and expected.get("facet") == actual.get("facet") + and expected.get("optional") == actual.get("optional") + and expected.get("embed") == actual.get("embed") + and expected.get("drop") == actual.get("drop") ) # Add new or modified fields @@ -657,12 +661,12 @@ def assert_equal(expected: TypesenseField, actual: TypesenseField) -> bool: if fields_to_update: logging.info(f"Updating schema: {str(fields_to_update)}") if not dry_run: - collection.update({"fields": fields_to_update}) # type: ignore + collection.update({"fields": fields_to_update}) return collection def delete_index(self) -> None: try: - self.index.delete() # type: ignore + self.index.delete() except ObjectNotFound: pass @@ -676,7 +680,7 @@ def index_calls(self, documents: list[Document]) -> bool: def search(self, query: str, options: dict) -> dict: options["q"] = query options["query_by"] = "transcript_plaintext" - return self.index.documents.search(options) # type: ignore + return self.index.documents.search(options) def get_documents( self, pagination: dict, search: dict | None = None diff --git a/app/utils/api_client.py b/app/utils/api_client.py index 19b263c..a68027c 100644 --- a/app/utils/api_client.py +++ b/app/utils/api_client.py @@ -1,10 +1,11 @@ +import json import logging import os import requests -def call(method: str, path: str, **kwargs): # type: ignore +def call(method: str, path: str, **kwargs): """Constructs and sends a :class:`Request `. :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. @@ -55,7 +56,7 @@ def call(method: str, path: str, **kwargs): # type: ignore ) if r.status_code >= 400: try: - logging.error(r.json()) + logging.error(json.dumps(r.json(), indent=2)) finally: r.raise_for_status() return r.json() diff --git a/app/utils/storage.py b/app/utils/storage.py index 835188b..3c6446c 100644 --- a/app/utils/storage.py +++ b/app/utils/storage.py @@ -15,7 +15,7 @@ @lru_cache() -def get_storage_client(): # type: ignore +def get_storage_client(): return boto3.resource( service_name="s3", endpoint_url=os.getenv("S3_ENDPOINT"), @@ -57,7 +57,7 @@ def fetch_audio(audio_url: str) -> str: mp3_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") if audio_url.startswith("data:"): uri = DataURI(audio_url) - mp3_file.write(uri.data) # type: ignore + mp3_file.write(uri.data) mp3_file.close() else: # Do this replacement so we can use the correct URL inside of Docker diff --git a/app/whisper/config.py b/app/whisper/config.py index d6a3cd9..4077511 100644 --- a/app/whisper/config.py +++ b/app/whisper/config.py @@ -5,7 +5,7 @@ @lru_cache() -def get_whisper_config(ttl_hash=None) -> dict[str, Any]: # type: ignore +def get_whisper_config(ttl_hash=None) -> dict[str, Any]: del ttl_hash whisper_kwargs = { @@ -37,5 +37,5 @@ def get_transcript_cleanup_config(ttl_hash=None) -> TranscriptCleanupConfig: config = "config/transcript_cleanup.json" if os.path.isfile(config): with open(config) as file: - return json.load(file) # type: ignore + return json.load(file) return [] diff --git a/app/whisper/deepgram.py b/app/whisper/deepgram.py index 6f5c215..b48ebfe 100644 --- a/app/whisper/deepgram.py +++ b/app/whisper/deepgram.py @@ -27,7 +27,7 @@ def transcribe( ) response: PrerecordedResponse = self.client.listen.prerecorded.v( "1" - ).transcribe_file(payload, deepgram_options, timeout=120) # type: ignore + ).transcribe_file(payload, deepgram_options, timeout=120) if ( response.results diff --git a/app/whisper/faster_whisper.py b/app/whisper/faster_whisper.py index 8721681..e2db0cb 100644 --- a/app/whisper/faster_whisper.py +++ b/app/whisper/faster_whisper.py @@ -12,7 +12,7 @@ def __init__(self, model_name: str): device = torch_device.split(":")[0] device_index = torch_device.split(":")[1] if ":" in torch_device else "0" device_index = ( - [int(i) for i in device_index.split(",")] # type: ignore + [int(i) for i in device_index.split(",")] if "," in device_index else int(device_index) ) @@ -45,7 +45,7 @@ def transcribe( result: WhisperResult = {"segments": [], "text": "", "language": language} if len(segments): - result["segments"] = [dict(segment._asdict()) for segment in segments] # type: ignore + result["segments"] = [dict(segment._asdict()) for segment in segments] result["text"] = "\n".join( [segment["text"] for segment in result["segments"]] ) diff --git a/app/whisper/openai.py b/app/whisper/openai.py index a23edac..ae077e4 100644 --- a/app/whisper/openai.py +++ b/app/whisper/openai.py @@ -26,4 +26,4 @@ def transcribe( prompt=prompt, response_format="verbose_json", language=language, - ).model_dump() # type: ignore + ).model_dump() diff --git a/app/whisper/transcribe.py b/app/whisper/transcribe.py index 842468b..bab6a82 100644 --- a/app/whisper/transcribe.py +++ b/app/whisper/transcribe.py @@ -80,7 +80,7 @@ def transcribe( # except WhisperException: # cleaned_results.append(None) # return cleaned_results -# return results # type: ignore +# return results def cleanup_transcript( diff --git a/app/whisper/whisper_s2t.py b/app/whisper/whisper_s2t.py index 4dc817a..666d9a1 100644 --- a/app/whisper/whisper_s2t.py +++ b/app/whisper/whisper_s2t.py @@ -15,7 +15,7 @@ def __init__(self, model_name: str): device = torch_device.split(":")[0] device_index = torch_device.split(":")[1] if ":" in torch_device else "0" device_index = ( - [int(i) for i in device_index.split(",")] # type: ignore + [int(i) for i in device_index.split(",")] if "," in device_index else int(device_index) ) diff --git a/app/worker.py b/app/worker.py index 74294d6..8bf110e 100644 --- a/app/worker.py +++ b/app/worker.py @@ -5,7 +5,6 @@ import os import signal from hashlib import sha256 -from multiprocessing.pool import AsyncResult from typing import Optional import requests @@ -60,8 +59,7 @@ backend=result_backend, task_acks_late=True, worker_cancel_long_running_tasks_on_connection_loss=True, - worker_prefetch_multiplier=os.getenv("CELERY_PREFETCH_MULTIPLIER", 1), - timezone="UTC", + worker_prefetch_multiplier=int(os.getenv("CELERY_PREFETCH_MULTIPLIER", 1)), ) celery.conf.task_default_queue = CELERY_DEFAULT_QUEUE search_adapters: list[SearchAdapter] = [] @@ -78,7 +76,7 @@ def queue_task( whisper_implementation: Optional[str] = None, id: Optional[int | str] = None, index_name: Optional[str] = None, -) -> AsyncResult[str]: +): return ( transcribe_task.s(options, audio_url, whisper_implementation).set( queue=CELERY_DEFAULT_QUEUE @@ -92,7 +90,7 @@ def queue_task( @signals.task_prerun.connect -def task_prerun(**kwargs): # type: ignore +def task_prerun(**kwargs): # If we've only had failing tasks on this worker, terminate it if len(recent_job_results) == 5 and states.SUCCESS not in recent_job_results: logger.fatal( @@ -115,21 +113,21 @@ def task_prerun(**kwargs): # type: ignore @signals.task_postrun.connect -def task_postrun(**kwargs): # type: ignore +def task_postrun(**kwargs): recent_job_results.insert(0, kwargs["state"]) if len(recent_job_results) > 5: recent_job_results.pop() -@signals.task_unknown.connect # type: ignore -def task_unknown(**kwargs): # type: ignore +@signals.task_unknown.connect +def task_unknown(**kwargs): logger.exception(kwargs["exc"]) logger.fatal("Unknown job, exiting...") os.kill(os.getpid(), signal.SIGQUIT) -@signals.task_retry.connect # type: ignore -def task_retry(**kwargs): # type: ignore +@signals.task_retry.connect +def task_retry(**kwargs): if isinstance(kwargs["reason"], WhisperException): return logger.exception(kwargs["reason"]) @@ -155,7 +153,7 @@ def transcribe_task( audio_file = fetch_audio(audio_url) try: return transcribe( - model=self.model(whisper_implementation), # type: ignore + model=self.model(whisper_implementation), audio_file=audio_file, options=options, ) @@ -199,7 +197,7 @@ def post_transcribe_task( api_client.call( "patch", f"calls/{id}", - json={"raw_transcript": transcript.transcript}, + json={"raw_transcript": transcript.transcript, "geo": geo}, ) global search_adapters diff --git a/poetry.lock b/poetry.lock index b6428c5..9524f7c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,6 +145,25 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "alembic" +version = "1.14.0" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"}, + {file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + [[package]] name = "amqp" version = "5.3.1" @@ -242,17 +261,17 @@ files = [ [[package]] name = "boto3" -version = "1.35.87" +version = "1.35.90" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.87-py3-none-any.whl", hash = "sha256:588ab05e2771c50fca5c242be14e7a25200ffd3dd95c45950ce40993473864c7"}, - {file = "boto3-1.35.87.tar.gz", hash = "sha256:341c58602889078a4a25dc4331b832b5b600a33acd73471d2532c6f01b16fbb4"}, + {file = "boto3-1.35.90-py3-none-any.whl", hash = "sha256:b0874233057995a8f0c813f5b45a36c09630e74c43d7a7c64db2feef2915d493"}, + {file = "boto3-1.35.90.tar.gz", hash = "sha256:dc56caaaab2157a4bfc109c88b50cd032f3ac66c06d17f8ee335b798eaf53e5c"}, ] [package.dependencies] -botocore = ">=1.35.87,<1.36.0" +botocore = ">=1.35.90,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -261,13 +280,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs" -version = "1.35.87" -description = "Type annotations for boto3 1.35.87 generated with mypy-boto3-builder 8.7.0" +version = "1.35.90" +description = "Type annotations for boto3 1.35.90 generated with mypy-boto3-builder 8.7.0" optional = false python-versions = ">=3.8" files = [ - {file = "boto3_stubs-1.35.87-py3-none-any.whl", hash = "sha256:76bb623169ee670ca04c07ccdde5218380b7ccc9803b2730e90e436fb42f4298"}, - {file = "boto3_stubs-1.35.87.tar.gz", hash = "sha256:85c1a54f30e33b5ab3946133275bef3fce1f7ae8fd97525532310f573bd0e38e"}, + {file = "boto3_stubs-1.35.90-py3-none-any.whl", hash = "sha256:68f384c11267c0eac982e041e4af0bc88efae6816b8def74aca40de76acbd45d"}, + {file = "boto3_stubs-1.35.90.tar.gz", hash = "sha256:23a3834d51c603b808847df5dd56b6c75d9c39e897848e65570d86b415d5f469"}, ] [package.dependencies] @@ -322,7 +341,7 @@ bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime ( bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)"] billing = ["mypy-boto3-billing (>=1.35.0,<1.36.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.35.0,<1.36.0)"] -boto3 = ["boto3 (==1.35.87)"] +boto3 = ["boto3 (==1.35.90)"] braket = ["mypy-boto3-braket (>=1.35.0,<1.36.0)"] budgets = ["mypy-boto3-budgets (>=1.35.0,<1.36.0)"] ce = ["mypy-boto3-ce (>=1.35.0,<1.36.0)"] @@ -687,13 +706,13 @@ xray = ["mypy-boto3-xray (>=1.35.0,<1.36.0)"] [[package]] name = "botocore" -version = "1.35.87" +version = "1.35.90" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.87-py3-none-any.whl", hash = "sha256:81cf84f12030d9ab3829484b04765d5641697ec53c2ac2b3987a99eefe501692"}, - {file = "botocore-1.35.87.tar.gz", hash = "sha256:3062d073ce4170a994099270f469864169dc1a1b8b3d4a21c14ce0ae995e0f89"}, + {file = "botocore-1.35.90-py3-none-any.whl", hash = "sha256:51dcbe1b32e2ac43dac17091f401a00ce5939f76afe999081802009cce1e92e4"}, + {file = "botocore-1.35.90.tar.gz", hash = "sha256:f007f58e8e3c1ad0412a6ddfae40ed92a7bca571c068cb959902bcf107f2ae48"}, ] [package.dependencies] @@ -706,13 +725,13 @@ crt = ["awscrt (==0.22.0)"] [[package]] name = "botocore-stubs" -version = "1.35.87" +version = "1.35.90" description = "Type annotations and code completion for botocore" optional = false python-versions = ">=3.8" files = [ - {file = "botocore_stubs-1.35.87-py3-none-any.whl", hash = "sha256:78645741966528d2cf1b6c140645766221cca39075acaa321af60751efe40645"}, - {file = "botocore_stubs-1.35.87.tar.gz", hash = "sha256:09e2a2c0757fb95a0ca595c724eb5a151f1c4c264b51c43cbdb55853a796c4c4"}, + {file = "botocore_stubs-1.35.90-py3-none-any.whl", hash = "sha256:f7fd78d84f49d28692662b9bdeb4c92f1bf8a5707d0c28c8544399005b02823b"}, + {file = "botocore_stubs-1.35.90.tar.gz", hash = "sha256:c6b294cae436eaaf87dcb717e4348c250ea1fc170336579da114b693663d8e42"}, ] [package.dependencies] @@ -843,19 +862,18 @@ files = [ celery = ">=5.0,<5.4" [[package]] -name = "celery-stubs" -version = "0.1.3" -description = "celery stubs" +name = "celery-types" +version = "0.22.0" +description = "Type stubs for Celery and its related packages" optional = false -python-versions = "*" +python-versions = ">=3.9,<4.0" files = [ - {file = "celery-stubs-0.1.3.tar.gz", hash = "sha256:0fb5345820f8a2bd14e6ffcbef2d10181e12e40f8369f551d7acc99d8d514919"}, - {file = "celery_stubs-0.1.3-py3-none-any.whl", hash = "sha256:dfb9ad27614a8af028b2055bb4a4ae99ca5e9a8d871428a506646d62153218d7"}, + {file = "celery_types-0.22.0-py3-none-any.whl", hash = "sha256:79a66637d1d6af5992d1dc80259d9538869941325e966006f1e795220519b9ac"}, + {file = "celery_types-0.22.0.tar.gz", hash = "sha256:0ecad2fa5a6eded0a1f919e5e1e381cc2ff0635fe4b21db53b4661b6876d5b30"}, ] [package.dependencies] -mypy = ">=0.950" -typing-extensions = ">=4.2.0" +typing-extensions = ">=4.9.0,<5.0.0" [[package]] name = "certifi" @@ -1045,73 +1063,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.9" +version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, - {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, - {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, - {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, - {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, - {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, - {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, - {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, - {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, - {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, - {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, - {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, - {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, - {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, - {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, - {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, - {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, - {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, - {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, - {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, - {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, - {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, - {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, - {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, - {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, - {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, ] [package.extras] @@ -1956,6 +1974,25 @@ sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=2.8.0)"] +[[package]] +name = "mako" +version = "1.3.8" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, + {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + [[package]] name = "markdown" version = "3.7" @@ -1971,6 +2008,76 @@ files = [ docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + [[package]] name = "marshmallow" version = "3.23.2" @@ -2108,43 +2215,49 @@ files = [ [[package]] name = "mypy" -version = "1.14.0" +version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87"}, - {file = "mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179"}, - {file = "mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e"}, - {file = "mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3"}, - {file = "mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44"}, - {file = "mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a"}, - {file = "mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc"}, - {file = "mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015"}, - {file = "mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb"}, - {file = "mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc"}, - {file = "mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd"}, - {file = "mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1"}, - {file = "mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63"}, - {file = "mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d"}, - {file = "mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba"}, - {file = "mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741"}, - {file = "mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7"}, - {file = "mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8"}, - {file = "mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc"}, - {file = "mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f"}, - {file = "mypy-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b16738b1d80ec4334654e89e798eb705ac0c36c8a5c4798496cd3623aa02286"}, - {file = "mypy-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10065fcebb7c66df04b05fc799a854b1ae24d9963c8bb27e9064a9bdb43aa8ad"}, - {file = "mypy-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbb7d683fa6bdecaa106e8368aa973ecc0ddb79a9eaeb4b821591ecd07e9e03c"}, - {file = "mypy-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3498cb55448dc5533e438cd13d6ddd28654559c8c4d1fd4b5ca57a31b81bac01"}, - {file = "mypy-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c7b243408ea43755f3a21a0a08e5c5ae30eddb4c58a80f415ca6b118816e60aa"}, - {file = "mypy-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14117b9da3305b39860d0aa34b8f1ff74d209a368829a584eb77524389a9c13e"}, - {file = "mypy-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af98c5a958f9c37404bd4eef2f920b94874507e146ed6ee559f185b8809c44cc"}, - {file = "mypy-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b343a1d3989547024377c2ba0dca9c74a2428ad6ed24283c213af8dbb0710b"}, - {file = "mypy-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cdb5563c1726c85fb201be383168f8c866032db95e1095600806625b3a648cb7"}, - {file = "mypy-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:74e925649c1ee0a79aa7448baf2668d81cc287dc5782cff6a04ee93f40fb8d3f"}, - {file = "mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab"}, - {file = "mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, ] [package.dependencies] @@ -2743,13 +2856,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyparsing" -version = "3.2.0" +version = "3.2.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" files = [ - {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, - {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, + {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, + {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, ] [package.extras] @@ -3265,6 +3378,21 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] +[[package]] +name = "sqlmodel" +version = "0.0.22" +description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b"}, + {file = "sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e"}, +] + +[package.dependencies] +pydantic = ">=1.10.13,<3.0.0" +SQLAlchemy = ">=2.0.14,<2.1.0" + [[package]] name = "starlette" version = "0.41.3" @@ -3863,4 +3991,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "bcf80c24f21cf923bb88d8810050fa100864b57d01c356ed40b733ac48087fc4" +content-hash = "05e012aa46486a043014b02eb86cfd5bbc871bd64221373c398768c6c91177c7" diff --git a/pyproject.toml b/pyproject.toml index d63bddf..d8b5fad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,18 +33,20 @@ requests-cache = "^1.2.0" celery-batches = "^0.8.1" vastai = "^0.2.6" typesense = "^0.21.0" +sqlmodel = "^0.0.22" +alembic = "^1.14.0" [tool.poetry.group.dev.dependencies] coverage = "^7.2.5" pytest = "^8.3.2" -mypy = "^1.11.2" +mypy = "^1.14.0" ruff = "^0.6.3" -celery-stubs = "^0.1.3" types-requests = "^2.32.0.20240712" types-cachetools = "^5.5.0.20240820" types-pytz = "^2024.1.0.20240417" pytest-dotenv = "^0.5.2" +celery-types = "^0.22.0" [build-system] requires = ["poetry-core"] diff --git a/tests/geocoding/test_geocoding.py b/tests/geocoding/test_geocoding.py index 67e06d6..6dfdf57 100644 --- a/tests/geocoding/test_geocoding.py +++ b/tests/geocoding/test_geocoding.py @@ -22,7 +22,7 @@ def test_lookup_geo_with_llm(self): "short_name": "sc21102", "talkgroup_description": "3 East: Elmhurst, Oakbrook Terrace Police", "talkgroup_group": "DuPage County - DuPage Public Safety Communications (DU-COMM)", - } # type: ignore + } ), Transcript( [ @@ -41,7 +41,7 @@ def test_lookup_geo_with_llm(self): "short_name": "chisuburbs", "talkgroup_description": "Fire Dispatch: South", "talkgroup_group": "Regional Emergency Dispatch - RED Center (Northbrook)", - } # type: ignore + } ), Transcript( [ @@ -68,7 +68,7 @@ def test_lookup_geo_with_llm(self): "short_name": "chi_oemc", "talkgroup_description": "Fire Supression North", "talkgroup_group": "Chicago Fire Department", - } # type: ignore + } ), Transcript( [ @@ -95,7 +95,7 @@ def test_lookup_geo_with_llm(self): "short_name": "chi_cpd", "talkgroup_description": "Citywide 1", "talkgroup_group": "Chicago Police Department", - } # type: ignore + } ), Transcript( [ diff --git a/tests/notification/test_notification.py b/tests/notification/test_notification.py index 519bb95..844c988 100644 --- a/tests/notification/test_notification.py +++ b/tests/notification/test_notification.py @@ -235,7 +235,7 @@ def test_alerts_when_keyword_and_traveltime_match(self): should_send, title, body = notification.should_send_alert( config, transcript, geo - ) # type: ignore + ) self.assertTrue(should_send) self.assertRegex( @@ -263,7 +263,7 @@ def test_does_not_alert_when_keyword_and_traveltime_do_not_match(self): should_send, title, body = notification.should_send_alert( config, transcript, geo - ) # type: ignore + ) self.assertFalse(should_send) self.assertEqual(title, "") @@ -284,7 +284,7 @@ def test_does_not_alert_when_keyword_does_not_match(self): should_send, title, body = notification.should_send_alert( config, transcript, geo - ) # type: ignore + ) self.assertFalse(should_send) self.assertEqual(title, "") @@ -305,7 +305,7 @@ def test_does_not_alert_when_no_location(self): should_send, title, body = notification.should_send_alert( config, transcript, geo - ) # type: ignore + ) self.assertFalse(should_send) self.assertEqual(title, "") diff --git a/tests/radio/test_analog.py b/tests/radio/test_analog.py index 9d0c0ac..2c6d739 100644 --- a/tests/radio/test_analog.py +++ b/tests/radio/test_analog.py @@ -24,7 +24,7 @@ def test_process_response(self): expected_transcript.append("Hello") expected_transcript.append("world") - result = process_response(self.response, {}) # type: ignore + result = process_response(self.response, {}) self.assertEqual(expected_transcript.json, result.json) diff --git a/tests/search/test_adapters.py b/tests/search/test_adapters.py index 11028f8..919903c 100644 --- a/tests/search/test_adapters.py +++ b/tests/search/test_adapters.py @@ -1,4 +1,3 @@ -import logging import os from unittest import TestCase @@ -6,7 +5,7 @@ import typesense from dotenv import load_dotenv -from app.geocoding.geocoding import GeoResponse +from app.geocoding.types import GeoResponse from app.models.metadata import Metadata from app.models.transcript import Transcript from app.search.adapters import MeilisearchAdapter, TypesenseAdapter @@ -202,5 +201,5 @@ def test_index_call(self): def test_upsert_index(self): adapter = TypesenseAdapter() adapter.upsert_index("calls") - collection = adapter.client.collections["calls"].retrieve() # type: ignore + collection = adapter.client.collections["calls"].retrieve() self.assertIsNotNone(collection) diff --git a/tests/test_e2e.py b/tests/test_e2e.py index b916b6c..d8f4e5b 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -1,5 +1,4 @@ import json -import logging import os import unittest from time import sleep @@ -120,7 +119,6 @@ def test_transcribes_analog(self): self.assertTrue(isinstance(json.loads(hit["raw_metadata"]), dict)) self.assertTrue(isinstance(json.loads(hit["raw_transcript"]), list)) - def test_transcribes_without_db(self): result = self.transcribe( "tests/data/9051-1699224861_773043750.0-call_20452.wav", diff --git a/tests/utils/test_conversion.py b/tests/utils/test_conversion.py index a92162c..be29d50 100644 --- a/tests/utils/test_conversion.py +++ b/tests/utils/test_conversion.py @@ -13,7 +13,7 @@ def setUp(self): self.format = "mp3" self.ffmpeg_args = ["-codec:a", "libmp3lame"] self.metadata = Metadata( - { # type: ignore + { "freq": 477787500, "start_time": 1673118015, "stop_time": 1673118023, diff --git a/tests/whisper/test_whisper.py b/tests/whisper/test_whisper.py index b92b92c..8afa2b6 100644 --- a/tests/whisper/test_whisper.py +++ b/tests/whisper/test_whisper.py @@ -29,7 +29,7 @@ def _transform_into_whisper_result( result["text"] = result["text"].strip() - return result # type: ignore + return result def test_transcript_cleanup_on_hallucinations(self): with open("tests/data/hallucinations.json") as file: From cce987bce75558a2004b3703a2457975fe137663 Mon Sep 17 00:00:00 2001 From: Eric Tendian Date: Thu, 2 Jan 2025 00:45:23 -0600 Subject: [PATCH 2/2] Fixing a few more linting errors --- app/whisper/faster_whisper.py | 13 ++++++------- app/whisper/openai.py | 2 +- app/whisper/whisper_s2t.py | 11 +++++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/whisper/faster_whisper.py b/app/whisper/faster_whisper.py index e2db0cb..660babf 100644 --- a/app/whisper/faster_whisper.py +++ b/app/whisper/faster_whisper.py @@ -11,11 +11,6 @@ def __init__(self, model_name: str): ) device = torch_device.split(":")[0] device_index = torch_device.split(":")[1] if ":" in torch_device else "0" - device_index = ( - [int(i) for i in device_index.split(",")] - if "," in device_index - else int(device_index) - ) compute_type = os.getenv( "TORCH_DTYPE", "int8" if "cpu" in os.getenv("TORCH_DEVICE", "") else "float16", @@ -24,7 +19,11 @@ def __init__(self, model_name: str): self.model = WhisperModel( model_name, device=device, - device_index=device_index, + device_index=( + [int(i) for i in device_index.split(",")] + if "," in device_index + else int(device_index) + ), compute_type=compute_type, ) @@ -45,7 +44,7 @@ def transcribe( result: WhisperResult = {"segments": [], "text": "", "language": language} if len(segments): - result["segments"] = [dict(segment._asdict()) for segment in segments] + result["segments"] = [dict(segment._asdict()) for segment in segments] # type: ignore result["text"] = "\n".join( [segment["text"] for segment in result["segments"]] ) diff --git a/app/whisper/openai.py b/app/whisper/openai.py index ae077e4..a23edac 100644 --- a/app/whisper/openai.py +++ b/app/whisper/openai.py @@ -26,4 +26,4 @@ def transcribe( prompt=prompt, response_format="verbose_json", language=language, - ).model_dump() + ).model_dump() # type: ignore diff --git a/app/whisper/whisper_s2t.py b/app/whisper/whisper_s2t.py index 666d9a1..f1bf7af 100644 --- a/app/whisper/whisper_s2t.py +++ b/app/whisper/whisper_s2t.py @@ -14,11 +14,6 @@ def __init__(self, model_name: str): ) device = torch_device.split(":")[0] device_index = torch_device.split(":")[1] if ":" in torch_device else "0" - device_index = ( - [int(i) for i in device_index.split(",")] - if "," in device_index - else int(device_index) - ) compute_type = os.getenv( "TORCH_DTYPE", "int8" if "cpu" in os.getenv("TORCH_DEVICE", "") else "float16", @@ -27,7 +22,11 @@ def __init__(self, model_name: str): model_kwargs = { "asr_options": BEST_ASR_CONFIG, "device": device, - "device_index": device_index, + "device_index": ( + [int(i) for i in device_index.split(",")] + if "," in device_index + else int(device_index) + ), "compute_type": compute_type, } backend = "CTranslate2"