From b4140460014d1fc134df9127600f6da2346376e3 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Tue, 20 Aug 2024 17:34:18 -0700 Subject: [PATCH 01/13] init commit with base ECS and Network System --- data/database/.gitignore | 2 - kennel/app.py | 13 + kennel/config.py | 20 + data/.gitkeep => kennel/engine/__init__.py | 0 .../engine/components/__init__.py | 0 kennel/engine/components/component.py | 16 + kennel/engine/components/controllable.py | 13 + kennel/engine/components/position.py | 14 + kennel/engine/entities/__init__.py | 0 kennel/engine/entities/cat.py | 33 + kennel/engine/entities/entity.py | 60 ++ kennel/engine/entities/laser.py | 13 + kennel/engine/game.py | 53 + kennel/engine/systems/__init__.py | 0 kennel/engine/systems/network.py | 88 ++ kennel/engine/systems/system.py | 31 + kennel/kennel.py | 81 ++ kennel/kennelcats.py | 45 + kennel/main.py | 149 ++- kennel/middleware.py | 30 + poetry.lock | 930 ++++++++++-------- pyproject.toml | 3 + static/index.js | 15 +- 23 files changed, 1173 insertions(+), 436 deletions(-) delete mode 100644 data/database/.gitignore create mode 100644 kennel/app.py create mode 100644 kennel/config.py rename data/.gitkeep => kennel/engine/__init__.py (100%) rename data/database/.gitkeep => kennel/engine/components/__init__.py (100%) create mode 100644 kennel/engine/components/component.py create mode 100644 kennel/engine/components/controllable.py create mode 100644 kennel/engine/components/position.py create mode 100644 kennel/engine/entities/__init__.py create mode 100644 kennel/engine/entities/cat.py create mode 100644 kennel/engine/entities/entity.py create mode 100644 kennel/engine/entities/laser.py create mode 100644 kennel/engine/game.py create mode 100644 kennel/engine/systems/__init__.py create mode 100644 kennel/engine/systems/network.py create mode 100644 kennel/engine/systems/system.py create mode 100644 kennel/kennel.py create mode 100644 kennel/kennelcats.py create mode 100644 kennel/middleware.py diff --git a/data/database/.gitignore b/data/database/.gitignore deleted file mode 100644 index 2621632..0000000 --- a/data/database/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.sqlite -*.db diff --git a/kennel/app.py b/kennel/app.py new file mode 100644 index 0000000..98a0448 --- /dev/null +++ b/kennel/app.py @@ -0,0 +1,13 @@ +import structlog +from fastapi import FastAPI +from fastapi.templating import Jinja2Templates + +app = FastAPI( + servers = [ + {"url": "https://kennel.hatecomputers.club", "description": "prod"} + ] +) + +logger = structlog.get_logger() + +templates = Jinja2Templates(directory="templates") diff --git a/kennel/config.py b/kennel/config.py new file mode 100644 index 0000000..8cec699 --- /dev/null +++ b/kennel/config.py @@ -0,0 +1,20 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +class Config: + SIMULATION_WIDTH = int(os.getenv("SIMULATION_WIDTH", 1000)) + SIMULATION_HEIGHT = int(os.getenv("SIMULATION_HEIGHT", 1000)) + + HATECOMPUTERS_ENDPOINT = os.getenv( + "HATECOMPUTERS_ENDPOINT", "https://hatecomputers.club" + ) + + MIN_TIME_STEP = float(os.getenv("MIN_TIME_STEP", 0.05)) + + COOKIE_MAX_AGE = int(os.getenv("COOKIE_MAX_AGE", 60 * 60 * 24 * 7)) # 1 week + + +config = Config() diff --git a/data/.gitkeep b/kennel/engine/__init__.py similarity index 100% rename from data/.gitkeep rename to kennel/engine/__init__.py diff --git a/data/database/.gitkeep b/kennel/engine/components/__init__.py similarity index 100% rename from data/database/.gitkeep rename to kennel/engine/components/__init__.py diff --git a/kennel/engine/components/component.py b/kennel/engine/components/component.py new file mode 100644 index 0000000..c8ef2d0 --- /dev/null +++ b/kennel/engine/components/component.py @@ -0,0 +1,16 @@ +from enum import Enum +from abc import abstractmethod + + +class ComponentType(str, Enum): + POSITION = "POSITION" + CONTROLLABLE = "CONTROLLABLE" + + +class Component: + def __init__(self, component_type: ComponentType): + self.component_type = component_type + + @abstractmethod + def dict(self) -> dict: + pass diff --git a/kennel/engine/components/controllable.py b/kennel/engine/components/controllable.py new file mode 100644 index 0000000..49b0557 --- /dev/null +++ b/kennel/engine/components/controllable.py @@ -0,0 +1,13 @@ +from .component import Component, ComponentType + + +class Controllable(Component): + def __init__(self, by: str): + super().__init__(ComponentType.CONTROLLABLE) + self.by = by + + def __repr__(self) -> str: + return f"Controllable(by={self.by})" + + def dict(self) -> dict: + return {"by": self.by} diff --git a/kennel/engine/components/position.py b/kennel/engine/components/position.py new file mode 100644 index 0000000..2a4da4a --- /dev/null +++ b/kennel/engine/components/position.py @@ -0,0 +1,14 @@ +from .component import Component, ComponentType + + +class Position(Component): + def __init__(self, x: float, y: float): + super().__init__(ComponentType.POSITION) + self.x = x + self.y = y + + def __repr__(self) -> str: + return f"Position(x={self.x}, y={self.y})" + + def dict(self) -> dict: + return {"x": self.x, "y": self.y} diff --git a/kennel/engine/entities/__init__.py b/kennel/engine/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kennel/engine/entities/cat.py b/kennel/engine/entities/cat.py new file mode 100644 index 0000000..02fdb33 --- /dev/null +++ b/kennel/engine/entities/cat.py @@ -0,0 +1,33 @@ +# +# # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY +# state_stochastic_matrix = [ +# # IDLE +# [0.5, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0], +# # FROLICKING +# [0.1, 0.5, 0.1, 0.1, 0.1, 0.05, 0.05, 0], +# # EEPY +# [0.1, 0.1, 0.5, 0.1, 0.1, 0.05, 0.05, 0], +# # ALERT +# [0.1, 0.1, 0.1, 0.5, 0.1, 0.05, 0.05, 0], +# # CHASING_CURSOR +# [0.1, 0.1, 0.1, 0.1, 0.5, 0.05, 0.05, 0], +# # CHASING_CAT +# [0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 0.05, 0], +# # SCRATCHING +# [0.1, 0.1, 0.1, 0.1, 0.1, 0.05, 0.5, 0], +# # ITCHY +# [0, 0, 0, 0, 0, 0, 0, 1], +# ] +# +# +# class CatState(Enum): +# IDLE = 0 +# FROLICKING = 1 +# EEPY = 2 +# ALERT = 3 +# CHASING_CURSOR = 4 +# CHASING_CAT = 5 +# SCRATCHING = 6 +# ITCHY = 7 +# +# diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py new file mode 100644 index 0000000..0ca8666 --- /dev/null +++ b/kennel/engine/entities/entity.py @@ -0,0 +1,60 @@ +from enum import Enum +from kennel.engine.components.component import Component, ComponentType +from typing import List, Optional + + +class EntityType(str, Enum): + LASER = "LASER" + + +class Entity: + def __init__( + self, entity_type: EntityType, id: str, component_list: List[Component] + ): + self.entity_type = entity_type + self.id = id + self.components = {} + for component in component_list: + self.add_component(component) + + def get_component(self, component_type: ComponentType) -> Optional[Component]: + return self.components[component_type] + + def add_component(self, component: Component) -> None: + self.components[component.component_type] = component + + +class EntityManager: + def __init__(self): + self.entities = {} + self.component_entities = {} + + def update(self) -> None: + self.component_entities = {} + for entity in self.entities.values(): + for component in entity.components.values(): + if component.component_type not in self.component_entities: + self.component_entities[component.component_type] = set() + self.component_entities[component.component_type].add(entity) + + def get_entities_with_component( + self, component_type: ComponentType + ) -> List[Entity]: + return self.component_entities.get(component_type, []) + + def add_entity(self, entity: Entity) -> None: + self.entities[entity.id] = entity + for component in entity.components.values(): + if component.component_type not in self.component_entities: + self.component_entities[component.component_type] = set() + self.component_entities[component.component_type].add(entity) + + def remove_entity(self, entity_id: str) -> None: + if entity_id in self.entities: + entity = self.entities[entity_id] + for component in entity.components.values(): + if component.component_type in self.component_entities: + self.component_entities[component.component_type].remove(entity) + + def get_entity(self, entity_id: str) -> Optional[Entity]: + return self.entities.get(entity_id) diff --git a/kennel/engine/entities/laser.py b/kennel/engine/entities/laser.py new file mode 100644 index 0000000..ead72a6 --- /dev/null +++ b/kennel/engine/entities/laser.py @@ -0,0 +1,13 @@ +from .entity import Entity, EntityType +from kennel.engine.components.position import Position +from kennel.engine.components.controllable import Controllable + + +class Laser(Entity): + def __init__(self, id: str, controllable_by: str): + components = [ + Position(0, 0), + Controllable(controllable_by), + ] + + super().__init__(EntityType.LASER, id, components) diff --git a/kennel/engine/game.py b/kennel/engine/game.py new file mode 100644 index 0000000..2236306 --- /dev/null +++ b/kennel/engine/game.py @@ -0,0 +1,53 @@ +from kennel.engine.entities.entity import EntityManager +from kennel.engine.systems.system import SystemManager +from kennel.app import logger +from typing import List, Optional +import time +import asyncio + + +class Game: + def __init__( + self, + entity_manager: EntityManager, + system_manager: SystemManager, + min_time_step: float, + ): + self.entity_manager = entity_manager + self.system_manager = system_manager + self.min_time_step = min_time_step + + self.last_time = time.time() + self.running = False + + async def update(self, delta_time: float) -> None: + self.entity_manager.update() + await self.system_manager.update(self.entity_manager, delta_time) + + async def run(self) -> None: + logger.info("Game started") + self.running = True + self.last_time = time.time() + steps_since_log = 0 + + while self.running: + current_time = time.time() + if (int(current_time) - int(self.last_time)) == 1 and int( + current_time + ) % 10 == 0: + logger.info(f"Game loop: {steps_since_log} steps since last log time") + steps_since_log = 0 + + delta_time = current_time - self.last_time + self.last_time = current_time + + await self.update(delta_time) + await asyncio.sleep( + max(self.min_time_step - (time.time() - current_time), 0) + ) + + steps_since_log += 1 + + def stop(self) -> None: + self.running = False + logger.info("Game stopped") diff --git a/kennel/engine/systems/__init__.py b/kennel/engine/systems/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py new file mode 100644 index 0000000..7df8088 --- /dev/null +++ b/kennel/engine/systems/network.py @@ -0,0 +1,88 @@ +from enum import Enum +from kennel.engine.entities.entity import EntityManager +from .system import System, SystemType +from abc import abstractmethod +import asyncio + + +class EventType(str, Enum): + ENTITY_BORN = "ENTITY_BORN" + ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" + ENTITY_DEATH = "ENTITY_DEATH" + + +class Event: + def __init__(self, event_type: EventType, data: dict): + self.event_type = event_type + self.data = data + + def __str__(self): + return f"Event({self.event_type}, {self.data})" + + def dict(self): + return {"event_type": self.event_type, "data": self.data} + + @staticmethod + def from_dict(data: dict): + return Event(EventType(data["event_type"]), data["data"]) + + +class Publishable: + @abstractmethod + async def publish(self, event: Event): + pass + + +class ClientEventTransformer: + @abstractmethod + def apply(self, event: Event) -> Event: + pass + + +class EventProcessor: + @abstractmethod + def accept(self, entity_manager: EntityManager, event: Event) -> None: + pass + + +class NetworkSystem(System): + def __init__( + self, + event_processor: EventProcessor, + client_event_transformer: ClientEventTransformer, + ): + super().__init__(SystemType.NETWORK) + self.event_processor = event_processor + self.client_event_transformer = client_event_transformer + + self.events = [] + self.clients = {} + + async def update(self, entity_manager: EntityManager, delta_time: float) -> None: + for event in self.events: + self.event_processor.accept(entity_manager, event) + client_events = [ + self.client_event_transformer.apply(entity_manager, event) + for event in self.events + ] + await asyncio.gather( + *[ + client.publish(event) + for client in self.clients.values() + for event in client_events + ] + ) + self.events = [] + + def client_event(self, client_id: str, event: Event) -> None: + event.data["client_id"] = client_id + self.events.append(event) + + def add_event(self, event: Event) -> None: + self.events.append(event) + + def add_client(self, client_id: str, client: Publishable) -> None: + self.clients[client_id] = client + + def remove_client(self, client_id: str) -> None: + del self.clients[client_id] diff --git a/kennel/engine/systems/system.py b/kennel/engine/systems/system.py new file mode 100644 index 0000000..0e51f3a --- /dev/null +++ b/kennel/engine/systems/system.py @@ -0,0 +1,31 @@ +from enum import Enum +from kennel.engine.entities.entity import EntityManager +from abc import abstractmethod + + +class SystemType(str, Enum): + NETWORK = "NETWORK" + + +class System: + def __init__(self, system_type: SystemType): + self.system_type = system_type + + @abstractmethod + async def update(self, entity_manager: EntityManager, delta_time: float): + pass + + +class SystemManager: + def __init__(self): + self.systems = {} + + def add_system(self, system: System) -> None: + self.systems[system.system_type] = system + + def get_system(self, system_type: SystemType) -> System: + return self.systems.get(system_type) + + async def update(self, entity_manager: EntityManager, delta_time: float) -> None: + for system in self.systems.values(): + await system.update(entity_manager, delta_time) diff --git a/kennel/kennel.py b/kennel/kennel.py new file mode 100644 index 0000000..0b38261 --- /dev/null +++ b/kennel/kennel.py @@ -0,0 +1,81 @@ +from kennel.engine.game import Game +from kennel.engine.components.component import ComponentType +from kennel.engine.entities.entity import EntityManager, Entity +from kennel.engine.entities.laser import Laser +from kennel.engine.systems.system import SystemManager +from kennel.engine.systems.network import ( + NetworkSystem, + EventProcessor, + ClientEventTransformer, + Event, + EventType, +) +from kennel.config import config +from .app import logger +from typing import List +import uuid + +entity_manager = EntityManager() +system_manager = SystemManager() + + +class KennelEventProcessor(EventProcessor): + def accept(self, entity_manager: EntityManager, event: Event) -> None: + if event.event_type == EventType.ENTITY_POSITION_UPDATE: + self._process_entity_position_update(entity_manager, event) + + def _process_entity_position_update( + self, entity_manager: EntityManager, event: Event + ) -> None: + entity = entity_manager.get_entity(event.data["entity_id"]) + if entity is None: + logger.error(f"Entity {event.data['entity_id']} does not exist") + return + if event.data["client_id"] is None: + logger.error("Client ID is required for position updates") + return + controllable = entity.get_component(ComponentType.CONTROLLABLE) + if controllable is None: + logger.error(f"Entity {entity} is not controllable") + return + if controllable.by != event.data["client_id"]: + logger.error( + f"Entity {entity} is not controllable by client {event.data['client_id']}" + ) + return + position = entity.get_component(ComponentType.POSITION) + if position is None: + logger.error(f"Entity {entity} has no position") + return + entity.add_component(position) + + +class KennelClientEventTransformer(ClientEventTransformer): + def apply(self, entity_manager: EntityManager, event: Event) -> Event: + if event.event_type == EventType.ENTITY_BORN: + id = event.data["id"] + entity = entity_manager.get_entity(id) + if entity is None: + logger.error(f"Entity {id} does not exist") + return event + for component_type in entity.components: + if component_type == ComponentType.CONTROLLABLE: + # Do not send controllable components to clients + continue + component = entity.get_component(component_type) + if component is not None: + event.data[component_type] = component.dict() + + return event + + +system_manager.add_system( + NetworkSystem(EventProcessor(), KennelClientEventTransformer()) +) + +kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) + + +def create_session_controllable_entities(session: str) -> List[Entity]: + laser = Laser(uuid.uuid4().hex, session) + return [laser] diff --git a/kennel/kennelcats.py b/kennel/kennelcats.py new file mode 100644 index 0000000..340b07e --- /dev/null +++ b/kennel/kennelcats.py @@ -0,0 +1,45 @@ +from datetime import datetime +from typing import List +import requests + + +class KennelCat: + def __init__( + self, + id: str, + user_id: str, + name: str, + link: str, + description: str, + spritesheet: str, + created_at: datetime, + ): + self.id = id + self.user_id = user_id + self.name = name + self.link = link + self.description = description + self.spritesheet = spritesheet + self.created_at = created_at + + @staticmethod + def from_dict(dict: dict) -> "KennelCat": + return KennelCat( + id=dict["id"], + user_id=dict["user_id"], + name=dict["name"], + link=dict["link"], + description=dict["description"], + spritesheet=dict["spritesheet"], + created_at=datetime.fromisoformat(dict["created_at"]), + ) + + +class KennelCatService: + def __init__(self, endpoint: str): + self.endpoint = endpoint + + def get_kennel(self) -> List[KennelCat]: + response = requests.get(f"{self.endpoint}/kennel") + response.raise_for_status() + return [KennelCat.from_dict(cat) for cat in response.json()] diff --git a/kennel/main.py b/kennel/main.py index 183fb64..122e40a 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -1,57 +1,118 @@ +from fastapi import ( + FastAPI, + Request, + Response, + WebSocket, + WebSocketException, + status, + Cookie, + Depends, +) +from fastapi.staticfiles import StaticFiles +from kennel.engine.systems.system import SystemType +from kennel.engine.systems.network import Event, Publishable, EventType +from typing import Annotated, Optional +from .kennel import ( + kennel, + system_manager, + entity_manager, + create_session_controllable_entities, +) +from .app import app, templates, logger +from .kennelcats import KennelCatService +from .middleware import logger_middleware +from .config import config +import asyncio import uuid -import structlog -from fastapi import FastAPI, Request, Response -from fastapi.staticfiles import StaticFiles -from fastapi.templating import Jinja2Templates +app.mount("/static", StaticFiles(directory="static"), name="static") -app = FastAPI( - servers = [ - {"url": "https://kennel.hatecomputers.club", "description": "prod"} - ] -) -logger = structlog.get_logger() +loop = asyncio.get_event_loop() -@app.middleware("http") -async def logger_middleware(request: Request, call_next): - structlog.contextvars.clear_contextvars() - structlog.contextvars.bind_contextvars( - path=request.url.path, - method=request.method, - client_host=request.client.host, - request_id=str(uuid.uuid4()), - ) - response = await call_next(request) +@app.on_event("startup") +async def startup_event(): + logger.info("Starting Kennel...") + loop.create_task(kennel.run()) - structlog.contextvars.bind_contextvars( - status_code=response.status_code, - ) - # Exclude /healthcheck endpoint from producing logs - if request.url.path != "/healthcheck": - if 400 <= response.status_code < 500: - logger.warn("Client error") - elif response.status_code >= 500: - logger.error("Server error") - else: - logger.info("OK") +@app.on_event("shutdown") +async def shutdown_event(): + logger.info("Stopping Kennel...") + kennel.stop() + loop.stop() + logger.info("Kennel stopped") - return response -templates = Jinja2Templates(directory="templates") +@app.get("/") +async def index(request: Request): + return templates.TemplateResponse(request=request, name="index.html") + + +@app.get("/assign") +async def assign( + response: Response, + session: Annotated[Optional[str], Cookie()] = None, +): + if session is None: + session = str(uuid.uuid4().hex) + response.set_cookie(key="session", value=session, max_age=config.COOKIE_MAX_AGE) + return {"session": session} + + +async def get_cookie_or_token( + websocket: WebSocket, + session: Annotated[str | None, Cookie()] = None, +): + if session is None: + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) + return session + + +class WebSocketClient(Publishable): + def __init__(self, websocket: WebSocket): + self.websocket = websocket + + async def publish(self, event: Event): + await self.websocket.send_json(event.dict()) + + +@app.websocket("/ws") +async def websocket_endpoint( + websocket: WebSocket, + session: Annotated[str, Depends(get_cookie_or_token)], +): + await websocket.accept() + logger.info(f"Websocket connection established for session {session}") + + session_entities = create_session_controllable_entities(session) + try: + network_system = system_manager.get_system(SystemType.NETWORK) + if network_system is None: + raise "Network system not found" + + for entity in session_entities: + logger.info(f"Adding entity {entity.id} for session {session}") + entity_manager.add_entity(entity) + network_system.add_event(Event(EventType.ENTITY_BORN, {"id": entity.id})) + + network_system.add_client(session, WebSocketClient(websocket)) + while True: + message = await websocket.receive_json() + network_system.client_event(session, Event.from_dict(message)) + except Exception as e: + logger.error(f"WebSocket exception {e}") + finally: + logger.info("Websocket connection closed") + for entity in session_entities: + logger.info(f"Removing entity {entity.id} for session {session}") + entity_manager.remove_entity(entity.id) + network_system.add_event(Event(EventType.ENTITY_DEATH, {"id": entity.id})) + + network_system.remove_client(session) + await websocket.close() @app.get("/healthcheck") async def healthcheck(): - return Response("hello") - - -@app.get("/") -async def read_main(request: Request): - return templates.TemplateResponse( - request=request, name="index.html" - ) - -app.mount("/static", StaticFiles(directory = "static"), name = "static") - + return Response("healthy") diff --git a/kennel/middleware.py b/kennel/middleware.py new file mode 100644 index 0000000..dddf63b --- /dev/null +++ b/kennel/middleware.py @@ -0,0 +1,30 @@ +import uuid +import structlog +from fastapi import Request, Response +from .app import app, logger + +@app.middleware("http") +async def logger_middleware(request: Request, call_next): + structlog.contextvars.clear_contextvars() + structlog.contextvars.bind_contextvars( + path=request.url.path, + method=request.method, + client_host=request.client.host, + request_id=str(uuid.uuid4()), + ) + response = await call_next(request) + + structlog.contextvars.bind_contextvars( + status_code=response.status_code, + ) + + # Exclude /healthcheck endpoint from producing logs + if request.url.path != "/healthcheck": + if 400 <= response.status_code < 500: + logger.warn("Client error") + elif response.status_code >= 500: + logger.error("Server error") + else: + logger.info("OK") + + return response diff --git a/poetry.lock b/poetry.lock index e92d06f..e8edd19 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,24 +2,24 @@ [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {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.2.0" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] @@ -33,13 +33,112 @@ trio = ["trio (>=0.23)"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -69,63 +168,83 @@ files = [ [[package]] name = "coverage" -version = "7.3.4" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"}, - {file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"}, - {file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"}, - {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"}, - {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"}, - {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"}, - {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"}, - {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"}, - {file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"}, - {file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"}, - {file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"}, - {file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"}, - {file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"}, - {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"}, - {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"}, - {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"}, - {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"}, - {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"}, - {file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"}, - {file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"}, - {file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"}, - {file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"}, - {file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"}, - {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"}, - {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"}, - {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"}, - {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"}, - {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"}, - {file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"}, - {file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"}, - {file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"}, - {file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"}, - {file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"}, - {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"}, - {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"}, - {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"}, - {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"}, - {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"}, - {file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"}, - {file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"}, - {file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"}, - {file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"}, - {file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"}, - {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"}, - {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"}, - {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"}, - {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"}, - {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"}, - {file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"}, - {file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"}, - {file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"}, - {file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -163,13 +282,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.5" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, ] [package.dependencies] @@ -180,7 +299,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httptools" @@ -256,13 +375,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -364,24 +483,24 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -390,135 +509,122 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "2.5.3" +version = "2.8.2" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, - {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.14.6" -typing-extensions = ">=4.6.1" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.14.6" -description = "" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, - {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, - {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, - {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, - {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, - {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, - {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, - {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, - {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, - {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, - {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, - {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, - {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, - {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, - {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, - {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] [package.dependencies] @@ -526,13 +632,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -564,13 +670,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -578,99 +684,122 @@ cli = ["click (>=5.0)"] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[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 = "ruff" -version = "0.1.9" +version = "0.1.15" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, - {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, - {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, - {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, - {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, - {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -692,32 +821,49 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam [[package]] name = "structlog" -version = "23.2.0" +version = "23.3.0" description = "Structured Logging for Python" optional = false python-versions = ">=3.8" files = [ - {file = "structlog-23.2.0-py3-none-any.whl", hash = "sha256:16a167e87b9fa7fae9a972d5d12805ef90e04857a93eba479d4be3801a6a1482"}, - {file = "structlog-23.2.0.tar.gz", hash = "sha256:334666b94707f89dbc4c81a22a8ccd34449f0201d5b1ee097a030b577fa8c858"}, + {file = "structlog-23.3.0-py3-none-any.whl", hash = "sha256:d6922a88ceabef5b13b9eda9c4043624924f60edbb00397f4d193bd754cde60a"}, + {file = "structlog-23.3.0.tar.gz", hash = "sha256:24b42b914ac6bc4a4e6f716e82ac70d7fb1e8c3b1035a765591953bfc37101a5"}, ] [package.extras] dev = ["structlog[tests,typing]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] typing = ["mypy (>=1.4)", "rich", "twisted"] [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "uvicorn" version = "0.25.0" @@ -745,42 +891,42 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "uvloop" -version = "0.19.0" +version = "0.20.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, + {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996"}, + {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b"}, + {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10"}, + {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae"}, + {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006"}, + {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73"}, + {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037"}, + {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9"}, + {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e"}, + {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756"}, + {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0"}, + {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf"}, + {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d"}, + {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e"}, + {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9"}, + {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab"}, + {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5"}, + {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00"}, + {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba"}, + {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf"}, + {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847"}, + {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b"}, + {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6"}, + {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2"}, + {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95"}, + {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7"}, + {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a"}, + {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541"}, + {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315"}, + {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66"}, + {file = "uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469"}, ] [package.extras] @@ -789,86 +935,98 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)" [[package]] name = "watchfiles" -version = "0.21.0" +version = "0.23.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" files = [ - {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, - {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"}, - {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"}, - {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, - {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, - {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, - {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"}, - {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"}, - {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"}, - {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"}, - {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"}, - {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"}, - {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"}, - {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"}, - {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}, + {file = "watchfiles-0.23.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4"}, + {file = "watchfiles-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b"}, + {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f"}, + {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5"}, + {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e"}, + {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6"}, + {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1"}, + {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207"}, + {file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b"}, + {file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981"}, + {file = "watchfiles-0.23.0-cp310-none-win32.whl", hash = "sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42"}, + {file = "watchfiles-0.23.0-cp310-none-win_amd64.whl", hash = "sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75"}, + {file = "watchfiles-0.23.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab"}, + {file = "watchfiles-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3"}, + {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4"}, + {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf"}, + {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a"}, + {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8"}, + {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788"}, + {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1"}, + {file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220"}, + {file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320"}, + {file = "watchfiles-0.23.0-cp311-none-win32.whl", hash = "sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b"}, + {file = "watchfiles-0.23.0-cp311-none-win_amd64.whl", hash = "sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e"}, + {file = "watchfiles-0.23.0-cp311-none-win_arm64.whl", hash = "sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304"}, + {file = "watchfiles-0.23.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5"}, + {file = "watchfiles-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8"}, + {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f"}, + {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77"}, + {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1"}, + {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b"}, + {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f"}, + {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0"}, + {file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c"}, + {file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581"}, + {file = "watchfiles-0.23.0-cp312-none-win32.whl", hash = "sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75"}, + {file = "watchfiles-0.23.0-cp312-none-win_amd64.whl", hash = "sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb"}, + {file = "watchfiles-0.23.0-cp312-none-win_arm64.whl", hash = "sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8"}, + {file = "watchfiles-0.23.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9"}, + {file = "watchfiles-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e"}, + {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217"}, + {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1"}, + {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1"}, + {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0"}, + {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d"}, + {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0"}, + {file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef"}, + {file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1"}, + {file = "watchfiles-0.23.0-cp313-none-win32.whl", hash = "sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b"}, + {file = "watchfiles-0.23.0-cp313-none-win_amd64.whl", hash = "sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff"}, + {file = "watchfiles-0.23.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:2dddc2487d33e92f8b6222b5fb74ae2cfde5e8e6c44e0248d24ec23befdc5366"}, + {file = "watchfiles-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e75695cc952e825fa3e0684a7f4a302f9128721f13eedd8dbd3af2ba450932b8"}, + {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2537ef60596511df79b91613a5bb499b63f46f01a11a81b0a2b0dedf645d0a9c"}, + {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20b423b58f5fdde704a226b598a2d78165fe29eb5621358fe57ea63f16f165c4"}, + {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b98732ec893975455708d6fc9a6daab527fc8bbe65be354a3861f8c450a632a4"}, + {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee1f5fcbf5bc33acc0be9dd31130bcba35d6d2302e4eceafafd7d9018c7755ab"}, + {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f195338a5a7b50a058522b39517c50238358d9ad8284fd92943643144c0c03"}, + {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524fcb8d59b0dbee2c9b32207084b67b2420f6431ed02c18bd191e6c575f5c48"}, + {file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0eff099a4df36afaa0eea7a913aa64dcf2cbd4e7a4f319a73012210af4d23810"}, + {file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a8323daae27ea290ba3350c70c836c0d2b0fb47897fa3b0ca6a5375b952b90d3"}, + {file = "watchfiles-0.23.0-cp38-none-win32.whl", hash = "sha256:aafea64a3ae698695975251f4254df2225e2624185a69534e7fe70581066bc1b"}, + {file = "watchfiles-0.23.0-cp38-none-win_amd64.whl", hash = "sha256:c846884b2e690ba62a51048a097acb6b5cd263d8bd91062cd6137e2880578472"}, + {file = "watchfiles-0.23.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a753993635eccf1ecb185dedcc69d220dab41804272f45e4aef0a67e790c3eb3"}, + {file = "watchfiles-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6bb91fa4d0b392f0f7e27c40981e46dda9eb0fbc84162c7fb478fe115944f491"}, + {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1f67312efa3902a8e8496bfa9824d3bec096ff83c4669ea555c6bdd213aa516"}, + {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ca6b71dcc50d320c88fb2d88ecd63924934a8abc1673683a242a7ca7d39e781"}, + {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aec5c29915caf08771d2507da3ac08e8de24a50f746eb1ed295584ba1820330"}, + {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1733b9bc2c8098c6bdb0ff7a3d7cb211753fecb7bd99bdd6df995621ee1a574b"}, + {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02ff5d7bd066c6a7673b17c8879cd8ee903078d184802a7ee851449c43521bdd"}, + {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e2de19801b0eaa4c5292a223effb7cfb43904cb742c5317a0ac686ed604765"}, + {file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8ada449e22198c31fb013ae7e9add887e8d2bd2335401abd3cbc55f8c5083647"}, + {file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3af1b05361e1cc497bf1be654a664750ae61f5739e4bb094a2be86ec8c6db9b6"}, + {file = "watchfiles-0.23.0-cp39-none-win32.whl", hash = "sha256:486bda18be5d25ab5d932699ceed918f68eb91f45d018b0343e3502e52866e5e"}, + {file = "watchfiles-0.23.0-cp39-none-win_amd64.whl", hash = "sha256:d2d42254b189a346249424fb9bb39182a19289a2409051ee432fb2926bad966a"}, + {file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe"}, + {file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b"}, + {file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed"}, + {file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee"}, + {file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aba037c1310dd108411d27b3d5815998ef0e83573e47d4219f45753c710f969f"}, + {file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a96ac14e184aa86dc43b8a22bb53854760a58b2966c2b41580de938e9bf26ed0"}, + {file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11698bb2ea5e991d10f1f4f83a39a02f91e44e4bd05f01b5c1ec04c9342bf63c"}, + {file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efadd40fca3a04063d40c4448c9303ce24dd6151dc162cfae4a2a060232ebdcb"}, + {file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:556347b0abb4224c5ec688fc58214162e92a500323f50182f994f3ad33385dcb"}, + {file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1cf7f486169986c4b9d34087f08ce56a35126600b6fef3028f19ca16d5889071"}, + {file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18de0f82c62c4197bea5ecf4389288ac755896aac734bd2cc44004c56e4ac47"}, + {file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:532e1f2c491274d1333a814e4c5c2e8b92345d41b12dc806cf07aaff786beb66"}, + {file = "watchfiles-0.23.0.tar.gz", hash = "sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b"}, ] [package.dependencies] @@ -958,4 +1116,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "831fb7a29d53803e194165cf76a916732351760bc9281cd43df4ec73e0307739" +content-hash = "70e11aa5a88ef265d9d5adaf31cfc22a8e422ec11d636cfff027d609d4ff57e8" diff --git a/pyproject.toml b/pyproject.toml index 369a432..9b83c4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,9 @@ fastapi = "^0.108.0" structlog = "^23.2.0" uvicorn = { extras = ["standard"], version = "^0.25.0" } jinja2 = "^3.1.4" +python-dotenv = "^1.0.1" +websockets = "^12.0" +requests = "^2.32.3" [tool.poetry.group.dev.dependencies] ruff = "^0.1.9" diff --git a/static/index.js b/static/index.js index 2c8ed38..ad39c4f 100644 --- a/static/index.js +++ b/static/index.js @@ -1,5 +1,12 @@ window.onload = () => { - console.log('from js'); - const kennelWindowEle = document.querySelector('#kennel-window'); - kennelWindowEle.innerHTML = 'rendered from static/index.js'; -} + const ws = new WebSocket("/ws"); + ws.onopen = () => { + console.log("connected"); + }; + ws.onmessage = (e) => { + console.log(e); + }; + ws.onclose = (e) => { + console.log("disconnected", e); + }; +}; -- 2.40.1 From 6f374aac7f4d553c484eecc77c9599be79f2d834 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Tue, 20 Aug 2024 18:17:33 -0700 Subject: [PATCH 02/13] send the initial state to clients --- kennel/config.py | 4 ++-- kennel/engine/components/controllable.py | 3 ++- kennel/engine/entities/entity.py | 10 ++++++++++ kennel/engine/systems/network.py | 1 + kennel/engine/systems/system.py | 1 + kennel/engine/systems/world.py | 24 ++++++++++++++++++++++++ kennel/kennel.py | 7 +++---- kennel/main.py | 20 +++++++++++++++----- templates/index.html | 2 +- 9 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 kennel/engine/systems/world.py diff --git a/kennel/config.py b/kennel/config.py index 8cec699..4452044 100644 --- a/kennel/config.py +++ b/kennel/config.py @@ -5,8 +5,8 @@ load_dotenv() class Config: - SIMULATION_WIDTH = int(os.getenv("SIMULATION_WIDTH", 1000)) - SIMULATION_HEIGHT = int(os.getenv("SIMULATION_HEIGHT", 1000)) + WORLD_WIDTH = int(os.getenv("WORLD_WIDTH", 1000)) + WORLD_HEIGHT = int(os.getenv("WORLD_HEIGHT", 1000)) HATECOMPUTERS_ENDPOINT = os.getenv( "HATECOMPUTERS_ENDPOINT", "https://hatecomputers.club" diff --git a/kennel/engine/components/controllable.py b/kennel/engine/components/controllable.py index 49b0557..25ec835 100644 --- a/kennel/engine/components/controllable.py +++ b/kennel/engine/components/controllable.py @@ -10,4 +10,5 @@ class Controllable(Component): return f"Controllable(by={self.by})" def dict(self) -> dict: - return {"by": self.by} + # don't serialize who owns this + return {} diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py index 0ca8666..b72e996 100644 --- a/kennel/engine/entities/entity.py +++ b/kennel/engine/entities/entity.py @@ -23,6 +23,13 @@ class Entity: def add_component(self, component: Component) -> None: self.components[component.component_type] = component + def to_dict(self) -> dict: + return { + "entity_type": self.entity_type, + "id": self.id, + "components": {k: v.dict() for k, v in self.components.items()}, + } + class EntityManager: def __init__(self): @@ -58,3 +65,6 @@ class EntityManager: def get_entity(self, entity_id: str) -> Optional[Entity]: return self.entities.get(entity_id) + + def to_dict(self) -> dict: + return {k: v.to_dict() for k, v in self.entities.items()} diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py index 7df8088..c9696c8 100644 --- a/kennel/engine/systems/network.py +++ b/kennel/engine/systems/network.py @@ -6,6 +6,7 @@ import asyncio class EventType(str, Enum): + INITIAL_STATE = "INITIAL_STATE" ENTITY_BORN = "ENTITY_BORN" ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" ENTITY_DEATH = "ENTITY_DEATH" diff --git a/kennel/engine/systems/system.py b/kennel/engine/systems/system.py index 0e51f3a..78bda58 100644 --- a/kennel/engine/systems/system.py +++ b/kennel/engine/systems/system.py @@ -5,6 +5,7 @@ from abc import abstractmethod class SystemType(str, Enum): NETWORK = "NETWORK" + WORLD = "WORLD" class System: diff --git a/kennel/engine/systems/world.py b/kennel/engine/systems/world.py new file mode 100644 index 0000000..396e7cb --- /dev/null +++ b/kennel/engine/systems/world.py @@ -0,0 +1,24 @@ +from kennel.engine.systems.system import System, SystemType +from kennel.engine.entities.entity import EntityManager +from kennel.engine.components.component import ComponentType +from kennel.app import logger + + +class WorldSystem(System): + def __init__(self, width: int, height: int): + super().__init__(SystemType.WORLD) + self.width = width + self.height = height + + async def update(self, entity_manager: EntityManager, delta_time: float): + entities = entity_manager.get_entities_with_component(ComponentType.POSITION) + for entity in entities: + position = entity.get_component(ComponentType.POSITION) + if position is None: + logger.error(f"Entity {entity} has no position component") + continue + + position.x = max(0, min(self.width, position.x)) + position.y = max(0, min(self.height, position.y)) + + entity.add_component(position) diff --git a/kennel/kennel.py b/kennel/kennel.py index 0b38261..61847a5 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -10,6 +10,7 @@ from kennel.engine.systems.network import ( Event, EventType, ) +from kennel.engine.systems.world import WorldSystem from kennel.config import config from .app import logger from typing import List @@ -59,9 +60,6 @@ class KennelClientEventTransformer(ClientEventTransformer): logger.error(f"Entity {id} does not exist") return event for component_type in entity.components: - if component_type == ComponentType.CONTROLLABLE: - # Do not send controllable components to clients - continue component = entity.get_component(component_type) if component is not None: event.data[component_type] = component.dict() @@ -69,8 +67,9 @@ class KennelClientEventTransformer(ClientEventTransformer): return event +system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) system_manager.add_system( - NetworkSystem(EventProcessor(), KennelClientEventTransformer()) + NetworkSystem(EventProcessor(), KennelClientEventTransformer()), ) kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) diff --git a/kennel/main.py b/kennel/main.py index 122e40a..17a143b 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -12,16 +12,16 @@ from fastapi.staticfiles import StaticFiles from kennel.engine.systems.system import SystemType from kennel.engine.systems.network import Event, Publishable, EventType from typing import Annotated, Optional -from .kennel import ( +from kennel.kennel import ( kennel, system_manager, entity_manager, create_session_controllable_entities, ) -from .app import app, templates, logger -from .kennelcats import KennelCatService -from .middleware import logger_middleware -from .config import config +from kennel.app import app, templates, logger +from kennel.kennelcats import KennelCatService +from kennel.middleware import logger_middleware +from kennel.config import config import asyncio import uuid @@ -85,6 +85,16 @@ async def websocket_endpoint( await websocket.accept() logger.info(f"Websocket connection established for session {session}") + await websocket.send_json( + { + "event_type": EventType.INITIAL_STATE, + "data": { + "world": {"width": config.WORLD_WIDTH, "height": config.WORLD_HEIGHT}, + "entities": kennel.entity_manager.to_dict(), + }, + } + ) + session_entities = create_session_controllable_entities(session) try: network_system = system_manager.get_system(SystemType.NETWORK) diff --git a/templates/index.html b/templates/index.html index 1bb53e8..07acb30 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,10 +5,10 @@ Kennel Club +
- -- 2.40.1 From 5dc264e08253c3a97ea41d1fde5fbf3f33dd6591 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 24 Aug 2024 13:03:50 -0700 Subject: [PATCH 03/13] bring in jQuery, build with Vite --- kennel/app.py | 7 +- kennel/main.py | 8 +- poetry.lock | 96 +-- pyproject.toml | 1 - static/.gitignore | 24 + static/index.html | 15 + static/index.js | 12 - static/package-lock.json | 1594 ++++++++++++++++++++++++++++++++++++++ static/package.json | 23 + static/public/vite.svg | 1 + static/src/main.ts | 18 + static/src/style.css | 3 + static/src/vite-env.d.ts | 1 + static/tsconfig.json | 23 + static/vite.config.ts | 27 + templates/index.html | 14 - 16 files changed, 1740 insertions(+), 127 deletions(-) create mode 100644 static/.gitignore create mode 100644 static/index.html delete mode 100644 static/index.js create mode 100644 static/package-lock.json create mode 100644 static/package.json create mode 100644 static/public/vite.svg create mode 100644 static/src/main.ts create mode 100644 static/src/style.css create mode 100644 static/src/vite-env.d.ts create mode 100644 static/tsconfig.json create mode 100644 static/vite.config.ts delete mode 100644 templates/index.html diff --git a/kennel/app.py b/kennel/app.py index 98a0448..429aa1a 100644 --- a/kennel/app.py +++ b/kennel/app.py @@ -1,13 +1,8 @@ import structlog from fastapi import FastAPI -from fastapi.templating import Jinja2Templates app = FastAPI( - servers = [ - {"url": "https://kennel.hatecomputers.club", "description": "prod"} - ] + servers=[{"url": "https://kennel.hatecomputers.club", "description": "prod"}] ) logger = structlog.get_logger() - -templates = Jinja2Templates(directory="templates") diff --git a/kennel/main.py b/kennel/main.py index 17a143b..6f661cb 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -8,6 +8,7 @@ from fastapi import ( Cookie, Depends, ) +from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from kennel.engine.systems.system import SystemType from kennel.engine.systems.network import Event, Publishable, EventType @@ -18,14 +19,15 @@ from kennel.kennel import ( entity_manager, create_session_controllable_entities, ) -from kennel.app import app, templates, logger +from kennel.app import app, logger from kennel.kennelcats import KennelCatService from kennel.middleware import logger_middleware from kennel.config import config import asyncio import uuid -app.mount("/static", StaticFiles(directory="static"), name="static") +app.mount("/static", StaticFiles(directory="static/dist"), name="static") + loop = asyncio.get_event_loop() @@ -46,7 +48,7 @@ async def shutdown_event(): @app.get("/") async def index(request: Request): - return templates.TemplateResponse(request=request, name="index.html") + return FileResponse("static/dist/index.html", media_type="text/html") @app.get("/assign") diff --git a/poetry.lock b/poetry.lock index e8edd19..bbe8c53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -375,13 +375,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] @@ -395,92 +395,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - [[package]] name = "packaging" version = "24.1" @@ -1116,4 +1030,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "70e11aa5a88ef265d9d5adaf31cfc22a8e422ec11d636cfff027d609d4ff57e8" +content-hash = "a4fd1897aa81d377f838e0b8fdd273781f385d8a33c12a2c043e30661f67dc21" diff --git a/pyproject.toml b/pyproject.toml index 9b83c4c..fd976b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ python = "^3.12" fastapi = "^0.108.0" structlog = "^23.2.0" uvicorn = { extras = ["standard"], version = "^0.25.0" } -jinja2 = "^3.1.4" python-dotenv = "^1.0.1" websockets = "^12.0" requests = "^2.32.3" diff --git a/static/.gitignore b/static/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/static/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..3770eaa --- /dev/null +++ b/static/index.html @@ -0,0 +1,15 @@ + + + + + + + the kennel. + + +
+ + + diff --git a/static/index.js b/static/index.js deleted file mode 100644 index ad39c4f..0000000 --- a/static/index.js +++ /dev/null @@ -1,12 +0,0 @@ -window.onload = () => { - const ws = new WebSocket("/ws"); - ws.onopen = () => { - console.log("connected"); - }; - ws.onmessage = (e) => { - console.log(e); - }; - ws.onclose = (e) => { - console.log("disconnected", e); - }; -}; diff --git a/static/package-lock.json b/static/package-lock.json new file mode 100644 index 0000000..e85f7a7 --- /dev/null +++ b/static/package-lock.json @@ -0,0 +1,1594 @@ +{ + "name": "kennel", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kennel", + "version": "0.1.0", + "dependencies": { + "jquery": "^3.7.1" + }, + "devDependencies": { + "@rollup/plugin-inject": "^5.0.5", + "@types/jquery": "^3.5.30", + "nodemon": "^3.1.4", + "typescript": "^5.5.3", + "vite": "^5.4.1", + "vite-plugin-dynamic-base": "^1.1.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", + "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", + "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", + "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", + "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", + "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", + "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", + "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", + "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", + "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", + "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", + "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", + "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", + "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", + "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", + "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", + "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", + "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.18.tgz", + "integrity": "sha512-qL9v5N5S38ijmqiQRvCFUUx2vmxWT/JJ2rswElnyaHkOHuVoAFhBB90Ywj4RKjh3R0zOjhEcemENTyF3q3G6WQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.12" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.7.18", + "@swc/core-darwin-x64": "1.7.18", + "@swc/core-linux-arm-gnueabihf": "1.7.18", + "@swc/core-linux-arm64-gnu": "1.7.18", + "@swc/core-linux-arm64-musl": "1.7.18", + "@swc/core-linux-x64-gnu": "1.7.18", + "@swc/core-linux-x64-musl": "1.7.18", + "@swc/core-win32-arm64-msvc": "1.7.18", + "@swc/core-win32-ia32-msvc": "1.7.18", + "@swc/core-win32-x64-msvc": "1.7.18" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.18.tgz", + "integrity": "sha512-MwLc5U+VGPMZm8MjlFBjEB2wyT1EK0NNJ3tn+ps9fmxdFP+PL8EpMiY1O1F2t1ydy2OzBtZz81sycjM9RieFBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.18.tgz", + "integrity": "sha512-IkukOQUw7/14VkHp446OkYGCZEHqZg9pTmTdBawlUyz2JwZMSn2VodCl7aFSdGCsU4Cwni8zKA8CCgkCCAELhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.18.tgz", + "integrity": "sha512-ATnb6jJaBeXCqrTUawWdoOy7eP9SCI7UMcfXlYIMxX4otKKspLPAEuGA5RaNxlCcj9ObyO0J3YGbtZ6hhD2pjg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.18.tgz", + "integrity": "sha512-poHtH7zL7lEp9K2inY90lGHJABWxURAOgWNeZqrcR5+jwIe7q5KBisysH09Zf/JNF9+6iNns+U0xgWTNJzBuGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.18.tgz", + "integrity": "sha512-qnNI1WmcOV7Wz1ZDyK6WrOlzLvJ01rnni8ec950mMHWkLRMP53QvCvhF3S+7gFplWBwWJTOOPPUqJp/PlSxWyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.18.tgz", + "integrity": "sha512-x9SCqCLzwtlqtD5At3I1a7Gco+EuXnzrJGoucmkpeQohshHuwa+cskqsXO6u1Dz0jXJEuHbBZB9va1wYYfjgFg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.18.tgz", + "integrity": "sha512-qtj8iOpMMgKjzxTv+islmEY0JBsbd93nka0gzcTTmGZxKtL5jSUsYQvkxwNPZr5M9NU1fgaR3n1vE6lFmtY0IQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.18.tgz", + "integrity": "sha512-ltX/Ol9+Qu4SXmISCeuwVgAjSa8nzHTymknpozzVMgjXUoZMoz6lcynfKL1nCh5XLgqh0XNHUKLti5YFF8LrrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.18.tgz", + "integrity": "sha512-RgTcFP3wgyxnQbTCJrlgBJmgpeTXo8t807GU9GxApAXfpLZJ3swJ2GgFUmIJVdLWyffSHF5BEkF3FmF6mtH5AQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.18.tgz", + "integrity": "sha512-XbZ0wAgzR757+DhQcnv60Y/bK9yuWPhDNRQVFFQVRsowvK3+c6EblyfUSytIidpXgyYFzlprq/9A9ZlO/wvDWw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", + "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/jquery": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz", + "integrity": "sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==", + "dev": true, + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-html-parser": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", + "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", + "dev": true, + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rollup": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", + "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.0", + "@rollup/rollup-android-arm64": "4.21.0", + "@rollup/rollup-darwin-arm64": "4.21.0", + "@rollup/rollup-darwin-x64": "4.21.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", + "@rollup/rollup-linux-arm-musleabihf": "4.21.0", + "@rollup/rollup-linux-arm64-gnu": "4.21.0", + "@rollup/rollup-linux-arm64-musl": "4.21.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", + "@rollup/rollup-linux-riscv64-gnu": "4.21.0", + "@rollup/rollup-linux-s390x-gnu": "4.21.0", + "@rollup/rollup-linux-x64-gnu": "4.21.0", + "@rollup/rollup-linux-x64-musl": "4.21.0", + "@rollup/rollup-win32-arm64-msvc": "4.21.0", + "@rollup/rollup-win32-ia32-msvc": "4.21.0", + "@rollup/rollup-win32-x64-msvc": "4.21.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/vite": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", + "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-dynamic-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-dynamic-base/-/vite-plugin-dynamic-base-1.1.0.tgz", + "integrity": "sha512-/hIOTuzedlPRiUTCIY1cJx7UDDUF0brxWIifE7xG1czCJy0Aw6k3QPbHVjPCs30YuU+WAgrX4ACOySLIp2NHkw==", + "dev": true, + "dependencies": { + "@swc/core": "^1.3.61", + "node-html-parser": "^5.3.3" + }, + "peerDependencies": { + "vite": ">= 2.9.5" + } + } + } +} diff --git a/static/package.json b/static/package.json new file mode 100644 index 0000000..1c698e0 --- /dev/null +++ b/static/package.json @@ -0,0 +1,23 @@ +{ + "name": "kennel", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "watch": "./node_modules/nodemon/bin/nodemon.js --watch './src/**/*' -e ts,html --exec \"npm run build\"" + }, + "devDependencies": { + "@rollup/plugin-inject": "^5.0.5", + "@types/jquery": "^3.5.30", + "nodemon": "^3.1.4", + "typescript": "^5.5.3", + "vite": "^5.4.1", + "vite-plugin-dynamic-base": "^1.1.0" + }, + "dependencies": { + "jquery": "^3.7.1" + } +} diff --git a/static/public/vite.svg b/static/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/static/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/src/main.ts b/static/src/main.ts new file mode 100644 index 0000000..8d12587 --- /dev/null +++ b/static/src/main.ts @@ -0,0 +1,18 @@ +import $ from "jquery"; + +$(document).ready(async () => { + await fetch("/assign", { + credentials: "include", + }); + + const ws = new WebSocket("/ws"); + ws.onopen = () => { + console.log("connected"); + }; + ws.onmessage = ({ data }) => { + console.log(JSON.parse(data)); + }; + ws.onclose = (e) => { + console.log("disconnected", e); + }; +}); diff --git a/static/src/style.css b/static/src/style.css new file mode 100644 index 0000000..adc68fa --- /dev/null +++ b/static/src/style.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} diff --git a/static/src/vite-env.d.ts b/static/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/static/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/static/tsconfig.json b/static/tsconfig.json new file mode 100644 index 0000000..0511b9f --- /dev/null +++ b/static/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/static/vite.config.ts b/static/vite.config.ts new file mode 100644 index 0000000..0582da4 --- /dev/null +++ b/static/vite.config.ts @@ -0,0 +1,27 @@ +import { fileURLToPath, URL } from "node:url"; +import { dynamicBase } from "vite-plugin-dynamic-base"; +import { defineConfig } from "vite"; +import inject from "@rollup/plugin-inject"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + dynamicBase({ + // dynamic public path var string, default window.__dynamic_base__ + publicPath: "", + // dynamic load resources on index.html, default false. maybe change default true + transformIndexHtml: false, + }), + inject({ + // => that should be first under plugins array + $: "jquery", + jQuery: "jquery", + }), + ], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + base: "/static/", +}); diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 07acb30..0000000 --- a/templates/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Kennel Club - - - -
- - - -- 2.40.1 From b144ad5df76e458297bd6dc030cad4af0f344901 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 24 Aug 2024 13:04:21 -0700 Subject: [PATCH 04/13] fix remove_entity method --- kennel/engine/entities/entity.py | 12 +++++++----- kennel/engine/game.py | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py index b72e996..6623512 100644 --- a/kennel/engine/entities/entity.py +++ b/kennel/engine/entities/entity.py @@ -57,11 +57,13 @@ class EntityManager: self.component_entities[component.component_type].add(entity) def remove_entity(self, entity_id: str) -> None: - if entity_id in self.entities: - entity = self.entities[entity_id] - for component in entity.components.values(): - if component.component_type in self.component_entities: - self.component_entities[component.component_type].remove(entity) + if entity_id not in self.entities: + return + entity = self.entities[entity_id] + for component in entity.components.values(): + if component.component_type in self.component_entities: + self.component_entities[component.component_type].remove(entity) + del self.entities[entity_id] def get_entity(self, entity_id: str) -> Optional[Entity]: return self.entities.get(entity_id) diff --git a/kennel/engine/game.py b/kennel/engine/game.py index 2236306..f3134be 100644 --- a/kennel/engine/game.py +++ b/kennel/engine/game.py @@ -42,9 +42,9 @@ class Game: self.last_time = current_time await self.update(delta_time) - await asyncio.sleep( - max(self.min_time_step - (time.time() - current_time), 0) - ) + sleep_time = max(self.min_time_step - (time.time() - current_time), 0) + if sleep_time > 0: + await asyncio.sleep(sleep_time) steps_since_log += 1 -- 2.40.1 From a0a2068b66204d7d06eb3c3b4aa61b28469121f6 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 24 Aug 2024 19:11:26 -0700 Subject: [PATCH 05/13] improve event parsing and begin sending events from the client --- kennel/engine/components/component.py | 2 +- kennel/engine/components/controllable.py | 2 +- kennel/engine/components/position.py | 2 +- kennel/engine/entities/entity.py | 2 +- kennel/engine/systems/network.py | 104 +++++++++++++++++------ kennel/kennel.py | 50 +++++------ kennel/main.py | 42 ++++++--- static/src/component.ts | 8 ++ static/src/entity.ts | 11 +++ static/src/event.ts | 28 ++++++ static/src/main.ts | 47 ++++++++-- static/src/mouse_controller.ts | 54 ++++++++++++ static/src/vector.ts | 10 +++ static/tsconfig.json | 4 +- 14 files changed, 284 insertions(+), 82 deletions(-) create mode 100644 static/src/component.ts create mode 100644 static/src/entity.ts create mode 100644 static/src/event.ts create mode 100644 static/src/mouse_controller.ts create mode 100644 static/src/vector.ts diff --git a/kennel/engine/components/component.py b/kennel/engine/components/component.py index c8ef2d0..da55138 100644 --- a/kennel/engine/components/component.py +++ b/kennel/engine/components/component.py @@ -12,5 +12,5 @@ class Component: self.component_type = component_type @abstractmethod - def dict(self) -> dict: + def to_dict(self) -> dict: pass diff --git a/kennel/engine/components/controllable.py b/kennel/engine/components/controllable.py index 25ec835..a9173af 100644 --- a/kennel/engine/components/controllable.py +++ b/kennel/engine/components/controllable.py @@ -9,6 +9,6 @@ class Controllable(Component): def __repr__(self) -> str: return f"Controllable(by={self.by})" - def dict(self) -> dict: + def to_dict(self) -> dict: # don't serialize who owns this return {} diff --git a/kennel/engine/components/position.py b/kennel/engine/components/position.py index 2a4da4a..5c5b372 100644 --- a/kennel/engine/components/position.py +++ b/kennel/engine/components/position.py @@ -10,5 +10,5 @@ class Position(Component): def __repr__(self) -> str: return f"Position(x={self.x}, y={self.y})" - def dict(self) -> dict: + def to_dict(self) -> dict: return {"x": self.x, "y": self.y} diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py index 6623512..1d8c34d 100644 --- a/kennel/engine/entities/entity.py +++ b/kennel/engine/entities/entity.py @@ -27,7 +27,7 @@ class Entity: return { "entity_type": self.entity_type, "id": self.id, - "components": {k: v.dict() for k, v in self.components.items()}, + "components": {k: v.to_dict() for k, v in self.components.items()}, } diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py index c9696c8..8dbf4cf 100644 --- a/kennel/engine/systems/network.py +++ b/kennel/engine/systems/network.py @@ -1,15 +1,17 @@ from enum import Enum -from kennel.engine.entities.entity import EntityManager +from kennel.engine.entities.entity import Entity, EntityType, EntityManager from .system import System, SystemType from abc import abstractmethod +from typing import Optional, List import asyncio class EventType(str, Enum): INITIAL_STATE = "INITIAL_STATE" + SET_CONTROLLABLE = "SET_CONTROLLABLE" ENTITY_BORN = "ENTITY_BORN" - ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" ENTITY_DEATH = "ENTITY_DEATH" + ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE" class Event: @@ -17,29 +19,86 @@ class Event: self.event_type = event_type self.data = data - def __str__(self): + def __str__(self) -> str: return f"Event({self.event_type}, {self.data})" - def dict(self): + def to_dict(self) -> dict: return {"event_type": self.event_type, "data": self.data} @staticmethod - def from_dict(data: dict): + def from_dict(data: dict) -> Optional["Event"]: + if "event_type" not in data or "data" not in data: + return + event_type = data["event_type"] + if event_type == EventType.INITIAL_STATE: + return InitialStateEvent( + data["data"]["world"]["width"], + data["data"]["world"]["height"], + data["data"]["entities"], + ) + if event_type == EventType.SET_CONTROLLABLE: + return SetControllableEvent(data["data"]["id"], data["data"]["client_id"]) + if event_type == EventType.ENTITY_BORN: + return EntityBornEvent(data["data"]) + if event_type == EventType.ENTITY_POSITION_UPDATE: + return EntityPositionUpdateEvent( + data["data"]["id"], data["data"]["position"] + ) + if event_type == EventType.ENTITY_DEATH: + return EntityDeathEvent(data["data"]["id"]) + + logger.warn(f"Unknown event type: {data['event_type']}") return Event(EventType(data["event_type"]), data["data"]) +class InitialStateEvent(Event): + def __init__(self, world_width: int, world_height: int, entities: List[Entity]): + self.world_width = world_width + self.world_height = world_height + self.entities = entities + super().__init__( + EventType.INITIAL_STATE, + { + "world": {"width": world_width, "height": world_height}, + "entities": [entity.to_dict() for entity in entities], + }, + ) + + +class SetControllableEvent(Event): + def __init__(self, entity_id: str, client_id: str): + self.entity_id = entity_id + self.client_id = client_id + super().__init__( + EventType.SET_CONTROLLABLE, {"id": entity_id, "client_id": client_id} + ) + + +class EntityBornEvent(Event): + def __init__(self, entity: Entity): + self.entity = entity + super().__init__(EventType.ENTITY_BORN, {"entity": entity.to_dict()}) + + +class EntityPositionUpdateEvent(Event): + def __init__(self, entity_id: str, position: dict): + super().__init__( + EventType.ENTITY_POSITION_UPDATE, + {"id": entity_id, "position": position}, + ) + + +class EntityDeathEvent(Event): + def __init__(self, entity_id: str): + super().__init__(EventType.ENTITY_DEATH, {"id": entity_id}) + + class Publishable: @abstractmethod async def publish(self, event: Event): pass -class ClientEventTransformer: - @abstractmethod - def apply(self, event: Event) -> Event: - pass - - class EventProcessor: @abstractmethod def accept(self, entity_manager: EntityManager, event: Event) -> None: @@ -47,37 +106,32 @@ class EventProcessor: class NetworkSystem(System): - def __init__( - self, - event_processor: EventProcessor, - client_event_transformer: ClientEventTransformer, - ): + def __init__(self, event_processor: EventProcessor): super().__init__(SystemType.NETWORK) self.event_processor = event_processor - self.client_event_transformer = client_event_transformer self.events = [] + self.client_events = [] self.clients = {} async def update(self, entity_manager: EntityManager, delta_time: float) -> None: - for event in self.events: + if len(self.events) + len(self.client_events) == 0: + return + for event in self.events + self.client_events: self.event_processor.accept(entity_manager, event) - client_events = [ - self.client_event_transformer.apply(entity_manager, event) - for event in self.events - ] + client_sendable = self.events + [event for event, _ in self.client_events] await asyncio.gather( *[ client.publish(event) for client in self.clients.values() - for event in client_events + for event in client_sendable ] ) self.events = [] + self.client_events = [] def client_event(self, client_id: str, event: Event) -> None: - event.data["client_id"] = client_id - self.events.append(event) + self.client_events.append((event, client_id)) def add_event(self, event: Event) -> None: self.events.append(event) diff --git a/kennel/kennel.py b/kennel/kennel.py index 61847a5..bb0438c 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -6,7 +6,6 @@ from kennel.engine.systems.system import SystemManager from kennel.engine.systems.network import ( NetworkSystem, EventProcessor, - ClientEventTransformer, Event, EventType, ) @@ -21,28 +20,33 @@ system_manager = SystemManager() class KennelEventProcessor(EventProcessor): - def accept(self, entity_manager: EntityManager, event: Event) -> None: + def accept( + self, entity_manager: EntityManager, event: type[Event | tuple[Event, str]] + ) -> None: + if isinstance(event, tuple): + client_event, client_id = event + self._process_client_event(entity_manager, client_event, client_id) + return + + def _process_client_event( + self, entity_manager: EntityManager, event: Event, client_id: str + ) -> None: if event.event_type == EventType.ENTITY_POSITION_UPDATE: - self._process_entity_position_update(entity_manager, event) + self._process_entity_position_update(entity_manager, event, client_id) def _process_entity_position_update( - self, entity_manager: EntityManager, event: Event + self, entity_manager: EntityManager, event: Event, client_id: str ) -> None: - entity = entity_manager.get_entity(event.data["entity_id"]) + entity = entity_manager.get_entity(event.data["id"]) if entity is None: - logger.error(f"Entity {event.data['entity_id']} does not exist") - return - if event.data["client_id"] is None: - logger.error("Client ID is required for position updates") + logger.error(f"Entity(id={event.data['id']}) does not exist") return controllable = entity.get_component(ComponentType.CONTROLLABLE) if controllable is None: logger.error(f"Entity {entity} is not controllable") return - if controllable.by != event.data["client_id"]: - logger.error( - f"Entity {entity} is not controllable by client {event.data['client_id']}" - ) + if controllable.by != client_id: + logger.error(f"Entity {entity} is not controllable by client {client_id}") return position = entity.get_component(ComponentType.POSITION) if position is None: @@ -51,30 +55,14 @@ class KennelEventProcessor(EventProcessor): entity.add_component(position) -class KennelClientEventTransformer(ClientEventTransformer): - def apply(self, entity_manager: EntityManager, event: Event) -> Event: - if event.event_type == EventType.ENTITY_BORN: - id = event.data["id"] - entity = entity_manager.get_entity(id) - if entity is None: - logger.error(f"Entity {id} does not exist") - return event - for component_type in entity.components: - component = entity.get_component(component_type) - if component is not None: - event.data[component_type] = component.dict() - - return event - - system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) system_manager.add_system( - NetworkSystem(EventProcessor(), KennelClientEventTransformer()), + NetworkSystem(KennelEventProcessor()), ) kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) def create_session_controllable_entities(session: str) -> List[Entity]: - laser = Laser(uuid.uuid4().hex, session) + laser = Laser(uuid.uuid4().hex[:10], session) return [laser] diff --git a/kennel/main.py b/kennel/main.py index 6f661cb..fc0fd0e 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -11,7 +11,14 @@ from fastapi import ( from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from kennel.engine.systems.system import SystemType -from kennel.engine.systems.network import Event, Publishable, EventType +from kennel.engine.systems.network import ( + Event, + Publishable, + EventType, + InitialStateEvent, + SetControllableEvent, + EntityBornEvent, +) from typing import Annotated, Optional from kennel.kennel import ( kennel, @@ -76,7 +83,7 @@ class WebSocketClient(Publishable): self.websocket = websocket async def publish(self, event: Event): - await self.websocket.send_json(event.dict()) + await self.websocket.send_json([event.event_type, event.data]) @app.websocket("/ws") @@ -85,19 +92,16 @@ async def websocket_endpoint( session: Annotated[str, Depends(get_cookie_or_token)], ): await websocket.accept() + client = WebSocketClient(websocket) logger.info(f"Websocket connection established for session {session}") - await websocket.send_json( - { - "event_type": EventType.INITIAL_STATE, - "data": { - "world": {"width": config.WORLD_WIDTH, "height": config.WORLD_HEIGHT}, - "entities": kennel.entity_manager.to_dict(), - }, - } + initial_state = InitialStateEvent( + config.WORLD_WIDTH, config.WORLD_HEIGHT, kennel.entity_manager.to_dict() ) + await client.publish(initial_state) session_entities = create_session_controllable_entities(session) + logger.info(f"Creating {len(session_entities)} entities for session {session}") try: network_system = system_manager.get_system(SystemType.NETWORK) if network_system is None: @@ -106,14 +110,26 @@ async def websocket_endpoint( for entity in session_entities: logger.info(f"Adding entity {entity.id} for session {session}") entity_manager.add_entity(entity) - network_system.add_event(Event(EventType.ENTITY_BORN, {"id": entity.id})) + network_system.add_event(EntityBornEvent(entity)) + + set_controllable_event = SetControllableEvent(entity.id, session) + await client.publish(set_controllable_event) network_system.add_client(session, WebSocketClient(websocket)) while True: message = await websocket.receive_json() - network_system.client_event(session, Event.from_dict(message)) + if type(message) is not list: + message = [message] + events = [Event.from_dict(event) for event in message] + if not all([event is not None for event in events]): + logger.info(f"Invalid events in: {message}"[:100]) + continue + for event in events: + network_system.client_event(session, event) + except WebSocketDisconnect as e: + logger.info(f"Websocket connection closed by client: {session}") except Exception as e: - logger.error(f"WebSocket exception {e}") + logger.error("Exception occurred", exc_info=e) finally: logger.info("Websocket connection closed") for entity in session_entities: diff --git a/static/src/component.ts b/static/src/component.ts new file mode 100644 index 0000000..d338cad --- /dev/null +++ b/static/src/component.ts @@ -0,0 +1,8 @@ +export enum ComponentType { + POSITION = "POSITION", + RENDERABLE = "RENDERABLE", +} + +export interface Component { + name: string; +} diff --git a/static/src/entity.ts b/static/src/entity.ts new file mode 100644 index 0000000..811d05a --- /dev/null +++ b/static/src/entity.ts @@ -0,0 +1,11 @@ +import type { Component } from "./component"; + +export enum EntityType { + LASER = "LASER", +} + +export interface Entity { + entity_type: EntityType; + id: string; + components: Record; +} diff --git a/static/src/event.ts b/static/src/event.ts new file mode 100644 index 0000000..cd50122 --- /dev/null +++ b/static/src/event.ts @@ -0,0 +1,28 @@ +export enum EventType { + INITIAL_STATE = "INITIAL_STATE", + SET_CONTROLLABLE = "SET_CONTROLLABLE", + ENTITY_BORN = "ENTITY_BORN", + ENTITY_DEATH = "ENTITY_DEATH", + ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE", +} + +export interface Event { + event_type: EventType; + data: any; +} + +export interface InitialStateEvent extends Event { + event_type: EventType.INITIAL_STATE; + data: { + world: { width: number; height: number }; + entities: any[]; + }; +} + +export interface SetControllableEvent extends Event { + event_type: EventType.SET_CONTROLLABLE; + data: { + id: string; + client_id: string; + }; +} diff --git a/static/src/main.ts b/static/src/main.ts index 8d12587..fc04b72 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -1,18 +1,51 @@ import $ from "jquery"; +import { Vec2 } from "./vector"; +import { EventType, type SetControllableEvent, type Event } from "./event"; +import { MouseController } from "./mouse_controller"; $(document).ready(async () => { - await fetch("/assign", { + const session_id = await fetch("/assign", { credentials: "include", + }) + .then((res) => res.json()) + .then(({ session }) => session); + + const controllable_entities = new Set(); + const control_callback = (movement: Vec2) => { + for (const id of controllable_entities) { + const message = JSON.stringify({ + event_type: EventType.ENTITY_POSITION_UPDATE, + data: { id, position: movement }, + }); + ws.send(message); + } + }; + const mouse_controller = new MouseController(control_callback); + $(document).on("mousemove", (event) => { + mouse_controller.move(event.clientX, event.clientY); }); + mouse_controller.start(); const ws = new WebSocket("/ws"); - ws.onopen = () => { - console.log("connected"); - }; + await new Promise((resolve) => { + ws.onopen = () => { + console.log("connected"); + resolve(); + }; + }); ws.onmessage = ({ data }) => { - console.log(JSON.parse(data)); + const [event_type, event_data] = JSON.parse(data); + const message = { event_type, data: event_data } as Event; + + console.log("Received message", message); + if (message.event_type === EventType.SET_CONTROLLABLE) { + const event = message as SetControllableEvent; + if (event.data.client_id === session_id) { + controllable_entities.add(event.data.id); + } + } }; - ws.onclose = (e) => { - console.log("disconnected", e); + ws.onclose = () => { + controllable_entities.clear(); }; }); diff --git a/static/src/mouse_controller.ts b/static/src/mouse_controller.ts new file mode 100644 index 0000000..b8849b4 --- /dev/null +++ b/static/src/mouse_controller.ts @@ -0,0 +1,54 @@ +import { Vec2 } from "./vector"; + +export class MouseController { + private readonly debounce_ms = 400; + private readonly movement_threshold = 40; + private last_event_time = Date.now(); + private movement_queue: Vec2[] = []; + private interval_id: number | null = null; + + constructor(private readonly callback: (new_movement: Vec2) => void) {} + + public start() { + if (this.interval_id !== null) { + return; + } + this.interval_id = setInterval(() => { + this.publish_movement(); + }, this.debounce_ms); + } + + public stop() { + if (this.interval_id === null) { + return; + } + clearInterval(this.interval_id); + this.interval_id = null; + } + + public move(x: number, y: number) { + const new_movement = new Vec2(x, y); + const last_movement = this.movement_queue.at(-1); + this.movement_queue.push(new_movement); + if ( + typeof last_movement === "undefined" || + new_movement.distance_to(last_movement) < this.movement_threshold + ) { + return; + } + this.publish_movement(); + } + + private publish_movement() { + if ( + Date.now() - this.last_event_time < this.debounce_ms || + this.movement_queue.length === 0 + ) { + return; + } + + this.last_event_time = Date.now(); + this.callback(this.movement_queue.at(-1)!); + this.movement_queue = []; + } +} diff --git a/static/src/vector.ts b/static/src/vector.ts new file mode 100644 index 0000000..e7be9fa --- /dev/null +++ b/static/src/vector.ts @@ -0,0 +1,10 @@ +export class Vec2 { + constructor( + private readonly x: number, + private readonly y: number, + ) {} + + public distance_to(that: Vec2): number { + return Math.sqrt((this.x - that.x) ** 2 + (this.y - that.y) ** 2); + } +} diff --git a/static/tsconfig.json b/static/tsconfig.json index 0511b9f..8f3e483 100644 --- a/static/tsconfig.json +++ b/static/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { - "target": "ES2020", + "target": "ES2022", "useDefineForClassFields": true, "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": ["ES2022", "DOM", "DOM.Iterable"], "skipLibCheck": true, /* Bundler mode */ -- 2.40.1 From d35ab6cc851d3fc85f55cfb5676f8dc3552fab24 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Mon, 2 Sep 2024 20:10:35 -0700 Subject: [PATCH 06/13] a bit more progress --- kennel/kennel.py | 16 +++-- static/src/event.ts | 28 -------- static/src/main.ts | 114 ++++++++++++++++++++++----------- static/src/mouse_controller.ts | 42 ++++++------ static/src/network.ts | 73 +++++++++++++++++++++ static/src/vector.ts | 4 ++ 6 files changed, 187 insertions(+), 90 deletions(-) delete mode 100644 static/src/event.ts create mode 100644 static/src/network.ts diff --git a/kennel/kennel.py b/kennel/kennel.py index bb0438c..a9ab4b8 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -8,6 +8,8 @@ from kennel.engine.systems.network import ( EventProcessor, Event, EventType, + Entity, + EntityPositionUpdateEvent, ) from kennel.engine.systems.world import WorldSystem from kennel.config import config @@ -35,24 +37,28 @@ class KennelEventProcessor(EventProcessor): self._process_entity_position_update(entity_manager, event, client_id) def _process_entity_position_update( - self, entity_manager: EntityManager, event: Event, client_id: str + self, + entity_manager: EntityManager, + event: EntityPositionUpdateEvent, + client_id: str, ) -> None: entity = entity_manager.get_entity(event.data["id"]) if entity is None: logger.error(f"Entity(id={event.data['id']}) does not exist") return controllable = entity.get_component(ComponentType.CONTROLLABLE) - if controllable is None: - logger.error(f"Entity {entity} is not controllable") - return - if controllable.by != client_id: + if controllable is None or controllable.by != client_id: logger.error(f"Entity {entity} is not controllable by client {client_id}") return position = entity.get_component(ComponentType.POSITION) if position is None: logger.error(f"Entity {entity} has no position") return + position.x = event.data["position"]["x"] + position.y = event.data["position"]["y"] + entity.add_component(position) + entity_manager.add_entity(entity) system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) diff --git a/static/src/event.ts b/static/src/event.ts deleted file mode 100644 index cd50122..0000000 --- a/static/src/event.ts +++ /dev/null @@ -1,28 +0,0 @@ -export enum EventType { - INITIAL_STATE = "INITIAL_STATE", - SET_CONTROLLABLE = "SET_CONTROLLABLE", - ENTITY_BORN = "ENTITY_BORN", - ENTITY_DEATH = "ENTITY_DEATH", - ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE", -} - -export interface Event { - event_type: EventType; - data: any; -} - -export interface InitialStateEvent extends Event { - event_type: EventType.INITIAL_STATE; - data: { - world: { width: number; height: number }; - entities: any[]; - }; -} - -export interface SetControllableEvent extends Event { - event_type: EventType.SET_CONTROLLABLE; - data: { - id: string; - client_id: string; - }; -} diff --git a/static/src/main.ts b/static/src/main.ts index fc04b72..eb98ba8 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -1,51 +1,93 @@ import $ from "jquery"; import { Vec2 } from "./vector"; -import { EventType, type SetControllableEvent, type Event } from "./event"; +import { + EventType, + type SetControllableEvent, + type Event, + type EventPublisher, +} from "./event"; import { MouseController } from "./mouse_controller"; +import { + EntityPositionUpdateEvent, + EventProcessor, + EventQueue, + WebSocketEventQueue, +} from "./network"; -$(document).ready(async () => { +class KennelClient { + private running: boolean; + private last_update: number; + + private controllable_entities: Set; + private mouse_controller: MouseController; + + constructor( + private readonly event_queue: EventQueue, + private readonly event_publisher: EventPublisher, + ) { + this.last_update = 0; + this.running = false; + + this.mouse_controller = new MouseController((position: Vec2) => + this.on_mouse_move(position), + ); + } + + public start() { + this.running = true; + this.last_update = performance.now(); + this.mouse_controller.start(); + + const loop = (timestamp: number) => { + const dt = timestamp - this.last_update; + this.propogate_state_after(dt); + requestAnimationFrame(loop); // tail call recursion! /s + }; + requestAnimationFrame(loop); + } + + public close() { + this.running = false; + this.mouse_controller.stop(); + this.controllable_entities.clear(); + } + + private propogate_state_after(dt: number) { + // TODO: interpolate cats and lasers and stuff + } + + private on_mouse_move(position: Vec2) { + for (const id of this.controllable_entities) { + const event: EntityPositionUpdateEvent = { + event_type: EventType.ENTITY_POSITION_UPDATE, + data: { id, position: { x: position.x, y: position.y } }, + }; + this.event_publisher.send(event); + } + } +} + +$(async () => { const session_id = await fetch("/assign", { credentials: "include", }) .then((res) => res.json()) .then(({ session }) => session); - const controllable_entities = new Set(); - const control_callback = (movement: Vec2) => { - for (const id of controllable_entities) { - const message = JSON.stringify({ - event_type: EventType.ENTITY_POSITION_UPDATE, - data: { id, position: movement }, - }); - ws.send(message); - } - }; - const mouse_controller = new MouseController(control_callback); + const ws = new WebSocket("/ws"); + await new Promise((resolve) => { + ws.onopen = () => resolve(); + }); + + const queue: EventQueue = new WebSocketEventQueue(ws); + + const kennel_client = new KennelClient(session_id, null); + + ws.onclose = () => kennel_client.close(); + + const mouse_controller = new MouseController(on_mouse_move); $(document).on("mousemove", (event) => { mouse_controller.move(event.clientX, event.clientY); }); mouse_controller.start(); - - const ws = new WebSocket("/ws"); - await new Promise((resolve) => { - ws.onopen = () => { - console.log("connected"); - resolve(); - }; - }); - ws.onmessage = ({ data }) => { - const [event_type, event_data] = JSON.parse(data); - const message = { event_type, data: event_data } as Event; - - console.log("Received message", message); - if (message.event_type === EventType.SET_CONTROLLABLE) { - const event = message as SetControllableEvent; - if (event.data.client_id === session_id) { - controllable_entities.add(event.data.id); - } - } - }; - ws.onclose = () => { - controllable_entities.clear(); - }; }); diff --git a/static/src/mouse_controller.ts b/static/src/mouse_controller.ts index b8849b4..c7ae304 100644 --- a/static/src/mouse_controller.ts +++ b/static/src/mouse_controller.ts @@ -1,21 +1,23 @@ import { Vec2 } from "./vector"; - export class MouseController { - private readonly debounce_ms = 400; - private readonly movement_threshold = 40; private last_event_time = Date.now(); - private movement_queue: Vec2[] = []; - private interval_id: number | null = null; + private last_movement: Vec2 | undefined; + private interval_id: number | undefined; - constructor(private readonly callback: (new_movement: Vec2) => void) {} + constructor( + private readonly publisher: (new_movement: Vec2) => void | Promise, + private readonly debounce_ms = 200, + private readonly l2_norm_threshold = 40, + ) {} public start() { - if (this.interval_id !== null) { + if (typeof this.interval_id !== "undefined") { return; } - this.interval_id = setInterval(() => { - this.publish_movement(); - }, this.debounce_ms); + this.interval_id = setInterval( + () => this.publish_movement(), + this.debounce_ms, + ); } public stop() { @@ -23,32 +25,30 @@ export class MouseController { return; } clearInterval(this.interval_id); - this.interval_id = null; + delete this.interval_id; } public move(x: number, y: number) { const new_movement = new Vec2(x, y); - const last_movement = this.movement_queue.at(-1); - this.movement_queue.push(new_movement); if ( - typeof last_movement === "undefined" || - new_movement.distance_to(last_movement) < this.movement_threshold + typeof this.last_movement !== "undefined" && + new_movement.distance_to(this.last_movement) >= this.l2_norm_threshold ) { - return; + this.publish_movement(); } - this.publish_movement(); + this.last_movement = new_movement; } private publish_movement() { if ( - Date.now() - this.last_event_time < this.debounce_ms || - this.movement_queue.length === 0 + typeof this.last_movement === "undefined" || + Date.now() - this.last_event_time < this.debounce_ms ) { return; } this.last_event_time = Date.now(); - this.callback(this.movement_queue.at(-1)!); - this.movement_queue = []; + this.publisher(this.last_movement.copy()); + delete this.last_movement; } } diff --git a/static/src/network.ts b/static/src/network.ts new file mode 100644 index 0000000..0d4e216 --- /dev/null +++ b/static/src/network.ts @@ -0,0 +1,73 @@ +export enum EventType { + INITIAL_STATE = "INITIAL_STATE", + SET_CONTROLLABLE = "SET_CONTROLLABLE", + ENTITY_BORN = "ENTITY_BORN", + ENTITY_DEATH = "ENTITY_DEATH", + ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE", +} + +export interface Event { + event_type: EventType; + data: any; +} + +export interface EntityPositionUpdateEvent extends Event { + event_type: EventType.ENTITY_POSITION_UPDATE; + data: { + id: string; + position: { + x: number; + y: number; + }; + }; +} + +export interface InitialStateEvent extends Event { + event_type: EventType.INITIAL_STATE; + data: { + world: { width: number; height: number }; + entities: any[]; + }; +} + +export interface SetControllableEvent extends Event { + event_type: EventType.SET_CONTROLLABLE; + data: { + id: string; + client_id: string; + }; +} + +export interface EventQueue { + peek(): Event[]; + clear(): void; +} + +export interface EventPublisher { + add(event: Event): void; + publish(): void; +} + +export class WebSocketEventQueue implements EventQueue { + private queue: Event[]; + + constructor(websocket: WebSocket) { + this.queue = []; + this.listen_to(websocket); + } + + public peek() { + return this.queue; + } + + public clear() { + this.queue = []; + } + + private listen_to(websocket: WebSocket) { + websocket.onmessage = ({ data }) => { + const [event_type, event_data] = JSON.parse(data); + this.queue.push({ event_type, data: event_data } as Event); + }; + } +} diff --git a/static/src/vector.ts b/static/src/vector.ts index e7be9fa..96bd721 100644 --- a/static/src/vector.ts +++ b/static/src/vector.ts @@ -7,4 +7,8 @@ export class Vec2 { public distance_to(that: Vec2): number { return Math.sqrt((this.x - that.x) ** 2 + (this.y - that.y) ** 2); } + + public copy(): Vec2 { + return new Vec2(this.x, this.y); + } } -- 2.40.1 From 9f5e81327be33489d9bb912de503cd01d6ba7dca Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Mon, 2 Sep 2024 21:05:43 -0700 Subject: [PATCH 07/13] get it compiling --- kennel/engine/systems/network.py | 2 +- kennel/kennel.py | 2 +- static/src/main.ts | 55 +++++++++++++++++++++----------- static/src/network.ts | 17 ++++++++++ static/src/vector.ts | 4 +-- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py index 8dbf4cf..96ebc46 100644 --- a/kennel/engine/systems/network.py +++ b/kennel/engine/systems/network.py @@ -101,7 +101,7 @@ class Publishable: class EventProcessor: @abstractmethod - def accept(self, entity_manager: EntityManager, event: Event) -> None: + def accept(self, entity_manager: EntityManager, event: Event | tuple[Event, str]) -> None: pass diff --git a/kennel/kennel.py b/kennel/kennel.py index a9ab4b8..b1dc90a 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -23,7 +23,7 @@ system_manager = SystemManager() class KennelEventProcessor(EventProcessor): def accept( - self, entity_manager: EntityManager, event: type[Event | tuple[Event, str]] + self, entity_manager: EntityManager, event: Event | tuple[Event, str] ) -> None: if isinstance(event, tuple): client_event, client_id = event diff --git a/static/src/main.ts b/static/src/main.ts index eb98ba8..16de636 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -1,16 +1,13 @@ import $ from "jquery"; import { Vec2 } from "./vector"; -import { - EventType, - type SetControllableEvent, - type Event, - type EventPublisher, -} from "./event"; import { MouseController } from "./mouse_controller"; import { EntityPositionUpdateEvent, - EventProcessor, + EventPublisher, EventQueue, + EventType, + SetControllableEvent, + WebsocketEventPublisher, WebSocketEventQueue, } from "./network"; @@ -18,10 +15,11 @@ class KennelClient { private running: boolean; private last_update: number; - private controllable_entities: Set; + private controllable_entities: Set = new Set(); private mouse_controller: MouseController; constructor( + private readonly client_id: string, private readonly event_queue: EventQueue, private readonly event_publisher: EventPublisher, ) { @@ -39,21 +37,45 @@ class KennelClient { this.mouse_controller.start(); const loop = (timestamp: number) => { + if (!this.running) return; const dt = timestamp - this.last_update; this.propogate_state_after(dt); requestAnimationFrame(loop); // tail call recursion! /s }; requestAnimationFrame(loop); + + $(document).on("mousemove", (event) => { + this.mouse_controller.move(event.clientX, event.clientY); + }); } public close() { this.running = false; this.mouse_controller.stop(); this.controllable_entities.clear(); + $(document).off("mousemove"); } private propogate_state_after(dt: number) { - // TODO: interpolate cats and lasers and stuff + const events = this.event_queue.peek(); + for (const event of events) { + switch (event.event_type) { + case EventType.SET_CONTROLLABLE: + this.process_set_controllable_event(event as SetControllableEvent); + break; + } + } + + console.log(events, dt); + this.event_queue.clear(); + } + + private process_set_controllable_event(event: SetControllableEvent) { + if (event.data.client_id !== this.client_id) { + console.warn("got controllable event for client that is not us"); + return; + } + this.controllable_entities.add(event.data.id); } private on_mouse_move(position: Vec2) { @@ -62,13 +84,13 @@ class KennelClient { event_type: EventType.ENTITY_POSITION_UPDATE, data: { id, position: { x: position.x, y: position.y } }, }; - this.event_publisher.send(event); + this.event_publisher.add(event); } } } $(async () => { - const session_id = await fetch("/assign", { + const client_id = await fetch("/assign", { credentials: "include", }) .then((res) => res.json()) @@ -80,14 +102,9 @@ $(async () => { }); const queue: EventQueue = new WebSocketEventQueue(ws); - - const kennel_client = new KennelClient(session_id, null); - + const publisher: EventPublisher = new WebsocketEventPublisher(ws); + const kennel_client = new KennelClient(client_id, queue, publisher); ws.onclose = () => kennel_client.close(); - const mouse_controller = new MouseController(on_mouse_move); - $(document).on("mousemove", (event) => { - mouse_controller.move(event.clientX, event.clientY); - }); - mouse_controller.start(); + kennel_client.start(); }); diff --git a/static/src/network.ts b/static/src/network.ts index 0d4e216..f707148 100644 --- a/static/src/network.ts +++ b/static/src/network.ts @@ -71,3 +71,20 @@ export class WebSocketEventQueue implements EventQueue { }; } } + +export class WebsocketEventPublisher implements EventPublisher { + private queue: Event[]; + + constructor(private readonly websocket: WebSocket) { + this.queue = []; + } + + public add(event: Event) { + this.queue.push(event); + } + + public publish() { + this.websocket.send(JSON.stringify(this.queue)); + this.queue = []; + } +} diff --git a/static/src/vector.ts b/static/src/vector.ts index 96bd721..29e62df 100644 --- a/static/src/vector.ts +++ b/static/src/vector.ts @@ -1,7 +1,7 @@ export class Vec2 { constructor( - private readonly x: number, - private readonly y: number, + public readonly x: number, + public readonly y: number, ) {} public distance_to(that: Vec2): number { -- 2.40.1 From c39367ac7e163395ae726bd169926004d4d99d67 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Mon, 2 Sep 2024 23:59:48 -0700 Subject: [PATCH 08/13] fix CI --- .drone.yml | 8 +- kennel/config.py | 3 +- kennel/engine/components/component.py | 2 +- kennel/engine/entities/entity.py | 3 +- kennel/engine/entities/laser.py | 5 +- kennel/engine/game.py | 8 +- kennel/engine/systems/network.py | 17 +- kennel/engine/systems/system.py | 5 +- kennel/engine/systems/world.py | 6 +- kennel/kennel.py | 35 +-- kennel/kennelcats.py | 1 + kennel/main.py | 34 +-- kennel/middleware.py | 5 +- poetry.lock | 339 +++++++------------------- pyproject.toml | 2 - tests/__init__.py | 0 tests/test_main.py | 18 -- 17 files changed, 161 insertions(+), 330 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/test_main.py diff --git a/.drone.yml b/.drone.yml index d05cb97..1054159 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,12 +4,12 @@ type: docker name: build steps: - - name: run tests + - name: lint image: python:3.12 commands: - pip install poetry - poetry install --with main,dev - - poetry run pytest + - poetry run ruff check kennel/* trigger: event: @@ -21,12 +21,12 @@ type: docker name: deploy steps: - - name: run tests + - name: run lier image: python:3.12 commands: - pip install poetry - poetry install --with main,dev - - poetry run pytest + - poetry run ruff check kennel/* - name: docker image: plugins/docker settings: diff --git a/kennel/config.py b/kennel/config.py index 4452044..a731c84 100644 --- a/kennel/config.py +++ b/kennel/config.py @@ -1,6 +1,7 @@ -from dotenv import load_dotenv import os +from dotenv import load_dotenv + load_dotenv() diff --git a/kennel/engine/components/component.py b/kennel/engine/components/component.py index da55138..9f56407 100644 --- a/kennel/engine/components/component.py +++ b/kennel/engine/components/component.py @@ -1,5 +1,5 @@ -from enum import Enum from abc import abstractmethod +from enum import Enum class ComponentType(str, Enum): diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py index 1d8c34d..d40ab19 100644 --- a/kennel/engine/entities/entity.py +++ b/kennel/engine/entities/entity.py @@ -1,7 +1,8 @@ from enum import Enum -from kennel.engine.components.component import Component, ComponentType from typing import List, Optional +from kennel.engine.components.component import Component, ComponentType + class EntityType(str, Enum): LASER = "LASER" diff --git a/kennel/engine/entities/laser.py b/kennel/engine/entities/laser.py index ead72a6..09c3c93 100644 --- a/kennel/engine/entities/laser.py +++ b/kennel/engine/entities/laser.py @@ -1,6 +1,7 @@ -from .entity import Entity, EntityType -from kennel.engine.components.position import Position from kennel.engine.components.controllable import Controllable +from kennel.engine.components.position import Position + +from .entity import Entity, EntityType class Laser(Entity): diff --git a/kennel/engine/game.py b/kennel/engine/game.py index f3134be..80e47bd 100644 --- a/kennel/engine/game.py +++ b/kennel/engine/game.py @@ -1,9 +1,9 @@ +import asyncio +import time + +from kennel.app import logger from kennel.engine.entities.entity import EntityManager from kennel.engine.systems.system import SystemManager -from kennel.app import logger -from typing import List, Optional -import time -import asyncio class Game: diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py index 96ebc46..1c1d71c 100644 --- a/kennel/engine/systems/network.py +++ b/kennel/engine/systems/network.py @@ -1,9 +1,12 @@ -from enum import Enum -from kennel.engine.entities.entity import Entity, EntityType, EntityManager -from .system import System, SystemType -from abc import abstractmethod -from typing import Optional, List import asyncio +from abc import abstractmethod +from enum import Enum +from typing import List, Optional + +from kennel.app import logger +from kennel.engine.entities.entity import Entity, EntityManager + +from .system import System, SystemType class EventType(str, Enum): @@ -101,7 +104,9 @@ class Publishable: class EventProcessor: @abstractmethod - def accept(self, entity_manager: EntityManager, event: Event | tuple[Event, str]) -> None: + def accept( + self, entity_manager: EntityManager, event: Event | tuple[Event, str] + ) -> None: pass diff --git a/kennel/engine/systems/system.py b/kennel/engine/systems/system.py index 78bda58..e3ddb27 100644 --- a/kennel/engine/systems/system.py +++ b/kennel/engine/systems/system.py @@ -1,6 +1,7 @@ -from enum import Enum -from kennel.engine.entities.entity import EntityManager from abc import abstractmethod +from enum import Enum + +from kennel.engine.entities.entity import EntityManager class SystemType(str, Enum): diff --git a/kennel/engine/systems/world.py b/kennel/engine/systems/world.py index 396e7cb..b74816f 100644 --- a/kennel/engine/systems/world.py +++ b/kennel/engine/systems/world.py @@ -1,7 +1,7 @@ -from kennel.engine.systems.system import System, SystemType -from kennel.engine.entities.entity import EntityManager -from kennel.engine.components.component import ComponentType from kennel.app import logger +from kennel.engine.components.component import ComponentType +from kennel.engine.entities.entity import EntityManager +from kennel.engine.systems.system import System, SystemType class WorldSystem(System): diff --git a/kennel/kennel.py b/kennel/kennel.py index b1dc90a..1e23670 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -1,21 +1,22 @@ -from kennel.engine.game import Game -from kennel.engine.components.component import ComponentType -from kennel.engine.entities.entity import EntityManager, Entity -from kennel.engine.entities.laser import Laser -from kennel.engine.systems.system import SystemManager -from kennel.engine.systems.network import ( - NetworkSystem, - EventProcessor, - Event, - EventType, - Entity, - EntityPositionUpdateEvent, -) -from kennel.engine.systems.world import WorldSystem -from kennel.config import config -from .app import logger -from typing import List import uuid +from typing import List + +from kennel.config import config +from kennel.engine.components.component import ComponentType +from kennel.engine.entities.entity import Entity, EntityManager +from kennel.engine.entities.laser import Laser +from kennel.engine.game import Game +from kennel.engine.systems.network import ( + EntityPositionUpdateEvent, + Event, + EventProcessor, + EventType, + NetworkSystem, +) +from kennel.engine.systems.system import SystemManager +from kennel.engine.systems.world import WorldSystem + +from .app import logger entity_manager = EntityManager() system_manager = SystemManager() diff --git a/kennel/kennelcats.py b/kennel/kennelcats.py index 340b07e..970e44f 100644 --- a/kennel/kennelcats.py +++ b/kennel/kennelcats.py @@ -1,5 +1,6 @@ from datetime import datetime from typing import List + import requests diff --git a/kennel/main.py b/kennel/main.py index fc0fd0e..c1106fe 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -1,37 +1,37 @@ +import asyncio +import uuid +from typing import Annotated, Optional + from fastapi import ( - FastAPI, + Cookie, + Depends, Request, Response, WebSocket, + WebSocketDisconnect, WebSocketException, status, - Cookie, - Depends, ) from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles -from kennel.engine.systems.system import SystemType + +from kennel.app import app, logger +from kennel.config import config from kennel.engine.systems.network import ( + EntityBornEvent, Event, - Publishable, EventType, InitialStateEvent, + Publishable, SetControllableEvent, - EntityBornEvent, ) -from typing import Annotated, Optional +from kennel.engine.systems.system import SystemType from kennel.kennel import ( + create_session_controllable_entities, + entity_manager, kennel, system_manager, - entity_manager, - create_session_controllable_entities, ) -from kennel.app import app, logger -from kennel.kennelcats import KennelCatService -from kennel.middleware import logger_middleware -from kennel.config import config -import asyncio -import uuid app.mount("/static", StaticFiles(directory="static/dist"), name="static") @@ -118,7 +118,7 @@ async def websocket_endpoint( network_system.add_client(session, WebSocketClient(websocket)) while True: message = await websocket.receive_json() - if type(message) is not list: + if not isinstance(message, list): message = [message] events = [Event.from_dict(event) for event in message] if not all([event is not None for event in events]): @@ -126,7 +126,7 @@ async def websocket_endpoint( continue for event in events: network_system.client_event(session, event) - except WebSocketDisconnect as e: + except WebSocketDisconnect: logger.info(f"Websocket connection closed by client: {session}") except Exception as e: logger.error("Exception occurred", exc_info=e) diff --git a/kennel/middleware.py b/kennel/middleware.py index dddf63b..7bc932f 100644 --- a/kennel/middleware.py +++ b/kennel/middleware.py @@ -1,8 +1,11 @@ import uuid + import structlog -from fastapi import Request, Response +from fastapi import Request + from .app import app, logger + @app.middleware("http") async def logger_middleware(request: Request, call_next): structlog.contextvars.clear_contextvars() diff --git a/poetry.lock b/poetry.lock index bbe8c53..9d4cc2d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,13 +33,13 @@ trio = ["trio (>=0.23)"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -166,90 +166,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "coverage" -version = "7.6.1" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, - {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, - {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, - {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, - {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, - {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, - {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, - {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, - {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, - {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, - {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, - {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, - {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, - {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, - {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, -] - -[package.extras] -toml = ["tomli"] - [[package]] name = "fastapi" version = "0.108.0" @@ -384,43 +300,6 @@ files = [ {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - [[package]] name = "pydantic" version = "2.8.2" @@ -544,44 +423,6 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - [[package]] name = "python-dotenv" version = "1.0.1" @@ -849,98 +690,94 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)" [[package]] name = "watchfiles" -version = "0.23.0" +version = "0.24.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" files = [ - {file = "watchfiles-0.23.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4"}, - {file = "watchfiles-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207"}, - {file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b"}, - {file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981"}, - {file = "watchfiles-0.23.0-cp310-none-win32.whl", hash = "sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42"}, - {file = "watchfiles-0.23.0-cp310-none-win_amd64.whl", hash = "sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75"}, - {file = "watchfiles-0.23.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab"}, - {file = "watchfiles-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1"}, - {file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220"}, - {file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320"}, - {file = "watchfiles-0.23.0-cp311-none-win32.whl", hash = "sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b"}, - {file = "watchfiles-0.23.0-cp311-none-win_amd64.whl", hash = "sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e"}, - {file = "watchfiles-0.23.0-cp311-none-win_arm64.whl", hash = "sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304"}, - {file = "watchfiles-0.23.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5"}, - {file = "watchfiles-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0"}, - {file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c"}, - {file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581"}, - {file = "watchfiles-0.23.0-cp312-none-win32.whl", hash = "sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75"}, - {file = "watchfiles-0.23.0-cp312-none-win_amd64.whl", hash = "sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb"}, - {file = "watchfiles-0.23.0-cp312-none-win_arm64.whl", hash = "sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8"}, - {file = "watchfiles-0.23.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9"}, - {file = "watchfiles-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0"}, - {file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef"}, - {file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1"}, - {file = "watchfiles-0.23.0-cp313-none-win32.whl", hash = "sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b"}, - {file = "watchfiles-0.23.0-cp313-none-win_amd64.whl", hash = "sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff"}, - {file = "watchfiles-0.23.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:2dddc2487d33e92f8b6222b5fb74ae2cfde5e8e6c44e0248d24ec23befdc5366"}, - {file = "watchfiles-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e75695cc952e825fa3e0684a7f4a302f9128721f13eedd8dbd3af2ba450932b8"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2537ef60596511df79b91613a5bb499b63f46f01a11a81b0a2b0dedf645d0a9c"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20b423b58f5fdde704a226b598a2d78165fe29eb5621358fe57ea63f16f165c4"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b98732ec893975455708d6fc9a6daab527fc8bbe65be354a3861f8c450a632a4"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee1f5fcbf5bc33acc0be9dd31130bcba35d6d2302e4eceafafd7d9018c7755ab"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f195338a5a7b50a058522b39517c50238358d9ad8284fd92943643144c0c03"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524fcb8d59b0dbee2c9b32207084b67b2420f6431ed02c18bd191e6c575f5c48"}, - {file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0eff099a4df36afaa0eea7a913aa64dcf2cbd4e7a4f319a73012210af4d23810"}, - {file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a8323daae27ea290ba3350c70c836c0d2b0fb47897fa3b0ca6a5375b952b90d3"}, - {file = "watchfiles-0.23.0-cp38-none-win32.whl", hash = "sha256:aafea64a3ae698695975251f4254df2225e2624185a69534e7fe70581066bc1b"}, - {file = "watchfiles-0.23.0-cp38-none-win_amd64.whl", hash = "sha256:c846884b2e690ba62a51048a097acb6b5cd263d8bd91062cd6137e2880578472"}, - {file = "watchfiles-0.23.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a753993635eccf1ecb185dedcc69d220dab41804272f45e4aef0a67e790c3eb3"}, - {file = "watchfiles-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6bb91fa4d0b392f0f7e27c40981e46dda9eb0fbc84162c7fb478fe115944f491"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1f67312efa3902a8e8496bfa9824d3bec096ff83c4669ea555c6bdd213aa516"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ca6b71dcc50d320c88fb2d88ecd63924934a8abc1673683a242a7ca7d39e781"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aec5c29915caf08771d2507da3ac08e8de24a50f746eb1ed295584ba1820330"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1733b9bc2c8098c6bdb0ff7a3d7cb211753fecb7bd99bdd6df995621ee1a574b"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02ff5d7bd066c6a7673b17c8879cd8ee903078d184802a7ee851449c43521bdd"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e2de19801b0eaa4c5292a223effb7cfb43904cb742c5317a0ac686ed604765"}, - {file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8ada449e22198c31fb013ae7e9add887e8d2bd2335401abd3cbc55f8c5083647"}, - {file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3af1b05361e1cc497bf1be654a664750ae61f5739e4bb094a2be86ec8c6db9b6"}, - {file = "watchfiles-0.23.0-cp39-none-win32.whl", hash = "sha256:486bda18be5d25ab5d932699ceed918f68eb91f45d018b0343e3502e52866e5e"}, - {file = "watchfiles-0.23.0-cp39-none-win_amd64.whl", hash = "sha256:d2d42254b189a346249424fb9bb39182a19289a2409051ee432fb2926bad966a"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aba037c1310dd108411d27b3d5815998ef0e83573e47d4219f45753c710f969f"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a96ac14e184aa86dc43b8a22bb53854760a58b2966c2b41580de938e9bf26ed0"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11698bb2ea5e991d10f1f4f83a39a02f91e44e4bd05f01b5c1ec04c9342bf63c"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efadd40fca3a04063d40c4448c9303ce24dd6151dc162cfae4a2a060232ebdcb"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:556347b0abb4224c5ec688fc58214162e92a500323f50182f994f3ad33385dcb"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1cf7f486169986c4b9d34087f08ce56a35126600b6fef3028f19ca16d5889071"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18de0f82c62c4197bea5ecf4389288ac755896aac734bd2cc44004c56e4ac47"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:532e1f2c491274d1333a814e4c5c2e8b92345d41b12dc806cf07aaff786beb66"}, - {file = "watchfiles-0.23.0.tar.gz", hash = "sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b"}, + {file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"}, + {file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e"}, + {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c"}, + {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188"}, + {file = "watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735"}, + {file = "watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04"}, + {file = "watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428"}, + {file = "watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823"}, + {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab"}, + {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec"}, + {file = "watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d"}, + {file = "watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c"}, + {file = "watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633"}, + {file = "watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a"}, + {file = "watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234"}, + {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef"}, + {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968"}, + {file = "watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444"}, + {file = "watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896"}, + {file = "watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418"}, + {file = "watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48"}, + {file = "watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f"}, + {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b"}, + {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18"}, + {file = "watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07"}, + {file = "watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366"}, + {file = "watchfiles-0.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318"}, + {file = "watchfiles-0.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91"}, + {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b"}, + {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22"}, + {file = "watchfiles-0.24.0-cp38-none-win32.whl", hash = "sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1"}, + {file = "watchfiles-0.24.0-cp38-none-win_amd64.whl", hash = "sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1"}, + {file = "watchfiles-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886"}, + {file = "watchfiles-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9"}, + {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca"}, + {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e"}, + {file = "watchfiles-0.24.0-cp39-none-win32.whl", hash = "sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da"}, + {file = "watchfiles-0.24.0-cp39-none-win_amd64.whl", hash = "sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e"}, + {file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1"}, ] [package.dependencies] @@ -1030,4 +867,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "a4fd1897aa81d377f838e0b8fdd273781f385d8a33c12a2c043e30661f67dc21" +content-hash = "36471b0f6883dfe8dd1fad61face0485e4c41fffeb82bafead9c728aa5e82696" diff --git a/pyproject.toml b/pyproject.toml index fd976b7..7bfc297 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,6 @@ requests = "^2.32.3" [tool.poetry.group.dev.dependencies] ruff = "^0.1.9" httpx = "^0.26.0" -pytest = "^7.4.3" -pytest-cov = "^4.1.0" [build-system] requires = ["poetry-core"] diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index 1c25f7f..0000000 --- a/tests/test_main.py +++ /dev/null @@ -1,18 +0,0 @@ -from http import HTTPStatus - -from fastapi.testclient import TestClient -from kennel.main import app -from structlog.testing import capture_logs - -client = TestClient(app) - - -def test_healthcheck(): - response = client.get("/healthcheck") - assert response.status_code == HTTPStatus.OK - assert response.text == "hello" - -def test_main(): - response = client.get("/") - assert response.status_code == HTTPStatus.OK - assert response.text.startswith("") -- 2.40.1 From 45726367eee475a7717d7dcad78895de348f0b97 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Thu, 5 Sep 2024 00:15:14 -0700 Subject: [PATCH 09/13] checkpoint; working position updates --- kennel/engine/systems/network.py | 81 +++++++++++++++++++------------- kennel/kennel.py | 28 +++++------ kennel/main.py | 33 +++++++------ static/src/main.ts | 6 ++- static/src/mouse_controller.ts | 15 ++---- static/src/network.ts | 6 ++- 6 files changed, 91 insertions(+), 78 deletions(-) diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py index 1c1d71c..d1542e9 100644 --- a/kennel/engine/systems/network.py +++ b/kennel/engine/systems/network.py @@ -1,7 +1,7 @@ import asyncio from abc import abstractmethod from enum import Enum -from typing import List, Optional +from typing import List, Optional, Dict from kennel.app import logger from kennel.engine.entities.entity import Entity, EntityManager @@ -55,7 +55,9 @@ class Event: class InitialStateEvent(Event): - def __init__(self, world_width: int, world_height: int, entities: List[Entity]): + def __init__( + self, world_width: int, world_height: int, entities: Dict[str, Entity] + ): self.world_width = world_width self.world_height = world_height self.entities = entities @@ -63,7 +65,7 @@ class InitialStateEvent(Event): EventType.INITIAL_STATE, { "world": {"width": world_width, "height": world_height}, - "entities": [entity.to_dict() for entity in entities], + "entities": entities, }, ) @@ -98,51 +100,66 @@ class EntityDeathEvent(Event): class Publishable: @abstractmethod - async def publish(self, event: Event): + async def publish(self, event: List[Event]): pass -class EventProcessor: +class UpstreamEventProcessor: @abstractmethod def accept( - self, entity_manager: EntityManager, event: Event | tuple[Event, str] - ) -> None: + self, entity_manager: EntityManager, client_event: tuple[Event, str] + ) -> Optional[Event]: pass class NetworkSystem(System): - def __init__(self, event_processor: EventProcessor): - super().__init__(SystemType.NETWORK) - self.event_processor = event_processor + event_processor: UpstreamEventProcessor + sever_events: List[Event] + client_upstream_events: List[tuple[Event, str]] + clients: Dict[str, tuple[Publishable, List[Event]]] + + def __init__(self, upstream_event_processor: UpstreamEventProcessor): + super().__init__(SystemType.NETWORK) + self.upstream_event_processor = upstream_event_processor + + self.server_events = [] # events to propogate to the entire network + self.client_upstream_events = [] # events that come from the clients - self.events = [] - self.client_events = [] self.clients = {} async def update(self, entity_manager: EntityManager, delta_time: float) -> None: - if len(self.events) + len(self.client_events) == 0: + for event in self.client_upstream_events: + produced_event = self.upstream_event_processor.accept(entity_manager, event) + if produced_event is not None: + self.server_events.append(produced_event) + promises = [ + client.publish(self.server_events + events) + for client, events in self.clients.values() + if len(self.server_events + events) > 0 + ] + await asyncio.gather(*promises) + + self.server_events = [] + self.client_upstream_events = [] + for client_id in self.clients.keys(): + (client, _) = self.clients[client_id] + self.clients[client_id] = (client, []) + + def server_global_event(self, event: Event) -> None: + self.server_events.append(event) + + def client_upstream_event(self, client_id: str, event: Event) -> None: + self.client_upstream_events.append((event, client_id)) + + def client_downstream_event(self, client_id: str, event: Event) -> None: + if client_id not in self.clients: + logger.info(f"client {client_id} not found") return - for event in self.events + self.client_events: - self.event_processor.accept(entity_manager, event) - client_sendable = self.events + [event for event, _ in self.client_events] - await asyncio.gather( - *[ - client.publish(event) - for client in self.clients.values() - for event in client_sendable - ] - ) - self.events = [] - self.client_events = [] - - def client_event(self, client_id: str, event: Event) -> None: - self.client_events.append((event, client_id)) - - def add_event(self, event: Event) -> None: - self.events.append(event) + (client, events) = self.clients[client_id] + self.clients[client_id] = (client, events + [event]) def add_client(self, client_id: str, client: Publishable) -> None: - self.clients[client_id] = client + self.clients[client_id] = (client, []) def remove_client(self, client_id: str) -> None: del self.clients[client_id] diff --git a/kennel/kennel.py b/kennel/kennel.py index 1e23670..6852e4c 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -1,5 +1,5 @@ import uuid -from typing import List +from typing import List, Optional from kennel.config import config from kennel.engine.components.component import ComponentType @@ -9,7 +9,7 @@ from kennel.engine.game import Game from kennel.engine.systems.network import ( EntityPositionUpdateEvent, Event, - EventProcessor, + UpstreamEventProcessor, EventType, NetworkSystem, ) @@ -22,27 +22,22 @@ entity_manager = EntityManager() system_manager = SystemManager() -class KennelEventProcessor(EventProcessor): +class KennelEventProcessor(UpstreamEventProcessor): def accept( - self, entity_manager: EntityManager, event: Event | tuple[Event, str] - ) -> None: - if isinstance(event, tuple): - client_event, client_id = event - self._process_client_event(entity_manager, client_event, client_id) - return - - def _process_client_event( - self, entity_manager: EntityManager, event: Event, client_id: str - ) -> None: - if event.event_type == EventType.ENTITY_POSITION_UPDATE: - self._process_entity_position_update(entity_manager, event, client_id) + self, entity_manager: EntityManager, event: tuple[Event, str] + ) -> Optional[Event]: + client_event, client_id = event + if client_event.event_type == EventType.ENTITY_POSITION_UPDATE: + return self._process_entity_position_update( + entity_manager, client_event, client_id + ) def _process_entity_position_update( self, entity_manager: EntityManager, event: EntityPositionUpdateEvent, client_id: str, - ) -> None: + ) -> Optional[Event]: entity = entity_manager.get_entity(event.data["id"]) if entity is None: logger.error(f"Entity(id={event.data['id']}) does not exist") @@ -60,6 +55,7 @@ class KennelEventProcessor(EventProcessor): entity.add_component(position) entity_manager.add_entity(entity) + return event system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) diff --git a/kennel/main.py b/kennel/main.py index c1106fe..0808eb5 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -1,6 +1,6 @@ import asyncio import uuid -from typing import Annotated, Optional +from typing import Annotated, Optional, List from fastapi import ( Cookie, @@ -82,8 +82,8 @@ class WebSocketClient(Publishable): def __init__(self, websocket: WebSocket): self.websocket = websocket - async def publish(self, event: Event): - await self.websocket.send_json([event.event_type, event.data]) + async def publish(self, events: List[Event]): + await self.websocket.send_json([event.to_dict() for event in events]) @app.websocket("/ws") @@ -95,27 +95,30 @@ async def websocket_endpoint( client = WebSocketClient(websocket) logger.info(f"Websocket connection established for session {session}") - initial_state = InitialStateEvent( - config.WORLD_WIDTH, config.WORLD_HEIGHT, kennel.entity_manager.to_dict() - ) - await client.publish(initial_state) - session_entities = create_session_controllable_entities(session) logger.info(f"Creating {len(session_entities)} entities for session {session}") try: network_system = system_manager.get_system(SystemType.NETWORK) if network_system is None: raise "Network system not found" + network_system.add_client(session, WebSocketClient(websocket)) + + network_system.client_downstream_event( + session, + InitialStateEvent( + config.WORLD_WIDTH, config.WORLD_HEIGHT, kennel.entity_manager.to_dict() + ), + ) for entity in session_entities: logger.info(f"Adding entity {entity.id} for session {session}") entity_manager.add_entity(entity) - network_system.add_event(EntityBornEvent(entity)) - set_controllable_event = SetControllableEvent(entity.id, session) - await client.publish(set_controllable_event) + network_system.server_global_event(EntityBornEvent(entity)) + network_system.client_downstream_event( + session, SetControllableEvent(entity.id, session) + ) - network_system.add_client(session, WebSocketClient(websocket)) while True: message = await websocket.receive_json() if not isinstance(message, list): @@ -125,20 +128,20 @@ async def websocket_endpoint( logger.info(f"Invalid events in: {message}"[:100]) continue for event in events: - network_system.client_event(session, event) + network_system.client_upstream_event(session, event) except WebSocketDisconnect: logger.info(f"Websocket connection closed by client: {session}") except Exception as e: logger.error("Exception occurred", exc_info=e) finally: - logger.info("Websocket connection closed") + network_system.remove_client(session) for entity in session_entities: logger.info(f"Removing entity {entity.id} for session {session}") entity_manager.remove_entity(entity.id) network_system.add_event(Event(EventType.ENTITY_DEATH, {"id": entity.id})) - network_system.remove_client(session) await websocket.close() + logger.info("Websocket connection closed") @app.get("/healthcheck") diff --git a/static/src/main.ts b/static/src/main.ts index 16de636..c3588de 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -65,9 +65,11 @@ class KennelClient { break; } } - - console.log(events, dt); + if (events.length > 0) { + console.log(events, dt); + } this.event_queue.clear(); + this.event_publisher.publish(); } private process_set_controllable_event(event: SetControllableEvent) { diff --git a/static/src/mouse_controller.ts b/static/src/mouse_controller.ts index c7ae304..bd8d51a 100644 --- a/static/src/mouse_controller.ts +++ b/static/src/mouse_controller.ts @@ -7,7 +7,6 @@ export class MouseController { constructor( private readonly publisher: (new_movement: Vec2) => void | Promise, private readonly debounce_ms = 200, - private readonly l2_norm_threshold = 40, ) {} public start() { @@ -29,20 +28,14 @@ export class MouseController { } public move(x: number, y: number) { - const new_movement = new Vec2(x, y); - if ( - typeof this.last_movement !== "undefined" && - new_movement.distance_to(this.last_movement) >= this.l2_norm_threshold - ) { - this.publish_movement(); - } - this.last_movement = new_movement; + this.last_movement = new Vec2(x, y); + this.publish_movement(); } private publish_movement() { if ( - typeof this.last_movement === "undefined" || - Date.now() - this.last_event_time < this.debounce_ms + Date.now() - this.last_event_time < this.debounce_ms || + typeof this.last_movement === "undefined" ) { return; } diff --git a/static/src/network.ts b/static/src/network.ts index f707148..fac96df 100644 --- a/static/src/network.ts +++ b/static/src/network.ts @@ -66,8 +66,7 @@ export class WebSocketEventQueue implements EventQueue { private listen_to(websocket: WebSocket) { websocket.onmessage = ({ data }) => { - const [event_type, event_data] = JSON.parse(data); - this.queue.push({ event_type, data: event_data } as Event); + this.queue = this.queue.concat(JSON.parse(data)); }; } } @@ -84,6 +83,9 @@ export class WebsocketEventPublisher implements EventPublisher { } public publish() { + if (this.queue.length === 0) { + return; + } this.websocket.send(JSON.stringify(this.queue)); this.queue = []; } -- 2.40.1 From e4e31978bae7e45be57b376415a4b925ac8cbc03 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Thu, 5 Sep 2024 00:17:35 -0700 Subject: [PATCH 10/13] fix linting --- kennel/engine/systems/network.py | 2 +- kennel/kennel.py | 2 +- kennel/main.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py index d1542e9..693e1bc 100644 --- a/kennel/engine/systems/network.py +++ b/kennel/engine/systems/network.py @@ -1,7 +1,7 @@ import asyncio from abc import abstractmethod from enum import Enum -from typing import List, Optional, Dict +from typing import Dict, List, Optional from kennel.app import logger from kennel.engine.entities.entity import Entity, EntityManager diff --git a/kennel/kennel.py b/kennel/kennel.py index 6852e4c..b4d7b7b 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -9,9 +9,9 @@ from kennel.engine.game import Game from kennel.engine.systems.network import ( EntityPositionUpdateEvent, Event, - UpstreamEventProcessor, EventType, NetworkSystem, + UpstreamEventProcessor, ) from kennel.engine.systems.system import SystemManager from kennel.engine.systems.world import WorldSystem diff --git a/kennel/main.py b/kennel/main.py index 0808eb5..22d3d39 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -1,6 +1,6 @@ import asyncio import uuid -from typing import Annotated, Optional, List +from typing import Annotated, List, Optional from fastapi import ( Cookie, @@ -92,7 +92,6 @@ async def websocket_endpoint( session: Annotated[str, Depends(get_cookie_or_token)], ): await websocket.accept() - client = WebSocketClient(websocket) logger.info(f"Websocket connection established for session {session}") session_entities = create_session_controllable_entities(session) -- 2.40.1 From 8ec7f5368232d59f344e1067e1bad5e48dbcb7ae Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 7 Sep 2024 20:20:07 -0700 Subject: [PATCH 11/13] get "cats" up there --- kennel/config.py | 2 + kennel/engine/components/component.py | 1 + .../components/markov_transition_state.py | 24 +++ kennel/engine/entities/cat.py | 14 +- kennel/engine/entities/entity.py | 1 + .../systems/markov_transition_state_system.py | 13 ++ kennel/engine/systems/system.py | 3 +- kennel/kennel.py | 81 +++++++++- kennel/kennelcats.py | 6 +- kennel/main.py | 3 + static/index.html | 9 +- static/package-lock.json | 9 +- static/package.json | 6 +- static/src/component.ts | 8 - static/src/engine/component.ts | 24 +++ .../debounce_publisher.ts} | 25 ++-- static/src/engine/entity.ts | 45 ++++++ static/src/{network.ts => engine/events.ts} | 17 ++- static/src/engine/game.ts | 112 ++++++++++++++ static/src/engine/input.ts | 49 +++++++ static/src/engine/network.ts | 138 ++++++++++++++++++ static/src/engine/render.ts | 46 ++++++ static/src/engine/system.ts | 14 ++ static/src/engine/trailing_position.ts | 33 +++++ static/src/entity.ts | 11 -- static/src/main.ts | 116 ++++----------- static/src/vector.ts | 14 -- 27 files changed, 674 insertions(+), 150 deletions(-) create mode 100644 kennel/engine/components/markov_transition_state.py create mode 100644 kennel/engine/systems/markov_transition_state_system.py delete mode 100644 static/src/component.ts create mode 100644 static/src/engine/component.ts rename static/src/{mouse_controller.ts => engine/debounce_publisher.ts} (51%) create mode 100644 static/src/engine/entity.ts rename static/src/{network.ts => engine/events.ts} (84%) create mode 100644 static/src/engine/game.ts create mode 100644 static/src/engine/input.ts create mode 100644 static/src/engine/network.ts create mode 100644 static/src/engine/render.ts create mode 100644 static/src/engine/system.ts create mode 100644 static/src/engine/trailing_position.ts delete mode 100644 static/src/entity.ts delete mode 100644 static/src/vector.ts diff --git a/kennel/config.py b/kennel/config.py index a731c84..8a09500 100644 --- a/kennel/config.py +++ b/kennel/config.py @@ -17,5 +17,7 @@ class Config: COOKIE_MAX_AGE = int(os.getenv("COOKIE_MAX_AGE", 60 * 60 * 24 * 7)) # 1 week + KENNEL_CATS_POLL_SEC = int(os.getenv("KENNEL_CATS_POLL_SEC", 10)) + config = Config() diff --git a/kennel/engine/components/component.py b/kennel/engine/components/component.py index 9f56407..90506b9 100644 --- a/kennel/engine/components/component.py +++ b/kennel/engine/components/component.py @@ -5,6 +5,7 @@ from enum import Enum class ComponentType(str, Enum): POSITION = "POSITION" CONTROLLABLE = "CONTROLLABLE" + MARKOV = "MARKOV" class Component: diff --git a/kennel/engine/components/markov_transition_state.py b/kennel/engine/components/markov_transition_state.py new file mode 100644 index 0000000..cec8223 --- /dev/null +++ b/kennel/engine/components/markov_transition_state.py @@ -0,0 +1,24 @@ +from kennel.engine.components.component import Component, ComponentType + + +class MarkovTransitionState(Component): + def __init__( + self, + state_names: dict[int, str], + initial_state_vector: list[float], + transition_matrix: list[list[float]], + ): + # TODO: Poll rate per state? + # TODO: State being an enum instead of a vector, just choose max and map + self.state_names = state_names + self.state = initial_state_vector + self.transition_matrix = transition_matrix + + super().__init__(ComponentType.MARKOV) + + def get_max_state_name(self, state_vector: list[float]): + max_val = max(state_vector) + return self.state_names[state_vector.index(max_val)] + + def to_dict(self): + return {"state": self.get_max_state_name(self.state)} diff --git a/kennel/engine/entities/cat.py b/kennel/engine/entities/cat.py index 02fdb33..edcde37 100644 --- a/kennel/engine/entities/cat.py +++ b/kennel/engine/entities/cat.py @@ -1,6 +1,18 @@ +from kennel.engine.components.position import Position + +from .entity import Entity, EntityType + + +class Cat(Entity): + def __init__(self, id: str): + components = [Position(0, 0)] + + super().__init__(EntityType.CAT, id, components) + + # # # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY -# state_stochastic_matrix = [ +# state_stochastic_matrix = [ [1, 0] # # IDLE # [0.5, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0], # # FROLICKING diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py index d40ab19..30e325a 100644 --- a/kennel/engine/entities/entity.py +++ b/kennel/engine/entities/entity.py @@ -6,6 +6,7 @@ from kennel.engine.components.component import Component, ComponentType class EntityType(str, Enum): LASER = "LASER" + CAT = "CAT" class Entity: diff --git a/kennel/engine/systems/markov_transition_state_system.py b/kennel/engine/systems/markov_transition_state_system.py new file mode 100644 index 0000000..ad3de65 --- /dev/null +++ b/kennel/engine/systems/markov_transition_state_system.py @@ -0,0 +1,13 @@ +from kennel.engine.components.component import ComponentType +from kennel.engine.entities.entity import EntityManager +from kennel.engine.systems.system import System, SystemType +from kennel.engine.systems.network import NetworkSystem + + +class MarkovTransitionStateSystem(System): + def __init__(self, network_system: NetworkSystem): + super().__init__(SystemType.MARKOV) + + def update(self, entity_manager: EntityManager, delta_time: float): + entity_manager.get_entities_with_component(ComponentType.MARKOV) + return diff --git a/kennel/engine/systems/system.py b/kennel/engine/systems/system.py index e3ddb27..8c577a6 100644 --- a/kennel/engine/systems/system.py +++ b/kennel/engine/systems/system.py @@ -7,6 +7,7 @@ from kennel.engine.entities.entity import EntityManager class SystemType(str, Enum): NETWORK = "NETWORK" WORLD = "WORLD" + MARKOV = "MARKOV" class System: @@ -14,7 +15,7 @@ class System: self.system_type = system_type @abstractmethod - async def update(self, entity_manager: EntityManager, delta_time: float): + async def update(self, entity_manager: EntityManager, delta_time: float) -> None: pass diff --git a/kennel/kennel.py b/kennel/kennel.py index b4d7b7b..7123b7f 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -1,18 +1,27 @@ +import asyncio import uuid +import time from typing import List, Optional from kennel.config import config from kennel.engine.components.component import ComponentType from kennel.engine.entities.entity import Entity, EntityManager from kennel.engine.entities.laser import Laser +from kennel.engine.entities.cat import Cat from kennel.engine.game import Game +from kennel.engine.systems.markov_transition_state_system import ( + MarkovTransitionStateSystem, +) from kennel.engine.systems.network import ( EntityPositionUpdateEvent, + EntityBornEvent, + EntityDeathEvent, Event, EventType, NetworkSystem, UpstreamEventProcessor, ) +from kennel.kennelcats import KennelCatService, KennelCat from kennel.engine.systems.system import SystemManager from kennel.engine.systems.world import WorldSystem @@ -58,12 +67,76 @@ class KennelEventProcessor(UpstreamEventProcessor): return event -system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) -system_manager.add_system( - NetworkSystem(KennelEventProcessor()), -) +class KennelCatsManager: + kennel_cat_service: KennelCatService + entity_manager: EntityManager + network_system: NetworkSystem + last_seen: set[str] + poll_interval_sec: int + running: bool + + def __init__( + self, + kennel_cat_service: KennelCatService, + entity_manager: EntityManager, + network_system: NetworkSystem, + poll_interval_sec: int, + ): + self.kennel_cat_service = kennel_cat_service + self.entity_manager = entity_manager + self.network_system = network_system + self.poll_interval_sec = poll_interval_sec + + self.last_seen = set() + self.running = False + + async def start(self) -> None: + logger.info("starting kennel cats manager") + if self.running: + return + + self.running = True + while self.running: + logger.info("polling kennel cats service") + cats = self.kennel_cat_service.get_kennel() + cats_table = {cat.id: cat for cat in cats} + cat_ids = set([cat.id for cat in cats]) + + removed_cats = [cats_table[id] for id in self.last_seen.difference(cat_ids)] + added_cats = [cats_table[id] for id in cat_ids.difference(self.last_seen)] + logger.info(f"removing {removed_cats}") + logger.info(f"adding {added_cats}") + + for removed in removed_cats: + self.entity_manager.remove_entity(removed) + entity_death = EntityDeathEvent(removed.id) + self.network_system.server_global_event(entity_death) + + for added in added_cats: + new_cat = Cat(added.id) + self.entity_manager.add_entity(new_cat) + entity_born = EntityBornEvent(new_cat) + self.network_system.server_global_event(entity_born) + + self.last_seen = cat_ids + await asyncio.sleep(self.poll_interval_sec) + + def stop(self) -> None: + logger.info("stopping kennel cats manager") + self.running = False + + +network_system = NetworkSystem(KennelEventProcessor()) +world_system = WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT) +markov_transition_state_system = MarkovTransitionStateSystem(network_system) +system_manager.add_system(network_system) +system_manager.add_system(world_system) kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) +kennel_cat_service = KennelCatService(config.HATECOMPUTERS_ENDPOINT) +kennel_cats_manager = KennelCatsManager( + kennel_cat_service, entity_manager, network_system, config.KENNEL_CATS_POLL_SEC +) def create_session_controllable_entities(session: str) -> List[Entity]: diff --git a/kennel/kennelcats.py b/kennel/kennelcats.py index 970e44f..2cb2f29 100644 --- a/kennel/kennelcats.py +++ b/kennel/kennelcats.py @@ -37,10 +37,10 @@ class KennelCat: class KennelCatService: - def __init__(self, endpoint: str): - self.endpoint = endpoint + def __init__(self, hc_endpoint: str): + self.hc_endpoint = hc_endpoint def get_kennel(self) -> List[KennelCat]: - response = requests.get(f"{self.endpoint}/kennel") + response = requests.get(f"{self.hc_endpoint}/kennel") response.raise_for_status() return [KennelCat.from_dict(cat) for cat in response.json()] diff --git a/kennel/main.py b/kennel/main.py index 22d3d39..49d62d4 100644 --- a/kennel/main.py +++ b/kennel/main.py @@ -30,6 +30,7 @@ from kennel.kennel import ( create_session_controllable_entities, entity_manager, kennel, + kennel_cats_manager, system_manager, ) @@ -43,12 +44,14 @@ loop = asyncio.get_event_loop() async def startup_event(): logger.info("Starting Kennel...") loop.create_task(kennel.run()) + loop.create_task(kennel_cats_manager.start()) @app.on_event("shutdown") async def shutdown_event(): logger.info("Stopping Kennel...") kennel.stop() + kennel_cats_manager.stop() loop.stop() logger.info("Kennel stopped") diff --git a/static/index.html b/static/index.html index 3770eaa..ca9ed9c 100644 --- a/static/index.html +++ b/static/index.html @@ -9,7 +9,14 @@ the kennel. -
+ +
+ +
diff --git a/static/package-lock.json b/static/package-lock.json index e85f7a7..b62cb6d 100644 --- a/static/package-lock.json +++ b/static/package-lock.json @@ -8,7 +8,8 @@ "name": "kennel", "version": "0.1.0", "dependencies": { - "jquery": "^3.7.1" + "jquery": "^3.7.1", + "laser-pen": "^1.0.1" }, "devDependencies": { "@rollup/plugin-inject": "^5.0.5", @@ -1230,6 +1231,12 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, + "node_modules/laser-pen": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/laser-pen/-/laser-pen-1.0.1.tgz", + "integrity": "sha512-+K3CQK5ryLDDjX0pEdSIRXh89F6KSpm225DEymWMheg2/umhUO+na/4l8MGs2gee7VO2Mx3kyG288WGrofasoA==", + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", diff --git a/static/package.json b/static/package.json index 1c698e0..b27d3de 100644 --- a/static/package.json +++ b/static/package.json @@ -4,10 +4,9 @@ "version": "0.1.0", "type": "module", "scripts": { - "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "watch": "./node_modules/nodemon/bin/nodemon.js --watch './src/**/*' -e ts,html --exec \"npm run build\"" + "dev": "./node_modules/nodemon/bin/nodemon.js --watch './src/**/*' -e ts,html --exec \"npm run build\"" }, "devDependencies": { "@rollup/plugin-inject": "^5.0.5", @@ -18,6 +17,7 @@ "vite-plugin-dynamic-base": "^1.1.0" }, "dependencies": { - "jquery": "^3.7.1" + "jquery": "^3.7.1", + "laser-pen": "^1.0.1" } } diff --git a/static/src/component.ts b/static/src/component.ts deleted file mode 100644 index d338cad..0000000 --- a/static/src/component.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum ComponentType { - POSITION = "POSITION", - RENDERABLE = "RENDERABLE", -} - -export interface Component { - name: string; -} diff --git a/static/src/engine/component.ts b/static/src/engine/component.ts new file mode 100644 index 0000000..9607fec --- /dev/null +++ b/static/src/engine/component.ts @@ -0,0 +1,24 @@ +export enum ComponentType { + POSITION = "POSITION", + RENDERABLE = "RENDERABLE", + TRAILING_POSITION = "TRAILING_POSITION", +} + +export interface Component { + name: ComponentType; +} + +export interface PositionComponent extends Component { + name: ComponentType.POSITION; + x: number; + y: number; +} + +export interface TrailingPositionComponent extends Component { + name: ComponentType.TRAILING_POSITION; + trails: Array<{ x: number; y: number; time: number }>; +} + +export interface RenderableComponent extends Component { + name: ComponentType.RENDERABLE; +} diff --git a/static/src/mouse_controller.ts b/static/src/engine/debounce_publisher.ts similarity index 51% rename from static/src/mouse_controller.ts rename to static/src/engine/debounce_publisher.ts index bd8d51a..8ee4bb0 100644 --- a/static/src/mouse_controller.ts +++ b/static/src/engine/debounce_publisher.ts @@ -1,12 +1,11 @@ -import { Vec2 } from "./vector"; -export class MouseController { +export class DebouncePublisher { private last_event_time = Date.now(); - private last_movement: Vec2 | undefined; + private unpublished_data: T | undefined; private interval_id: number | undefined; constructor( - private readonly publisher: (new_movement: Vec2) => void | Promise, - private readonly debounce_ms = 200, + private readonly publisher: (data: T) => void | Promise, + private readonly debounce_ms = 100, ) {} public start() { @@ -14,7 +13,7 @@ export class MouseController { return; } this.interval_id = setInterval( - () => this.publish_movement(), + () => this.debounce_publish(), this.debounce_ms, ); } @@ -27,21 +26,21 @@ export class MouseController { delete this.interval_id; } - public move(x: number, y: number) { - this.last_movement = new Vec2(x, y); - this.publish_movement(); + public update(data: T) { + this.unpublished_data = data; + this.debounce_publish(); } - private publish_movement() { + private debounce_publish() { if ( Date.now() - this.last_event_time < this.debounce_ms || - typeof this.last_movement === "undefined" + typeof this.unpublished_data === "undefined" ) { return; } this.last_event_time = Date.now(); - this.publisher(this.last_movement.copy()); - delete this.last_movement; + this.publisher(this.unpublished_data); + this.unpublished_data = undefined; } } diff --git a/static/src/engine/entity.ts b/static/src/engine/entity.ts new file mode 100644 index 0000000..2c8d38e --- /dev/null +++ b/static/src/engine/entity.ts @@ -0,0 +1,45 @@ +import { + Component, + ComponentType, + PositionComponent, + RenderableComponent, + TrailingPositionComponent, +} from "./component"; + +export enum EntityType { + LASER = "LASER", + CAT = "CAT", +} + +export interface Entity { + entity_type: EntityType; + id: string; + components: Record; +} + +export const create_laser = (base: Entity) => { + const trailing_position: TrailingPositionComponent = { + name: ComponentType.TRAILING_POSITION, + trails: [], + }; + base.components[ComponentType.TRAILING_POSITION] = trailing_position; + + const renderable: RenderableComponent = { + name: ComponentType.RENDERABLE, + }; + base.components[ComponentType.RENDERABLE] = renderable; + return base; +}; + +export const create_cat = (base: Entity) => { + const renderable: RenderableComponent = { + name: ComponentType.RENDERABLE, + }; + base.components[ComponentType.RENDERABLE] = renderable; + base.components[ComponentType.POSITION] = { + component: ComponentType.POSITION, + x: Math.random() * 1_000, + y: Math.random() * 1_000, + } as unknown as PositionComponent; // TODO: hack + return base; +}; diff --git a/static/src/network.ts b/static/src/engine/events.ts similarity index 84% rename from static/src/network.ts rename to static/src/engine/events.ts index fac96df..60632f5 100644 --- a/static/src/network.ts +++ b/static/src/engine/events.ts @@ -1,3 +1,4 @@ +import { Entity } from "./entity"; export enum EventType { INITIAL_STATE = "INITIAL_STATE", SET_CONTROLLABLE = "SET_CONTROLLABLE", @@ -26,7 +27,7 @@ export interface InitialStateEvent extends Event { event_type: EventType.INITIAL_STATE; data: { world: { width: number; height: number }; - entities: any[]; + entities: Record; }; } @@ -38,6 +39,20 @@ export interface SetControllableEvent extends Event { }; } +export interface EntityBornEvent extends Event { + event_type: EventType.ENTITY_BORN; + data: { + entity: Entity; + }; +} + +export interface EntityDeathEvent extends Event { + event_type: EventType.ENTITY_DEATH; + data: { + id: string; + }; +} + export interface EventQueue { peek(): Event[]; clear(): void; diff --git a/static/src/engine/game.ts b/static/src/engine/game.ts new file mode 100644 index 0000000..64f63b5 --- /dev/null +++ b/static/src/engine/game.ts @@ -0,0 +1,112 @@ +import { System, SystemType } from "./system"; +import { Entity } from "./entity"; +import { ComponentType } from "./component"; + +export class Game { + private running: boolean; + private last_update: number; + + private readonly entities: Map = new Map(); + private readonly component_entities: Map> = + new Map(); + private readonly systems: Map = new Map(); + private readonly system_order: SystemType[]; + + constructor( + public readonly client_id: string, + systems: System[], + ) { + this.last_update = performance.now(); + this.running = false; + + systems.forEach((system) => this.systems.set(system.system_type, system)); + this.system_order = systems.map(({ system_type }) => system_type); + } + + public start() { + if (this.running) return; + + console.log("starting game"); + this.running = true; + this.last_update = performance.now(); + + const game_loop = (timestamp: number) => { + if (!this.running) return; + + // rebuild component -> { entity } map + this.component_entities.clear(); + Array.from(this.entities.values()).forEach((entity) => + Object.values(entity.components).forEach((component) => { + const set = + this.component_entities.get(component.name) ?? new Set(); + set.add(entity.id); + this.component_entities.set(component.name, set); + }), + ); + + const dt = timestamp - this.last_update; + + this.system_order.forEach((system_type) => + this.systems.get(system_type)!.update(dt, this), + ); + + this.last_update = timestamp; + requestAnimationFrame(game_loop); // tail call recursion! /s + }; + requestAnimationFrame(game_loop); + } + + public stop() { + if (!this.running) return; + this.running = false; + } + + public for_each_entity_with_component( + component: ComponentType, + callback: (entity: Entity) => void, + ) { + this.component_entities.get(component)?.forEach((entity_id) => { + const entity = this.entities.get(entity_id); + if (!entity) return; + + callback(entity); + }); + } + + public get_entity(id: string) { + return this.entities.get(id); + } + + public put_entity(entity: Entity) { + const old_entity = this.entities.get(entity.id); + if (old_entity) this.clear_entity_components(old_entity); + + Object.values(entity.components).forEach((component) => { + const set = + this.component_entities.get(component.name) ?? new Set(); + set.add(entity.id); + this.component_entities.set(component.name, set); + }); + this.entities.set(entity.id, entity); + } + + public remove_entity(id: string) { + const entity = this.entities.get(id); + if (typeof entity === "undefined") return; + + this.clear_entity_components(entity); + this.entities.delete(id); + } + + private clear_entity_components(entity: Entity) { + Object.values(entity.components).forEach((component) => { + const set = this.component_entities.get(component.name); + if (typeof set === "undefined") return; + set.delete(entity.id); + }); + } + + public get_system(system_type: SystemType): T | undefined { + return this.systems.get(system_type) as T; + } +} diff --git a/static/src/engine/input.ts b/static/src/engine/input.ts new file mode 100644 index 0000000..c1728e9 --- /dev/null +++ b/static/src/engine/input.ts @@ -0,0 +1,49 @@ +import { DebouncePublisher } from "./debounce_publisher"; +import { EntityPositionUpdateEvent, EventPublisher, EventType } from "./events"; +import { Game } from "./game"; +import { System, SystemType } from "./system"; + +export class InputSystem extends System { + private readonly controllable_entities: Set = new Set(); + private readonly mouse_movement_debouncer: DebouncePublisher<{ + x: number; + y: number; + }>; + + constructor( + private readonly message_publisher: EventPublisher, + target: HTMLElement, + ) { + super(SystemType.INPUT); + + this.mouse_movement_debouncer = new DebouncePublisher((data) => + this.publish_mouse_movement(data), + ); + + target.addEventListener("mousemove", (event) => { + this.mouse_movement_debouncer.update({ + x: event.clientX, + y: event.clientY, + }); + }); + } + + private publish_mouse_movement({ x, y }: { x: number; y: number }) { + console.log(`publishing mouse movement at (${x}, ${y})`); + for (const entity_id of this.controllable_entities) { + this.message_publisher.add({ + event_type: EventType.ENTITY_POSITION_UPDATE, + data: { + id: entity_id, + position: { x, y }, + }, + } as EntityPositionUpdateEvent); + } + } + + public add_controllable_entity(entity_id: string) { + this.controllable_entities.add(entity_id); + } + + public update(_dt: number, _game: Game) {} +} diff --git a/static/src/engine/network.ts b/static/src/engine/network.ts new file mode 100644 index 0000000..850da65 --- /dev/null +++ b/static/src/engine/network.ts @@ -0,0 +1,138 @@ +import { + ComponentType, + PositionComponent, + TrailingPositionComponent, +} from "./component"; +import { create_cat, create_laser, Entity, EntityType } from "./entity"; +import { + EntityBornEvent, + EntityDeathEvent, + EntityPositionUpdateEvent, + EventPublisher, + EventQueue, + EventType, + InitialStateEvent, + SetControllableEvent, +} from "./events"; +import { Game } from "./game"; +import { InputSystem } from "./input"; +import { RenderSystem } from "./render"; +import { System, SystemType } from "./system"; + +export class NetworkSystem extends System { + constructor( + private readonly event_queue: EventQueue, + private readonly event_publisher: EventPublisher, + ) { + super(SystemType.NETWORK); + } + + public update(_dt: number, game: Game) { + const events = this.event_queue.peek(); + for (const event of events) { + switch (event.event_type) { + case EventType.INITIAL_STATE: + this.process_initial_state_event(event as InitialStateEvent, game); + break; + case EventType.SET_CONTROLLABLE: + this.process_set_controllable_event( + event as SetControllableEvent, + game, + ); + break; + case EventType.ENTITY_BORN: + this.process_entity_born_event(event as EntityBornEvent, game); + break; + case EventType.ENTITY_POSITION_UPDATE: + this.process_entity_position_update_event( + event as EntityPositionUpdateEvent, + game, + ); + break; + case EventType.ENTITY_DEATH: + this.process_entity_death_event(event as EntityDeathEvent, game); + break; + } + } + + this.event_queue.clear(); + this.event_publisher.publish(); + } + + private process_initial_state_event(event: InitialStateEvent, game: Game) { + console.log("received initial state", event); + const { world, entities } = event.data; + const render_system = game.get_system(SystemType.RENDER); + if (!render_system) { + console.error("render system not found"); + return; + } + render_system.set_world_dimensions(world.width, world.height); + Object.values(entities).forEach((entity) => + game.put_entity(this.process_new_entity(entity)), + ); + } + + private process_entity_position_update_event( + event: EntityPositionUpdateEvent, + game: Game, + ) { + console.log("received entity position update", event); + const { position, id } = event.data; + const entity = game.get_entity(id); + if (typeof entity === "undefined") return; + + const position_component = entity.components[ + ComponentType.POSITION + ] as PositionComponent; + position_component.x = position.x; + position_component.y = position.y; + + if (ComponentType.TRAILING_POSITION in entity.components) { + const trailing_position = entity.components[ + ComponentType.TRAILING_POSITION + ] as TrailingPositionComponent; + trailing_position.trails.push({ + x: position_component.x, + y: position_component.y, + time: Date.now(), + }); + } + } + + private process_entity_born_event(event: EntityBornEvent, game: Game) { + console.log("received a new entity", event); + const { entity } = event.data; + game.put_entity(this.process_new_entity(entity)); + } + + private process_new_entity(entity: Entity): Entity { + if (entity.entity_type === EntityType.LASER) { + return create_laser(entity); + } + if (entity.entity_type === EntityType.CAT) { + return create_cat(entity); + } + return entity; + } + + private process_entity_death_event(event: EntityDeathEvent, game: Game) { + console.log("an entity died D:", event); + const { id } = event.data; + game.remove_entity(id); + } + + private process_set_controllable_event( + event: SetControllableEvent, + game: Game, + ) { + console.log("got a controllable event", event); + if (event.data.client_id !== game.client_id) { + console.warn("got controllable event for client that is not us"); + return; + } + + const input_system = game.get_system(SystemType.INPUT)!; + input_system.add_controllable_entity(event.data.id); + } +} diff --git a/static/src/engine/render.ts b/static/src/engine/render.ts new file mode 100644 index 0000000..8f0343a --- /dev/null +++ b/static/src/engine/render.ts @@ -0,0 +1,46 @@ +import { + ComponentType, + PositionComponent, + TrailingPositionComponent, +} from "./component"; +import { Game } from "./game"; +import { System, SystemType } from "./system"; +import { drawLaserPen } from "laser-pen"; + +export class RenderSystem extends System { + constructor(private readonly canvas: HTMLCanvasElement) { + super(SystemType.RENDER); + } + + public set_world_dimensions(width: number, height: number) { + this.canvas.width = width; + this.canvas.height = height; + } + + public update(_dt: number, game: Game) { + const ctx = this.canvas.getContext("2d"); + if (ctx === null) return; + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + game.for_each_entity_with_component(ComponentType.RENDERABLE, (entity) => { + if (ComponentType.TRAILING_POSITION in entity.components) { + const trailing_position = entity.components[ + ComponentType.TRAILING_POSITION + ] as TrailingPositionComponent; + if (trailing_position.trails.length < 3) return; + drawLaserPen(ctx, trailing_position.trails); + return; + } + + if (ComponentType.POSITION in entity.components) { + const position = entity.components[ + ComponentType.POSITION + ] as PositionComponent; + ctx.beginPath(); + ctx.arc(position.x, position.y, 50, 0, 2 * Math.PI); + ctx.stroke(); + return; + } + }); + } +} diff --git a/static/src/engine/system.ts b/static/src/engine/system.ts new file mode 100644 index 0000000..e19cf5a --- /dev/null +++ b/static/src/engine/system.ts @@ -0,0 +1,14 @@ +import { Game } from "./game"; + +export enum SystemType { + INPUT = "INPUT", + NETWORK = "NETWORK", + RENDER = "RENDER", + TRAILING_POSITION = "TRAILING_POSITION", +} + +export abstract class System { + constructor(public readonly system_type: SystemType) {} + + abstract update(dt: number, game: Game): void; +} diff --git a/static/src/engine/trailing_position.ts b/static/src/engine/trailing_position.ts new file mode 100644 index 0000000..1ea22d3 --- /dev/null +++ b/static/src/engine/trailing_position.ts @@ -0,0 +1,33 @@ +import { ComponentType, TrailingPositionComponent } from "./component"; +import { Game } from "./game"; +import { System, SystemType } from "./system"; + +export class TrailingPositionSystem extends System { + constructor( + private readonly point_filter: ( + trail_point: { + x: number; + y: number; + time: number; + }[], + ) => { + x: number; + y: number; + time: number; + }[], + ) { + super(SystemType.TRAILING_POSITION); + } + + public update(_dt: number, game: Game) { + game.for_each_entity_with_component( + ComponentType.TRAILING_POSITION, + (entity) => { + const trailing_position = entity.components[ + ComponentType.TRAILING_POSITION + ] as TrailingPositionComponent; + trailing_position.trails = this.point_filter(trailing_position.trails); + }, + ); + } +} diff --git a/static/src/entity.ts b/static/src/entity.ts deleted file mode 100644 index 811d05a..0000000 --- a/static/src/entity.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Component } from "./component"; - -export enum EntityType { - LASER = "LASER", -} - -export interface Entity { - entity_type: EntityType; - id: string; - components: Record; -} diff --git a/static/src/main.ts b/static/src/main.ts index c3588de..c11e41e 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -1,95 +1,16 @@ import $ from "jquery"; -import { Vec2 } from "./vector"; -import { MouseController } from "./mouse_controller"; import { - EntityPositionUpdateEvent, EventPublisher, EventQueue, - EventType, - SetControllableEvent, WebsocketEventPublisher, WebSocketEventQueue, -} from "./network"; - -class KennelClient { - private running: boolean; - private last_update: number; - - private controllable_entities: Set = new Set(); - private mouse_controller: MouseController; - - constructor( - private readonly client_id: string, - private readonly event_queue: EventQueue, - private readonly event_publisher: EventPublisher, - ) { - this.last_update = 0; - this.running = false; - - this.mouse_controller = new MouseController((position: Vec2) => - this.on_mouse_move(position), - ); - } - - public start() { - this.running = true; - this.last_update = performance.now(); - this.mouse_controller.start(); - - const loop = (timestamp: number) => { - if (!this.running) return; - const dt = timestamp - this.last_update; - this.propogate_state_after(dt); - requestAnimationFrame(loop); // tail call recursion! /s - }; - requestAnimationFrame(loop); - - $(document).on("mousemove", (event) => { - this.mouse_controller.move(event.clientX, event.clientY); - }); - } - - public close() { - this.running = false; - this.mouse_controller.stop(); - this.controllable_entities.clear(); - $(document).off("mousemove"); - } - - private propogate_state_after(dt: number) { - const events = this.event_queue.peek(); - for (const event of events) { - switch (event.event_type) { - case EventType.SET_CONTROLLABLE: - this.process_set_controllable_event(event as SetControllableEvent); - break; - } - } - if (events.length > 0) { - console.log(events, dt); - } - this.event_queue.clear(); - this.event_publisher.publish(); - } - - private process_set_controllable_event(event: SetControllableEvent) { - if (event.data.client_id !== this.client_id) { - console.warn("got controllable event for client that is not us"); - return; - } - this.controllable_entities.add(event.data.id); - } - - private on_mouse_move(position: Vec2) { - for (const id of this.controllable_entities) { - const event: EntityPositionUpdateEvent = { - event_type: EventType.ENTITY_POSITION_UPDATE, - data: { id, position: { x: position.x, y: position.y } }, - }; - this.event_publisher.add(event); - } - } -} +} from "./engine/events"; +import { Game } from "./engine/game"; +import { NetworkSystem } from "./engine/network"; +import { RenderSystem } from "./engine/render"; +import { InputSystem } from "./engine/input"; +import { TrailingPositionSystem } from "./engine/trailing_position"; +import { drainPoints, setDelay } from "laser-pen"; $(async () => { const client_id = await fetch("/assign", { @@ -105,8 +26,25 @@ $(async () => { const queue: EventQueue = new WebSocketEventQueue(ws); const publisher: EventPublisher = new WebsocketEventPublisher(ws); - const kennel_client = new KennelClient(client_id, queue, publisher); - ws.onclose = () => kennel_client.close(); + const network_system = new NetworkSystem(queue, publisher); - kennel_client.start(); + const gamecanvas = $("#gamecanvas").get(0)! as HTMLCanvasElement; + const input_system = new InputSystem(publisher, gamecanvas); + + setDelay(500); + const render_system = new RenderSystem(gamecanvas); + + const trailing_position = new TrailingPositionSystem(drainPoints); + + const systems = [ + network_system, + trailing_position, + input_system, + render_system, + ]; + + const game = new Game(client_id, systems); + ws.onclose = () => game.stop(); + + game.start(); }); diff --git a/static/src/vector.ts b/static/src/vector.ts deleted file mode 100644 index 29e62df..0000000 --- a/static/src/vector.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class Vec2 { - constructor( - public readonly x: number, - public readonly y: number, - ) {} - - public distance_to(that: Vec2): number { - return Math.sqrt((this.x - that.x) ** 2 + (this.y - that.y) ** 2); - } - - public copy(): Vec2 { - return new Vec2(this.x, this.y); - } -} -- 2.40.1 From f7e7121fce7bd3f7fdabe836955e43b3d194c1c6 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 14 Sep 2024 22:33:35 -0700 Subject: [PATCH 12/13] add a spritesheet component --- kennel/engine/components/component.py | 1 + kennel/engine/components/sprite_sheet.py | 48 ++++++++++++++++++++++++ kennel/engine/entities/cat.py | 35 +++++++++++------ kennel/kennel.py | 5 +-- kennel/kennelcats.py | 5 ++- static/src/engine/component.ts | 22 +++++++++++ static/src/engine/debounce_publisher.ts | 2 +- static/src/engine/entity.ts | 6 --- static/src/main.ts | 3 +- 9 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 kennel/engine/components/sprite_sheet.py diff --git a/kennel/engine/components/component.py b/kennel/engine/components/component.py index 90506b9..7fd7757 100644 --- a/kennel/engine/components/component.py +++ b/kennel/engine/components/component.py @@ -6,6 +6,7 @@ class ComponentType(str, Enum): POSITION = "POSITION" CONTROLLABLE = "CONTROLLABLE" MARKOV = "MARKOV" + SPRITESHEET = "SPRITESHEET" class Component: diff --git a/kennel/engine/components/sprite_sheet.py b/kennel/engine/components/sprite_sheet.py new file mode 100644 index 0000000..5730085 --- /dev/null +++ b/kennel/engine/components/sprite_sheet.py @@ -0,0 +1,48 @@ +from .component import Component, ComponentType + + +class SpriteSpec: + def __init__( + self, + ms_per_frame: int, + top_x: int, + top_y: int, + end_x: int, + end_y: int, + frames: int, + ): + self.ms_per_frame = ms_per_frame + self.frames = frames + self.top_x = top_x + self.top_y = top_y + self.end_x = end_x + self.end_y = end_y + + +class SpriteSheet(Component): + def __init__( + self, + source: str, + state_to_spritespec: dict[str, SpriteSpec], + initial_state: str, + ): + super().__init__(ComponentType.SPRITESHEET) + self.source = source + self.state_to_spritespec = state_to_spritespec + + # these are only really used for client initialization + self.initial_state = initial_state + self.current_frame = 0 + self.last_update = 0 + + def __repr__(self) -> str: + return f"SpriteSheet(source={self.source}, state_to_spritespec={self.state_to_spritespec}, initial_state={self.initial_state}, current_frame={self.current_frame}, last_update={self.last_update})" + + def to_dict(self) -> dict: + return { + "source": self.source, + "current_frame": self.current_frame, + "last_update": self.last_update, + "current_state": self.initial_state, + "state_to_spritespec": self.state_to_spritespec, + } diff --git a/kennel/engine/entities/cat.py b/kennel/engine/entities/cat.py index edcde37..b5a8c6c 100644 --- a/kennel/engine/entities/cat.py +++ b/kennel/engine/entities/cat.py @@ -1,14 +1,33 @@ from kennel.engine.components.position import Position - +from kennel.engine.components.sprite_sheet import SpriteSheet, SpriteSpec +from enum import Enum from .entity import Entity, EntityType +class CatState(Enum): + IDLE = "IDLE" + FROLICKING = "FROLICKING" + EEPY = "EEPY" + ALERT = "ALERT" + CHASING_CURSOR = "CHASING_CURSOR" + CHASING_CAT = "CHASING_CAT" + SCRATCHING = "SCRATCHING" + ITCHY = "ITCHY" + + class Cat(Entity): - def __init__(self, id: str): - components = [Position(0, 0)] + def __init__(self, id: str, spritesheet_source: str): + state_to_spritespec = self.get_state_to_spritespec() + components = [ + Position(0, 0), + SpriteSheet(spritesheet_source, state_to_spritespec, CatState.ALERT), + ] super().__init__(EntityType.CAT, id, components) + def get_state_to_spritespec(self): + return {CatState.ALERT: SpriteSpec(100, 0, 0, 200, 40, 5)} + # # # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY @@ -32,14 +51,6 @@ class Cat(Entity): # ] # # -# class CatState(Enum): -# IDLE = 0 -# FROLICKING = 1 -# EEPY = 2 -# ALERT = 3 -# CHASING_CURSOR = 4 -# CHASING_CAT = 5 -# SCRATCHING = 6 -# ITCHY = 7 + # # diff --git a/kennel/kennel.py b/kennel/kennel.py index 7123b7f..6d4f8f9 100644 --- a/kennel/kennel.py +++ b/kennel/kennel.py @@ -104,8 +104,7 @@ class KennelCatsManager: removed_cats = [cats_table[id] for id in self.last_seen.difference(cat_ids)] added_cats = [cats_table[id] for id in cat_ids.difference(self.last_seen)] - logger.info(f"removing {removed_cats}") - logger.info(f"adding {added_cats}") + logger.info(f"removing {removed_cats}, adding {added_cats}") for removed in removed_cats: self.entity_manager.remove_entity(removed) @@ -113,7 +112,7 @@ class KennelCatsManager: self.network_system.server_global_event(entity_death) for added in added_cats: - new_cat = Cat(added.id) + new_cat = Cat(added.id, added.spritesheet) self.entity_manager.add_entity(new_cat) entity_born = EntityBornEvent(new_cat) self.network_system.server_global_event(entity_born) diff --git a/kennel/kennelcats.py b/kennel/kennelcats.py index 2cb2f29..d354dad 100644 --- a/kennel/kennelcats.py +++ b/kennel/kennelcats.py @@ -43,4 +43,7 @@ class KennelCatService: def get_kennel(self) -> List[KennelCat]: response = requests.get(f"{self.hc_endpoint}/kennel") response.raise_for_status() - return [KennelCat.from_dict(cat) for cat in response.json()] + cats = [KennelCat.from_dict(cat) for cat in response.json()] + for cat in cats: + cat.spritesheet = self.hc_endpoint + cat.spritesheet + return cats diff --git a/static/src/engine/component.ts b/static/src/engine/component.ts index 9607fec..a16215b 100644 --- a/static/src/engine/component.ts +++ b/static/src/engine/component.ts @@ -2,6 +2,7 @@ export enum ComponentType { POSITION = "POSITION", RENDERABLE = "RENDERABLE", TRAILING_POSITION = "TRAILING_POSITION", + SPRITESHEET = "SPRITESHEET", } export interface Component { @@ -22,3 +23,24 @@ export interface TrailingPositionComponent extends Component { export interface RenderableComponent extends Component { name: ComponentType.RENDERABLE; } + +export interface SpriteSpec { + ms_per_frame: number; + frames: number; + top_x: number; + top_y: number; + end_x: number; + end_y: number; +} + +export interface SpriteSheetComponent extends Component { + name: ComponentType.SPRITESHEET; + source: string; + sheet: HTMLImageElement; + + current_frame: number; + last_update: number; + current_state: string; + + state_to_spritespec: Record; +} diff --git a/static/src/engine/debounce_publisher.ts b/static/src/engine/debounce_publisher.ts index 8ee4bb0..bc5e95f 100644 --- a/static/src/engine/debounce_publisher.ts +++ b/static/src/engine/debounce_publisher.ts @@ -5,7 +5,7 @@ export class DebouncePublisher { constructor( private readonly publisher: (data: T) => void | Promise, - private readonly debounce_ms = 100, + private readonly debounce_ms = 150, ) {} public start() { diff --git a/static/src/engine/entity.ts b/static/src/engine/entity.ts index 2c8d38e..87c9a67 100644 --- a/static/src/engine/entity.ts +++ b/static/src/engine/entity.ts @@ -1,7 +1,6 @@ import { Component, ComponentType, - PositionComponent, RenderableComponent, TrailingPositionComponent, } from "./component"; @@ -36,10 +35,5 @@ export const create_cat = (base: Entity) => { name: ComponentType.RENDERABLE, }; base.components[ComponentType.RENDERABLE] = renderable; - base.components[ComponentType.POSITION] = { - component: ComponentType.POSITION, - x: Math.random() * 1_000, - y: Math.random() * 1_000, - } as unknown as PositionComponent; // TODO: hack return base; }; diff --git a/static/src/main.ts b/static/src/main.ts index c11e41e..cc6f42c 100644 --- a/static/src/main.ts +++ b/static/src/main.ts @@ -10,7 +10,7 @@ import { NetworkSystem } from "./engine/network"; import { RenderSystem } from "./engine/render"; import { InputSystem } from "./engine/input"; import { TrailingPositionSystem } from "./engine/trailing_position"; -import { drainPoints, setDelay } from "laser-pen"; +import { drainPoints, setDelay, setRoundCap } from "laser-pen"; $(async () => { const client_id = await fetch("/assign", { @@ -32,6 +32,7 @@ $(async () => { const input_system = new InputSystem(publisher, gamecanvas); setDelay(500); + setRoundCap(true); const render_system = new RenderSystem(gamecanvas); const trailing_position = new TrailingPositionSystem(drainPoints); -- 2.40.1 From 8ac11eda05e7159a7f762d3396cf7bb8ee51534b Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 15 Sep 2024 17:38:02 -0700 Subject: [PATCH 13/13] render the spritesheet spec in the render system --- kennel/engine/components/sprite_sheet.py | 17 ++++- kennel/engine/entities/cat.py | 35 ++++++++-- static/src/engine/component.ts | 2 +- static/src/engine/debounce_publisher.ts | 2 +- static/src/engine/render.ts | 84 ++++++++++++++++++++++-- static/src/engine/trailing_position.ts | 20 +++--- 6 files changed, 136 insertions(+), 24 deletions(-) diff --git a/kennel/engine/components/sprite_sheet.py b/kennel/engine/components/sprite_sheet.py index 5730085..3a01f5b 100644 --- a/kennel/engine/components/sprite_sheet.py +++ b/kennel/engine/components/sprite_sheet.py @@ -18,6 +18,19 @@ class SpriteSpec: self.end_x = end_x self.end_y = end_y + def to_dict(self) -> dict: + return { + "ms_per_frame": self.ms_per_frame, + "top_x": self.top_x, + "top_y": self.top_y, + "end_x": self.end_x, + "end_y": self.end_y, + "frames": self.frames, + } + + def __repr__(self) -> str: + return f"SpriteSpec(ms_per_frame={self.ms_per_frame}, top_x={self.top_x}, top_y={self.top_y}, end_x={self.end_x}, end_y={self.end_y}, frames={self.frames})" + class SpriteSheet(Component): def __init__( @@ -44,5 +57,7 @@ class SpriteSheet(Component): "current_frame": self.current_frame, "last_update": self.last_update, "current_state": self.initial_state, - "state_to_spritespec": self.state_to_spritespec, + "state_to_spritespec": { + k: v.to_dict() for k, v in self.state_to_spritespec.items() + }, } diff --git a/kennel/engine/entities/cat.py b/kennel/engine/entities/cat.py index b5a8c6c..c0acfd8 100644 --- a/kennel/engine/entities/cat.py +++ b/kennel/engine/entities/cat.py @@ -4,7 +4,7 @@ from enum import Enum from .entity import Entity, EntityType -class CatState(Enum): +class CatState(str, Enum): IDLE = "IDLE" FROLICKING = "FROLICKING" EEPY = "EEPY" @@ -13,20 +13,47 @@ class CatState(Enum): CHASING_CAT = "CHASING_CAT" SCRATCHING = "SCRATCHING" ITCHY = "ITCHY" + MAKING_BISCUITS = "MAKING_BISCUITS" + + +class CatSpriteState(str, Enum): + ALERT = "ALERT" + MAKING_BISCUITS = "MAKING_BISCUITS" class Cat(Entity): def __init__(self, id: str, spritesheet_source: str): state_to_spritespec = self.get_state_to_spritespec() components = [ - Position(0, 0), - SpriteSheet(spritesheet_source, state_to_spritespec, CatState.ALERT), + Position(50, 50), + SpriteSheet( + spritesheet_source, state_to_spritespec, CatSpriteState.MAKING_BISCUITS + ), ] super().__init__(EntityType.CAT, id, components) def get_state_to_spritespec(self): - return {CatState.ALERT: SpriteSpec(100, 0, 0, 200, 40, 5)} + creature_width = 32 + creature_height = 32 + return { + CatSpriteState.ALERT: SpriteSpec( + 100, + creature_width * 7, + creature_height * 3, + creature_width * 8, + creature_height * 4, + 1, + ), + CatSpriteState.MAKING_BISCUITS: SpriteSpec( + 300, + 0, + 0, + creature_width, + creature_height * 2, + 2, + ), + } # diff --git a/static/src/engine/component.ts b/static/src/engine/component.ts index a16215b..eb6fdd6 100644 --- a/static/src/engine/component.ts +++ b/static/src/engine/component.ts @@ -36,7 +36,7 @@ export interface SpriteSpec { export interface SpriteSheetComponent extends Component { name: ComponentType.SPRITESHEET; source: string; - sheet: HTMLImageElement; + sheet?: HTMLImageElement; current_frame: number; last_update: number; diff --git a/static/src/engine/debounce_publisher.ts b/static/src/engine/debounce_publisher.ts index bc5e95f..8ee4bb0 100644 --- a/static/src/engine/debounce_publisher.ts +++ b/static/src/engine/debounce_publisher.ts @@ -5,7 +5,7 @@ export class DebouncePublisher { constructor( private readonly publisher: (data: T) => void | Promise, - private readonly debounce_ms = 150, + private readonly debounce_ms = 100, ) {} public start() { diff --git a/static/src/engine/render.ts b/static/src/engine/render.ts index 8f0343a..4ff1ec0 100644 --- a/static/src/engine/render.ts +++ b/static/src/engine/render.ts @@ -2,6 +2,8 @@ import { ComponentType, PositionComponent, TrailingPositionComponent, + SpriteSheetComponent, + SpriteSpec, } from "./component"; import { Game } from "./game"; import { System, SystemType } from "./system"; @@ -17,7 +19,7 @@ export class RenderSystem extends System { this.canvas.height = height; } - public update(_dt: number, game: Game) { + public update(dt: number, game: Game) { const ctx = this.canvas.getContext("2d"); if (ctx === null) return; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); @@ -32,15 +34,87 @@ export class RenderSystem extends System { return; } - if (ComponentType.POSITION in entity.components) { + if ( + ComponentType.POSITION in entity.components && + ComponentType.SPRITESHEET in entity.components + ) { const position = entity.components[ ComponentType.POSITION ] as PositionComponent; - ctx.beginPath(); - ctx.arc(position.x, position.y, 50, 0, 2 * Math.PI); - ctx.stroke(); + const spritesheet = entity.components[ + ComponentType.SPRITESHEET + ] as SpriteSheetComponent; + + if (typeof spritesheet.sheet === "undefined") { + const img = new Image(); + img.src = spritesheet.source; + spritesheet.sheet = img; + } + const spritespec = + spritesheet.state_to_spritespec[spritesheet.current_state]; + + this.blit_sprite(ctx, spritesheet, position); + + spritesheet.last_update += dt; + if (spritesheet.last_update > spritespec.ms_per_frame) { + spritesheet.current_frame++; + spritesheet.current_frame %= spritespec.frames; + spritesheet.last_update = 0; + } + return; } }); } + + private blit_sprite( + ctx: CanvasRenderingContext2D, + spritesheet: SpriteSheetComponent, + position: { x: number; y: number }, + ) { + ctx.save(); + ctx.translate(position.x, position.y); + ctx.translate(-position.x, -position.y); + + if (typeof spritesheet.sheet === "undefined") return; + + const spritespec = + spritesheet.state_to_spritespec[spritesheet.current_state]; + + ctx.drawImage( + spritesheet.sheet, + ...this.get_sprite_args(spritesheet.current_frame, spritespec), + ...this.get_draw_args(spritespec, position), + ); + + ctx.restore(); + } + + private get_sprite_args( + current_frame: number, + sprite_spec: SpriteSpec, + ): [sx: number, sy: number, sw: number, sh: number] { + const [width, height] = this.get_dimensions(sprite_spec); + return [ + sprite_spec.top_x, + sprite_spec.top_y + current_frame * height, + width, + height, + ]; + } + + private get_draw_args( + sprite_spec: SpriteSpec, + position: { x: number; y: number }, + ): [dx: number, dy: number, dw: number, dh: number] { + const [width, height] = this.get_dimensions(sprite_spec); + return [position.x - width / 2, position.y - height / 2, width, height]; + } + + private get_dimensions(sprite_spec: SpriteSpec): [number, number] { + return [ + sprite_spec.end_x - sprite_spec.top_x, + (sprite_spec.end_y - sprite_spec.top_y) / sprite_spec.frames, + ]; + } } diff --git a/static/src/engine/trailing_position.ts b/static/src/engine/trailing_position.ts index 1ea22d3..5b41682 100644 --- a/static/src/engine/trailing_position.ts +++ b/static/src/engine/trailing_position.ts @@ -2,19 +2,15 @@ import { ComponentType, TrailingPositionComponent } from "./component"; import { Game } from "./game"; import { System, SystemType } from "./system"; +interface Point { + x: number; + y: number; + time: number; +} + export class TrailingPositionSystem extends System { constructor( - private readonly point_filter: ( - trail_point: { - x: number; - y: number; - time: number; - }[], - ) => { - x: number; - y: number; - time: number; - }[], + private readonly point_filter: (trail_point: Array) => Array ) { super(SystemType.TRAILING_POSITION); } @@ -27,7 +23,7 @@ export class TrailingPositionSystem extends System { ComponentType.TRAILING_POSITION ] as TrailingPositionComponent; trailing_position.trails = this.point_filter(trailing_position.trails); - }, + } ); } } -- 2.40.1