├── src ├── __init__.py ├── common │ ├── __init__.py │ ├── settings.py │ └── logger.py ├── domain │ ├── __init__.py │ ├── hello_client.py │ ├── exceptions.py │ └── command.py ├── delivery │ ├── __init__.py │ └── api │ │ └── v1 │ │ ├── __init__.py │ │ ├── health │ │ ├── __init__.py │ │ ├── health_response.py │ │ └── health_router.py │ │ └── hello │ │ ├── __init__.py │ │ ├── hello_response.py │ │ └── hello_router.py ├── use_cases │ ├── __init__.py │ ├── health_command.py │ └── say_hello_command.py └── infrastructure │ ├── __init__.py │ └── hello │ ├── __init__.py │ └── hello_client.py ├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── acceptance │ │ ├── __init__.py │ │ └── delivery │ │ │ ├── __init__.py │ │ │ └── api │ │ │ ├── __init__.py │ │ │ └── test_hello_controller.py │ └── use_cases │ │ ├── __init__.py │ │ ├── test_health_command.py │ │ └── test_say_hello_command.py ├── acceptance │ ├── __init__.py │ └── delivery │ │ ├── __init__.py │ │ └── api │ │ ├── __init__.py │ │ ├── test_heath_controller.py │ │ └── test_hello_controller.py └── integration │ ├── __init__.py │ └── hello │ ├── __init__.py │ └── test_dummy_hello_client_integration.py ├── .python-version ├── scripts ├── hooks │ ├── pre-push │ ├── pre-commit │ └── post-merge ├── local-setup.sh └── pre-requirements.sh ├── .github ├── dependabot.yml └── workflows │ └── app.yml ├── pyproject.toml ├── Dockerfile ├── main.py ├── ruff.toml ├── Makefile ├── README.md ├── .gitignore └── uv.lock /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12.8 2 | -------------------------------------------------------------------------------- /src/delivery/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/use_cases/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/acceptance/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/delivery/api/v1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/infrastructure/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/acceptance/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/use_cases/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/delivery/api/v1/health/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/delivery/api/v1/hello/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/infrastructure/hello/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/acceptance/delivery/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/hello/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/acceptance/delivery/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/acceptance/delivery/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/acceptance/delivery/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | exec < /dev/tty 5 | 6 | make pre-push 7 | -------------------------------------------------------------------------------- /scripts/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | exec < /dev/tty 5 | 6 | make pre-commit 7 | -------------------------------------------------------------------------------- /src/delivery/api/v1/health/health_response.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class HealthResponse(BaseModel): 5 | ok: bool 6 | -------------------------------------------------------------------------------- /src/delivery/api/v1/hello/hello_response.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class HelloResponse(BaseModel): 5 | message: str 6 | -------------------------------------------------------------------------------- /src/domain/hello_client.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class HelloClient(ABC): 5 | @abstractmethod 6 | def get(self, name: str) -> str: 7 | raise NotImplementedError 8 | -------------------------------------------------------------------------------- /scripts/local-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | function main { 5 | install_git_hooks 6 | } 7 | 8 | function install_git_hooks { 9 | echo "Installing git hooks..." 10 | git config core.hooksPath scripts/hooks 11 | } 12 | 13 | main 14 | -------------------------------------------------------------------------------- /src/domain/exceptions.py: -------------------------------------------------------------------------------- 1 | class SayHelloClientException(Exception): 2 | def __init__(self, name: str) -> None: 3 | super().__init__(f"'{name}' invalid name") 4 | 5 | 6 | class SayHelloCommandHandlerException(Exception): 7 | def __init__(self, error: str) -> None: 8 | super().__init__(error) 9 | -------------------------------------------------------------------------------- /src/infrastructure/hello/hello_client.py: -------------------------------------------------------------------------------- 1 | from src.domain.exceptions import SayHelloClientException 2 | from src.domain.hello_client import HelloClient 3 | 4 | 5 | class DummyHelloClient(HelloClient): 6 | def get(self, name: str) -> str: 7 | if name.startswith("Error"): 8 | raise SayHelloClientException(name) 9 | 10 | return name 11 | -------------------------------------------------------------------------------- /src/common/settings.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings 2 | 3 | 4 | class Settings(BaseSettings): 5 | openapi_url: str = "/openapi.json" 6 | api_v1_prefix: str = "/api/v1" 7 | project_name: str = "FastAPI Template" 8 | description: str = "This is the documentation of the FastAPI template project." 9 | logger_name: str = "server" 10 | 11 | 12 | settings = Settings() 13 | -------------------------------------------------------------------------------- /scripts/hooks/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://git-scm.com/docs/githooks#_post_merge 3 | 4 | changed_files="$(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)" 5 | 6 | detect_changes() { 7 | files_to_search="Dockerfile\|pyproject.toml\|uv.lock" 8 | echo "$changed_files" | grep --quiet $files_to_search && echo " * changes detected in $1" && echo " * running $2" && make build && make install 9 | } 10 | 11 | detect_changes 12 | -------------------------------------------------------------------------------- /tests/unit/use_cases/test_health_command.py: -------------------------------------------------------------------------------- 1 | from expects import be_true, expect 2 | 3 | from src.use_cases.health_command import HealthCommand, HealthCommandHandler 4 | 5 | 6 | class TestHealthCommandHandler: 7 | def test_execute(self) -> None: 8 | command = HealthCommand() 9 | handler = HealthCommandHandler() 10 | 11 | response = handler.execute(command) 12 | 13 | expect(response.message()).to(be_true) 14 | -------------------------------------------------------------------------------- /src/domain/command.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from abc import ABC, abstractmethod 3 | from typing import Any 4 | 5 | 6 | class Command: 7 | def __init__(self) -> None: 8 | self.command_id = uuid.uuid1() 9 | 10 | 11 | class CommandResponse(ABC): 12 | @abstractmethod 13 | def message(self) -> Any: # noqa: ANN401 14 | raise NotImplementedError 15 | 16 | 17 | class CommandHandler(ABC): 18 | @abstractmethod 19 | def execute(self, command: Command) -> CommandResponse: 20 | raise NotImplementedError 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "uv" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /src/common/logger.py: -------------------------------------------------------------------------------- 1 | from logging import INFO, Formatter, Logger, StreamHandler, getLogger 2 | 3 | from src.common.settings import settings 4 | 5 | 6 | def setup_logging() -> Logger: 7 | log_format = "%(levelname)s: %(asctime)s - %(message)s" 8 | formatter = Formatter(log_format) 9 | 10 | handler = StreamHandler() 11 | handler.setLevel(INFO) 12 | handler.setFormatter(formatter) 13 | 14 | _logger = getLogger(settings.logger_name) 15 | _logger.setLevel(INFO) 16 | _logger.addHandler(handler) 17 | return _logger 18 | 19 | 20 | logger = setup_logging() 21 | -------------------------------------------------------------------------------- /scripts/pre-requirements.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function uv_is_not_installed() { 4 | if ! command -v uv &> /dev/null; then 5 | echo "uv is not installed. Please go to https://docs.astral.sh/uv/getting-started/installation/." 6 | return 1 7 | fi 8 | return 0 9 | } 10 | 11 | function docker_is_not_installed() { 12 | if ! command -v docker &> /dev/null; then 13 | echo "docker is not installed. Please go to https://docs.docker.com/get-docker/." 14 | return 1 15 | fi 16 | return 0 17 | } 18 | 19 | uv_is_not_installed 20 | docker_is_not_installed 21 | -------------------------------------------------------------------------------- /tests/acceptance/delivery/api/test_heath_controller.py: -------------------------------------------------------------------------------- 1 | from http.client import OK 2 | 3 | import pytest 4 | from expects import equal, expect 5 | from fastapi.testclient import TestClient 6 | 7 | from main import app, settings 8 | 9 | 10 | class TestHealthControllerAcceptance: 11 | @pytest.fixture 12 | def client(self) -> TestClient: 13 | return TestClient(app) 14 | 15 | def test_health_controller(self, client: TestClient) -> None: 16 | response = client.get(f"{settings.api_v1_prefix}/health") 17 | 18 | expect(response.status_code).to(equal(OK)) 19 | expect(response.json()).to(equal({"ok": True})) 20 | -------------------------------------------------------------------------------- /tests/acceptance/delivery/api/test_hello_controller.py: -------------------------------------------------------------------------------- 1 | from http.client import OK 2 | 3 | import pytest 4 | from expects import equal, expect 5 | from fastapi.testclient import TestClient 6 | 7 | from main import app, settings 8 | 9 | 10 | class TestHelloControllerAcceptance: 11 | @pytest.fixture 12 | def client(self) -> TestClient: 13 | return TestClient(app) 14 | 15 | def test_hello_controller(self, client: TestClient) -> None: 16 | name = "peter" 17 | 18 | response = client.get(f"{settings.api_v1_prefix}/hello/{name}") 19 | 20 | expect(response.status_code).to(equal(OK)) 21 | expect(response.json()).to(equal({"message": f"Hello, {name}!"})) 22 | -------------------------------------------------------------------------------- /.github/workflows/app.yml: -------------------------------------------------------------------------------- 1 | name: Pass checks and tests 2 | on: [push, pull_request] 3 | permissions: 4 | contents: read 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Set up Python 3.12 11 | uses: actions/setup-python@v3 12 | with: 13 | python-version: "3.12" 14 | - name: Install uv 15 | uses: astral-sh/setup-uv@v5 16 | - name: Install dependencies 17 | run: make install 18 | - name: Activate virtualenv 19 | run: | 20 | . .venv/bin/activate 21 | echo PATH=$PATH >> $GITHUB_ENV 22 | - name: Check style, format and lint 23 | run: make checks 24 | - name: Tests 25 | run: make test 26 | -------------------------------------------------------------------------------- /src/delivery/api/v1/health/health_router.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from src.delivery.api.v1.health.health_response import HealthResponse 4 | from src.domain.command import CommandHandler 5 | from src.use_cases.health_command import HealthCommand, HealthCommandHandler 6 | 7 | health: APIRouter = APIRouter() 8 | 9 | 10 | async def health_command_handler() -> CommandHandler: 11 | return HealthCommandHandler() 12 | 13 | 14 | @health.get("/health", response_model=HealthResponse) 15 | def get(handler: CommandHandler = Depends(health_command_handler)) -> HealthResponse: 16 | command = HealthCommand() 17 | response = handler.execute(command) 18 | ok = response.message() 19 | return HealthResponse(ok=ok) 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "fastapi-boilerplate" 3 | version = "0.1.0" 4 | description = "This repository is a FastAPI boilerplate to use as a fast starter point" 5 | authors = [ 6 | { name = "Pedro López Mareque", email = "pedro.lopez.mareque@gmail.com" } 7 | ] 8 | requires-python = "==3.12.8" 9 | readme = "README.md" 10 | license = "MIT" 11 | dependencies = [ 12 | "fastapi[standard]>=0.115.0,<0.116", 13 | "pydantic-settings>=2.5.2,<3", 14 | ] 15 | 16 | [dependency-groups] 17 | dev = [ 18 | "pytest>=8.3.3,<9", 19 | "pytest-watch>=4.2.0,<5", 20 | "expects>=0.9.0,<0.10", 21 | "doublex>=1.9.6.1,<2", 22 | "coverage>=7.6.1,<8", 23 | "ruff>=0.6.7,<0.7", 24 | "ty>=0.0.1a6", 25 | ] 26 | 27 | [tool.uv] 28 | default-groups = ["dev"] 29 | -------------------------------------------------------------------------------- /tests/integration/hello/test_dummy_hello_client_integration.py: -------------------------------------------------------------------------------- 1 | from expects import equal, expect, raise_error 2 | 3 | from src.domain.exceptions import SayHelloClientException 4 | from src.infrastructure.hello.hello_client import DummyHelloClient 5 | 6 | 7 | class TestDummyHelloClientIntegration: 8 | def test_get_name(self) -> None: 9 | expected_name = "Yes" 10 | client = DummyHelloClient() 11 | 12 | name = client.get(expected_name) 13 | 14 | expect(name).to(equal(expected_name)) 15 | 16 | def test_raise_error(self) -> None: 17 | expected_name = "Error" 18 | client = DummyHelloClient() 19 | 20 | error_message = f"'{expected_name}' invalid name" 21 | expect(lambda: client.get(expected_name)).to(raise_error(SayHelloClientException, error_message)) 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ################################### 2 | # BUILDER # 3 | ################################### 4 | 5 | FROM python:3.12.8-alpine AS builder 6 | 7 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 8 | 9 | WORKDIR /code 10 | 11 | RUN --mount=type=cache,target=/root/.cache/uv \ 12 | --mount=type=bind,source=uv.lock,target=uv.lock \ 13 | --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ 14 | uv sync --locked --no-install-project --no-group dev 15 | 16 | ################################### 17 | # RUNTIME # 18 | ################################### 19 | 20 | FROM python:3.12.8-alpine 21 | 22 | ENV PATH="/code/.venv/bin:$PATH" 23 | 24 | WORKDIR /code 25 | 26 | COPY --from=builder /code/.venv /code/.venv 27 | 28 | COPY main.py /code/main.py 29 | 30 | COPY src /code/src 31 | 32 | EXPOSE 8000 33 | 34 | ENTRYPOINT ["fastapi", "run"] 35 | -------------------------------------------------------------------------------- /src/use_cases/health_command.py: -------------------------------------------------------------------------------- 1 | from logging import Logger 2 | 3 | from src.common.logger import logger 4 | from src.domain.command import Command, CommandHandler, CommandResponse 5 | 6 | 7 | class HealthCommand(Command): 8 | def __init__(self) -> None: 9 | super().__init__() 10 | 11 | 12 | class HealthCommandResponse(CommandResponse): 13 | def message(self) -> bool: 14 | return True 15 | 16 | 17 | class HealthCommandHandler(CommandHandler): 18 | def __init__(self, _logger: Logger = logger) -> None: 19 | self._logger = _logger 20 | 21 | def execute(self, command: HealthCommand) -> HealthCommandResponse: 22 | command_id = command.command_id 23 | self._logger.info(f"Command {command_id}: HealthCommandHandler#execute") 24 | 25 | response = HealthCommandResponse() 26 | message = response.message() 27 | self._logger.info(f"Command {command_id}: HealthCommandResponse {message}") 28 | return response 29 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from collections.abc import AsyncGenerator 2 | from contextlib import asynccontextmanager 3 | from time import sleep 4 | 5 | from fastapi import FastAPI 6 | 7 | from src.common.logger import logger 8 | from src.common.settings import settings 9 | from src.delivery.api.v1.health.health_router import health 10 | from src.delivery.api.v1.hello.hello_router import hello 11 | 12 | 13 | @asynccontextmanager 14 | async def lifespan(_app: FastAPI) -> AsyncGenerator: 15 | logger.info("Starting FastAPI server...") 16 | 17 | yield 18 | 19 | # Graceful shutdown 20 | sleep(5) # wait for the app to finish processing requests 21 | logger.info("FastAPI server finished!") 22 | 23 | 24 | app = FastAPI( 25 | title=settings.project_name, 26 | description=settings.description, 27 | lifespan=lifespan, 28 | openapi_url=settings.openapi_url, 29 | ) 30 | 31 | app.include_router(prefix=settings.api_v1_prefix, router=health) 32 | app.include_router(prefix=settings.api_v1_prefix, router=hello) 33 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 120 2 | 3 | [lint] 4 | select = [ 5 | "ANN001", # missing-type-function-argument 6 | "ANN002", # missing-type-args 7 | "ANN003", # missing-type-kwargs 8 | "ANN201", # missing-return-type-undocumented-public-function 9 | "ANN202", # missing-return-type-private-function 10 | "ANN205", # missing-return-type-static-method 11 | "ANN206", # missing-return-type-class-method 12 | "ANN401", # any_type 13 | "B904", # raise-without-from-inside-except 14 | "RSE102", # Unnecessary parentheses on raised exception 15 | "T20", # print_found, 16 | "TRY002", # raise-vanilla-class 17 | 18 | "C4", # flake8-comprehensions 19 | "E", # pycodestyle errors 20 | "F", # pyflakes 21 | "I", # isort 22 | "ICN", # flake8-import-conventions 23 | "ISC", # flake8-str-concat 24 | "RET", # flake8-return 25 | "RUF", # ruff 26 | "SIM", # common simplification rules 27 | "UP", # pyupgrade 28 | "W" # pycodestyle warnings 29 | ] 30 | 31 | -------------------------------------------------------------------------------- /tests/unit/acceptance/delivery/api/test_hello_controller.py: -------------------------------------------------------------------------------- 1 | from http.client import BAD_REQUEST 2 | 3 | from doublex import ANY_ARG, Mimic, Stub 4 | from expects import equal, expect 5 | from fastapi.testclient import TestClient 6 | 7 | from main import app, settings 8 | from src.delivery.api.v1.hello.hello_router import say_hello_command_handler 9 | from src.domain.exceptions import SayHelloCommandHandlerException 10 | from src.use_cases.say_hello_command import SayHelloCommandHandler 11 | 12 | 13 | class TestHelloController: 14 | ERROR_MESSAGE = "any error message" 15 | 16 | def _failing_handler(self) -> SayHelloCommandHandler: 17 | with Mimic(Stub, SayHelloCommandHandler) as self.handler: 18 | exception = SayHelloCommandHandlerException(self.ERROR_MESSAGE) 19 | self.handler.execute(ANY_ARG).raises(exception) 20 | return self.handler # type: ignore 21 | 22 | def test_hello_controller(self) -> None: 23 | client = TestClient(app) 24 | app.dependency_overrides[say_hello_command_handler] = self._failing_handler 25 | invalid_name = "any-invalid-name" 26 | 27 | response = client.get(f"{settings.api_v1_prefix}/hello/{invalid_name}") 28 | 29 | expect(response.status_code).to(equal(BAD_REQUEST)) 30 | expect(response.json()).to(equal({"detail": self.ERROR_MESSAGE})) 31 | -------------------------------------------------------------------------------- /src/delivery/api/v1/hello/hello_router.py: -------------------------------------------------------------------------------- 1 | from http.client import BAD_REQUEST 2 | 3 | from fastapi import APIRouter, Depends, HTTPException 4 | 5 | from src.delivery.api.v1.hello.hello_response import HelloResponse 6 | from src.domain.command import CommandHandler 7 | from src.domain.exceptions import SayHelloCommandHandlerException 8 | from src.domain.hello_client import HelloClient 9 | from src.infrastructure.hello.hello_client import DummyHelloClient 10 | from src.use_cases.say_hello_command import ( 11 | SayHelloCommand, 12 | SayHelloCommandHandler, 13 | ) 14 | 15 | hello: APIRouter = APIRouter() 16 | 17 | 18 | async def hello_client() -> DummyHelloClient: 19 | return DummyHelloClient() 20 | 21 | 22 | async def say_hello_command_handler( 23 | client: HelloClient = Depends(hello_client), 24 | ) -> CommandHandler: 25 | return SayHelloCommandHandler(client) 26 | 27 | 28 | @hello.get("/hello/{name}", response_model=HelloResponse) 29 | def get(name: str, handler: CommandHandler = Depends(say_hello_command_handler)) -> HelloResponse: 30 | command = SayHelloCommand(name) 31 | try: 32 | response = handler.execute(command) 33 | message = response.message() 34 | return HelloResponse(message=message) 35 | except SayHelloCommandHandlerException as ex: 36 | raise HTTPException(status_code=BAD_REQUEST, detail=f"{ex}") from ex 37 | -------------------------------------------------------------------------------- /tests/unit/use_cases/test_say_hello_command.py: -------------------------------------------------------------------------------- 1 | from doublex import Mimic, Stub 2 | from expects import equal, expect, raise_error 3 | 4 | from src.domain.exceptions import ( 5 | SayHelloClientException, 6 | SayHelloCommandHandlerException, 7 | ) 8 | from src.infrastructure.hello.hello_client import DummyHelloClient 9 | from src.use_cases.say_hello_command import SayHelloCommand, SayHelloCommandHandler 10 | 11 | 12 | class TestSayHelloCommandHandler: 13 | def test_execute(self) -> None: 14 | name = "John" 15 | command = SayHelloCommand(name) 16 | with Mimic(Stub, DummyHelloClient) as hello_client: 17 | hello_client.get(name).returns(name) 18 | handler = SayHelloCommandHandler(hello_client) # type: ignore 19 | 20 | response = handler.execute(command) 21 | 22 | expect(response.message()).to(equal(f"Hello, {name}!")) 23 | 24 | def test_raise_exception(self) -> None: 25 | name = "John" 26 | command = SayHelloCommand(name) 27 | with Mimic(Stub, DummyHelloClient) as hello_client: 28 | hello_client.get(name).raises(SayHelloClientException(name)) 29 | handler = SayHelloCommandHandler(hello_client) # type: ignore 30 | 31 | error_message = f"Command {command.command_id}: '{name}' invalid name" 32 | expect(lambda: handler.execute(command)).to(raise_error(SayHelloCommandHandlerException, error_message)) 33 | -------------------------------------------------------------------------------- /src/use_cases/say_hello_command.py: -------------------------------------------------------------------------------- 1 | from logging import Logger 2 | 3 | from src.common.logger import logger 4 | from src.domain.command import Command, CommandHandler, CommandResponse 5 | from src.domain.exceptions import ( 6 | SayHelloClientException, 7 | SayHelloCommandHandlerException, 8 | ) 9 | from src.domain.hello_client import HelloClient 10 | 11 | 12 | class SayHelloCommand(Command): 13 | def __init__(self, name: str) -> None: 14 | self.name = name 15 | super().__init__() 16 | 17 | 18 | class SayHelloCommandResponse(CommandResponse): 19 | def __init__(self, name: str) -> None: 20 | self.name = name 21 | 22 | def message(self) -> str: 23 | return f"Hello, {self.name}!" 24 | 25 | 26 | class SayHelloCommandHandler(CommandHandler): 27 | def __init__(self, hello_client: HelloClient, _logger: Logger = logger) -> None: 28 | self._hello_client = hello_client 29 | self._logger = _logger 30 | 31 | def execute(self, command: SayHelloCommand) -> SayHelloCommandResponse: 32 | command_id = command.command_id 33 | self._logger.info(f"Command {command_id}: HealthCommandHandler#execute") 34 | 35 | try: 36 | name = self._hello_client.get(command.name) 37 | except SayHelloClientException as ex: 38 | error_message = f"Command {command_id}: {ex}" 39 | raise SayHelloCommandHandlerException(error_message) from ex 40 | 41 | response = SayHelloCommandResponse(name) 42 | message = response.message() 43 | self._logger.info(f"Command {command_id}: SayHelloCommandResponse {message}") 44 | return response 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | .PHONY: help 4 | help: ## Show this help. 5 | @grep -E '^\S+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | \ 6 | awk 'BEGIN {FS = ":.*?## "}; {printf "%-30s %s\n", $$1, $$2}' 7 | 8 | pre-requirements: 9 | @scripts/pre-requirements.sh 10 | 11 | .PHONY: local-setup 12 | local-setup: pre-requirements ## Sets up the local environment (e.g. install git hooks) 13 | scripts/local-setup.sh 14 | make install 15 | 16 | .PHONY: build 17 | build: pre-requirements ## Install the app packages 18 | docker build -t fastapi-boilerplate . 19 | 20 | .PHONY: up 21 | up: pre-requirements build ## Run the app inside docker 22 | docker run -p 8000:8000 fastapi-boilerplate 23 | 24 | .PHONY: install 25 | install: pre-requirements ## Install the app packages 26 | uv python install 3.12.8 27 | uv python pin 3.12.8 28 | uv sync --no-install-project 29 | 30 | .PHONY: update 31 | update: pre-requirements ## Updates the app packages 32 | uv lock --upgrade 33 | 34 | .PHONY: add-dev-package 35 | add-dev-package: pre-requirements ## Installs a new package in the app. ex: make add-dev-package package=XXX 36 | uv add --dev $(package) 37 | 38 | .PHONY: add-package 39 | add-package: pre-requirements ## Installs a new package in the app. ex: make add-package package=XXX 40 | uv add $(package) 41 | 42 | .PHONY: run 43 | run: pre-requirements ## Runs the app in production mode 44 | OPENAPI_URL= fastapi run 45 | 46 | .PHONY: dev 47 | dev: pre-requirements ## Runs the app in development mode 48 | fastapi dev 49 | 50 | .PHONY: check-typing 51 | check-typing: pre-requirements ## Run a static analyzer over the code to find issues 52 | ty check . 53 | 54 | .PHONY: check-lint 55 | check-lint: pre-requirements ## Checks the code style 56 | ruff check 57 | 58 | .PHONY: lint 59 | lint: pre-requirements ## Lints the code format 60 | ruff check --fix 61 | 62 | .PHONY: check-format 63 | check-format: pre-requirements ## Check format python code 64 | ruff format --check 65 | 66 | .PHONY: format 67 | format: pre-requirements ## Format python code 68 | ruff format 69 | 70 | .PHONY: checks 71 | checks: pre-requirements check-lint check-format check-typing ## Run all checks 72 | 73 | .PHONY: test-unit 74 | test-unit: pre-requirements ## Run unit tests 75 | pytest tests/unit -ra -x --durations=5 76 | 77 | .PHONY: test-integration 78 | test-integration: pre-requirements ## Run integration tests 79 | pytest tests/integration -ra -x --durations=5 80 | 81 | .PHONY: test-acceptance 82 | test-acceptance: pre-requirements ## Run acceptance tests 83 | pytest tests/acceptance -ra -x --durations=5 84 | 85 | .PHONY: test 86 | test: test-unit test-integration test-acceptance ## Run all the tests 87 | 88 | .PHONY: watch 89 | watch: pre-requirements ## Run all the tests in watch mode 90 | ptw --runner "pytest tests -ra -x --durations=5" 91 | 92 | .PHONY: coverage 93 | coverage: pre-requirements ## Generates the coverage report 94 | coverage run --branch -m pytest tests 95 | coverage html 96 | @open "${PWD}/htmlcov/index.html" 97 | 98 | .PHONY: pre-commit 99 | pre-commit: pre-requirements check-lint check-format check-typing test-unit 100 | 101 | .PHONY: pre-push 102 | pre-push: pre-requirements test-integration test-acceptance 103 | 104 | .PHONY: rename-project 105 | rename-project: ## Rename project make rename name=new-name 106 | sed -i 's/fastapi-boilerplate/$(name)/' pyproject.toml 107 | sed -i 's/fastapi-boilerplate/$(name)/' Makefile 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastAPI Boilerplate ![status](https://github.com/pmareke/fastapi-boilerplate/actions/workflows/app.yml/badge.svg) 2 | 3 | - This repository is meant to be used as a fast starter point. 4 | - The Python version is the 3.12.8. 5 | - The project has configured a [Github Action](https://github.com/pmareke/fastapi-boilerplate/actions) which runs on every push to the `main` branch. 6 | - The project has a `Dockerfile` ready to use to deploy the app in production. 7 | 8 | ## Requirements 9 | 10 | - You only need to have [uv](https://docs.astral.sh/uv) installed. 11 | - In order to work in the project you need to activate the **virtual environment**, you can do it: 12 | - Manually with the following command `source .venv/bin/activate`. 13 | - Automatically with [pyautoenv](https://github.com/hsaunders1904/pyautoenv). 14 | 15 | ## Folder structure 16 | 17 | - There is a `tests` folder with the tests files. 18 | - The `unit` folder contains the unit tests, also known as [F.I.R.S.T](https://dzone.com/articles/writing-your-first-unit-tests#:~:text=First%20class%20developers%20write%20their,self%2Dvalidating%2C%20and%20timely.&text=Unit%20tests%20are%20required%20to%20test%20singular%20sections%20of%20code.). 19 | - The `integration` folder contains the tests that will validate the connection between our app and the external services. 20 | - The `acceptance` folder contains the tests that validate the app behavior from the outside. 21 | - The production code goes inside the `src` folder. 22 | - The `delivery` folder contains the `API` logic. 23 | - The `domain` folder contains the domain classes of the app. 24 | - The `infrastructure` folder contains the classes that interact with the external services. 25 | - The `use_cases` folder contains the business logic. 26 | - The `common` folder contains the shared logic. 27 | - Inside the `scripts` folder you can find the git hooks files. 28 | 29 | ## Project commands 30 | 31 | The project uses [Makefiles](https://www.gnu.org/software/make/manual/html_node/Introduction.html) to run the most common tasks: 32 | 33 | - `add-package package=XXX`: Installs the package XXX in the app, ex: `make install package=requests`. 34 | - `build` : Builds the app using the Dockerfile. 35 | - `check-typing`: Runs a static analyzer over the code in order to find issues. 36 | - `check-format`: Checks the code format. 37 | - `check-lint`: Checks the code style. 38 | - `checks`: Runs all the checks. 39 | - `coverage` : Generates the coverage report. 40 | - `dev`: Runs the app in development mode. 41 | - `format`: Formats the code. 42 | - `lint`: Lints the code. 43 | - `help` : Shows this help. 44 | - `install`: Installs the app packages. 45 | - `local-setup`: Sets up the local environment (e.g. install git hooks). 46 | - `run`: Runs the app in production mode. 47 | - `test`: Run all the tests. 48 | - `update`: Updates the app packages. 49 | - `watch`: Run all the tests in watch mode. 50 | 51 | **Important: Please run the `make local-setup` command before starting with the code.** 52 | 53 | _In order to create a commit you have to pass the pre-commit phase which runs the check and test commands._ 54 | 55 | ## Packages 56 | 57 | This project uses [uv](https://docs.astral.sh/uv) as the package manager. 58 | 59 | ### Testing 60 | 61 | - [pytest](https://docs.pytest.org/en/7.1.x/contents.html): Testing runner. 62 | - [expects](https://expects.readthedocs.io/en/stable/): An expressive and extensible TDD/BDD assertion library for Python. 63 | - [doublex](https://pypi.org/project/doublex-expects/): A powerful test doubles framework for Python. 64 | 65 | ### Code style 66 | 67 | - [ty](https://github.com/astral-sh/ty): A static type checker. 68 | - [ruff](https://docs.astral.sh/ruff/installation/): A Python linter and formatter. 69 | 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | .DS_Store 3 | .ruff_cache 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | pyrightconfig.json 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ 164 | 165 | pyrightconfig.json 166 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = "==3.12.8" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "idna" }, 20 | { name = "sniffio" }, 21 | { name = "typing-extensions" }, 22 | ] 23 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 26 | ] 27 | 28 | [[package]] 29 | name = "certifi" 30 | version = "2025.4.26" 31 | source = { registry = "https://pypi.org/simple" } 32 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } 33 | wheels = [ 34 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, 35 | ] 36 | 37 | [[package]] 38 | name = "click" 39 | version = "8.2.1" 40 | source = { registry = "https://pypi.org/simple" } 41 | dependencies = [ 42 | { name = "colorama", marker = "sys_platform == 'win32'" }, 43 | ] 44 | sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } 45 | wheels = [ 46 | { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, 47 | ] 48 | 49 | [[package]] 50 | name = "colorama" 51 | version = "0.4.6" 52 | source = { registry = "https://pypi.org/simple" } 53 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 54 | wheels = [ 55 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 56 | ] 57 | 58 | [[package]] 59 | name = "coverage" 60 | version = "7.8.2" 61 | source = { registry = "https://pypi.org/simple" } 62 | sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759 } 63 | wheels = [ 64 | { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876 }, 65 | { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130 }, 66 | { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176 }, 67 | { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068 }, 68 | { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328 }, 69 | { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099 }, 70 | { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314 }, 71 | { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489 }, 72 | { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366 }, 73 | { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165 }, 74 | { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548 }, 75 | { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623 }, 76 | ] 77 | 78 | [[package]] 79 | name = "dnspython" 80 | version = "2.7.0" 81 | source = { registry = "https://pypi.org/simple" } 82 | sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } 83 | wheels = [ 84 | { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, 85 | ] 86 | 87 | [[package]] 88 | name = "docopt" 89 | version = "0.6.2" 90 | source = { registry = "https://pypi.org/simple" } 91 | sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901 } 92 | 93 | [[package]] 94 | name = "doublex" 95 | version = "1.9.6.1" 96 | source = { registry = "https://pypi.org/simple" } 97 | dependencies = [ 98 | { name = "pyhamcrest" }, 99 | { name = "six" }, 100 | ] 101 | sdist = { url = "https://files.pythonhosted.org/packages/fe/3d/0edaecbafa90c19530739467a5dd02c8ebf9968201ab1ff15537ea98422f/doublex-1.9.6.1.tar.gz", hash = "sha256:48fbc633598eb913a6eb0c4694f8c040ba57bae9653d45643a84243e0c9f3268", size = 15734 } 102 | 103 | [[package]] 104 | name = "email-validator" 105 | version = "2.2.0" 106 | source = { registry = "https://pypi.org/simple" } 107 | dependencies = [ 108 | { name = "dnspython" }, 109 | { name = "idna" }, 110 | ] 111 | sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 } 112 | wheels = [ 113 | { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 }, 114 | ] 115 | 116 | [[package]] 117 | name = "expects" 118 | version = "0.9.0" 119 | source = { registry = "https://pypi.org/simple" } 120 | sdist = { url = "https://files.pythonhosted.org/packages/86/9a/4944ecc222f24d18e8d2819800472ffc2668e52986afd5c7bc41ecaf897b/expects-0.9.0.tar.gz", hash = "sha256:419902ccafe81b7e9559eeb6b7a07ef9d5c5604eddb93000f0642b3b2d594f4c", size = 27901 } 121 | 122 | [[package]] 123 | name = "fastapi" 124 | version = "0.115.12" 125 | source = { registry = "https://pypi.org/simple" } 126 | dependencies = [ 127 | { name = "pydantic" }, 128 | { name = "starlette" }, 129 | { name = "typing-extensions" }, 130 | ] 131 | sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } 132 | wheels = [ 133 | { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, 134 | ] 135 | 136 | [package.optional-dependencies] 137 | standard = [ 138 | { name = "email-validator" }, 139 | { name = "fastapi-cli", extra = ["standard"] }, 140 | { name = "httpx" }, 141 | { name = "jinja2" }, 142 | { name = "python-multipart" }, 143 | { name = "uvicorn", extra = ["standard"] }, 144 | ] 145 | 146 | [[package]] 147 | name = "fastapi-boilerplate" 148 | version = "0.1.0" 149 | source = { virtual = "." } 150 | dependencies = [ 151 | { name = "fastapi", extra = ["standard"] }, 152 | { name = "pydantic-settings" }, 153 | ] 154 | 155 | [package.dev-dependencies] 156 | dev = [ 157 | { name = "coverage" }, 158 | { name = "doublex" }, 159 | { name = "expects" }, 160 | { name = "pytest" }, 161 | { name = "pytest-watch" }, 162 | { name = "ruff" }, 163 | { name = "ty" }, 164 | ] 165 | 166 | [package.metadata] 167 | requires-dist = [ 168 | { name = "fastapi", extras = ["standard"], specifier = ">=0.115.0,<0.116" }, 169 | { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, 170 | ] 171 | 172 | [package.metadata.requires-dev] 173 | dev = [ 174 | { name = "coverage", specifier = ">=7.6.1,<8" }, 175 | { name = "doublex", specifier = ">=1.9.6.1,<2" }, 176 | { name = "expects", specifier = ">=0.9.0,<0.10" }, 177 | { name = "pytest", specifier = ">=8.3.3,<9" }, 178 | { name = "pytest-watch", specifier = ">=4.2.0,<5" }, 179 | { name = "ruff", specifier = ">=0.6.7,<0.7" }, 180 | { name = "ty", specifier = ">=0.0.1a6" }, 181 | ] 182 | 183 | [[package]] 184 | name = "fastapi-cli" 185 | version = "0.0.7" 186 | source = { registry = "https://pypi.org/simple" } 187 | dependencies = [ 188 | { name = "rich-toolkit" }, 189 | { name = "typer" }, 190 | { name = "uvicorn", extra = ["standard"] }, 191 | ] 192 | sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753 } 193 | wheels = [ 194 | { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705 }, 195 | ] 196 | 197 | [package.optional-dependencies] 198 | standard = [ 199 | { name = "uvicorn", extra = ["standard"] }, 200 | ] 201 | 202 | [[package]] 203 | name = "h11" 204 | version = "0.16.0" 205 | source = { registry = "https://pypi.org/simple" } 206 | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } 207 | wheels = [ 208 | { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, 209 | ] 210 | 211 | [[package]] 212 | name = "httpcore" 213 | version = "1.0.9" 214 | source = { registry = "https://pypi.org/simple" } 215 | dependencies = [ 216 | { name = "certifi" }, 217 | { name = "h11" }, 218 | ] 219 | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } 220 | wheels = [ 221 | { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, 222 | ] 223 | 224 | [[package]] 225 | name = "httptools" 226 | version = "0.6.4" 227 | source = { registry = "https://pypi.org/simple" } 228 | sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } 229 | wheels = [ 230 | { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, 231 | { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, 232 | { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, 233 | { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, 234 | { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, 235 | { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, 236 | { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, 237 | ] 238 | 239 | [[package]] 240 | name = "httpx" 241 | version = "0.28.1" 242 | source = { registry = "https://pypi.org/simple" } 243 | dependencies = [ 244 | { name = "anyio" }, 245 | { name = "certifi" }, 246 | { name = "httpcore" }, 247 | { name = "idna" }, 248 | ] 249 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 250 | wheels = [ 251 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 252 | ] 253 | 254 | [[package]] 255 | name = "idna" 256 | version = "3.10" 257 | source = { registry = "https://pypi.org/simple" } 258 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 259 | wheels = [ 260 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 261 | ] 262 | 263 | [[package]] 264 | name = "iniconfig" 265 | version = "2.1.0" 266 | source = { registry = "https://pypi.org/simple" } 267 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } 268 | wheels = [ 269 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, 270 | ] 271 | 272 | [[package]] 273 | name = "jinja2" 274 | version = "3.1.6" 275 | source = { registry = "https://pypi.org/simple" } 276 | dependencies = [ 277 | { name = "markupsafe" }, 278 | ] 279 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } 280 | wheels = [ 281 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, 282 | ] 283 | 284 | [[package]] 285 | name = "markdown-it-py" 286 | version = "3.0.0" 287 | source = { registry = "https://pypi.org/simple" } 288 | dependencies = [ 289 | { name = "mdurl" }, 290 | ] 291 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 292 | wheels = [ 293 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 294 | ] 295 | 296 | [[package]] 297 | name = "markupsafe" 298 | version = "3.0.2" 299 | source = { registry = "https://pypi.org/simple" } 300 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, 303 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, 304 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, 305 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, 306 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, 307 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, 308 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, 309 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, 310 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 311 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 312 | ] 313 | 314 | [[package]] 315 | name = "mdurl" 316 | version = "0.1.2" 317 | source = { registry = "https://pypi.org/simple" } 318 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 319 | wheels = [ 320 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 321 | ] 322 | 323 | [[package]] 324 | name = "packaging" 325 | version = "25.0" 326 | source = { registry = "https://pypi.org/simple" } 327 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } 328 | wheels = [ 329 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, 330 | ] 331 | 332 | [[package]] 333 | name = "pluggy" 334 | version = "1.6.0" 335 | source = { registry = "https://pypi.org/simple" } 336 | sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } 337 | wheels = [ 338 | { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, 339 | ] 340 | 341 | [[package]] 342 | name = "pydantic" 343 | version = "2.11.5" 344 | source = { registry = "https://pypi.org/simple" } 345 | dependencies = [ 346 | { name = "annotated-types" }, 347 | { name = "pydantic-core" }, 348 | { name = "typing-extensions" }, 349 | { name = "typing-inspection" }, 350 | ] 351 | sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102 } 352 | wheels = [ 353 | { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229 }, 354 | ] 355 | 356 | [[package]] 357 | name = "pydantic-core" 358 | version = "2.33.2" 359 | source = { registry = "https://pypi.org/simple" } 360 | dependencies = [ 361 | { name = "typing-extensions" }, 362 | ] 363 | sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } 364 | wheels = [ 365 | { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, 366 | { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, 367 | { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, 368 | { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, 369 | { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, 370 | { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, 371 | { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, 372 | { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, 373 | { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, 374 | { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, 375 | { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, 376 | { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, 377 | { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, 378 | { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, 379 | ] 380 | 381 | [[package]] 382 | name = "pydantic-settings" 383 | version = "2.9.1" 384 | source = { registry = "https://pypi.org/simple" } 385 | dependencies = [ 386 | { name = "pydantic" }, 387 | { name = "python-dotenv" }, 388 | { name = "typing-inspection" }, 389 | ] 390 | sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 } 391 | wheels = [ 392 | { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, 393 | ] 394 | 395 | [[package]] 396 | name = "pygments" 397 | version = "2.19.1" 398 | source = { registry = "https://pypi.org/simple" } 399 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 400 | wheels = [ 401 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 402 | ] 403 | 404 | [[package]] 405 | name = "pyhamcrest" 406 | version = "2.1.0" 407 | source = { registry = "https://pypi.org/simple" } 408 | sdist = { url = "https://files.pythonhosted.org/packages/16/3f/f286caba4e64391a8dc9200e6de6ce0d07471e3f718248c3276843b7793b/pyhamcrest-2.1.0.tar.gz", hash = "sha256:c6acbec0923d0cb7e72c22af1926f3e7c97b8e8d69fc7498eabacaf7c975bd9c", size = 60538 } 409 | wheels = [ 410 | { url = "https://files.pythonhosted.org/packages/0c/71/1b25d3797a24add00f6f8c1bb0ac03a38616e2ec6606f598c1d50b0b0ffb/pyhamcrest-2.1.0-py3-none-any.whl", hash = "sha256:f6913d2f392e30e0375b3ecbd7aee79e5d1faa25d345c8f4ff597665dcac2587", size = 54555 }, 411 | ] 412 | 413 | [[package]] 414 | name = "pytest" 415 | version = "8.3.5" 416 | source = { registry = "https://pypi.org/simple" } 417 | dependencies = [ 418 | { name = "colorama", marker = "sys_platform == 'win32'" }, 419 | { name = "iniconfig" }, 420 | { name = "packaging" }, 421 | { name = "pluggy" }, 422 | ] 423 | sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } 424 | wheels = [ 425 | { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, 426 | ] 427 | 428 | [[package]] 429 | name = "pytest-watch" 430 | version = "4.2.0" 431 | source = { registry = "https://pypi.org/simple" } 432 | dependencies = [ 433 | { name = "colorama" }, 434 | { name = "docopt" }, 435 | { name = "pytest" }, 436 | { name = "watchdog" }, 437 | ] 438 | sdist = { url = "https://files.pythonhosted.org/packages/36/47/ab65fc1d682befc318c439940f81a0de1026048479f732e84fe714cd69c0/pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9", size = 16340 } 439 | 440 | [[package]] 441 | name = "python-dotenv" 442 | version = "1.1.0" 443 | source = { registry = "https://pypi.org/simple" } 444 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } 445 | wheels = [ 446 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, 447 | ] 448 | 449 | [[package]] 450 | name = "python-multipart" 451 | version = "0.0.20" 452 | source = { registry = "https://pypi.org/simple" } 453 | sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } 454 | wheels = [ 455 | { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, 456 | ] 457 | 458 | [[package]] 459 | name = "pyyaml" 460 | version = "6.0.2" 461 | source = { registry = "https://pypi.org/simple" } 462 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 463 | wheels = [ 464 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 465 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 466 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 467 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 468 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 469 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 470 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 471 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 472 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 473 | ] 474 | 475 | [[package]] 476 | name = "rich" 477 | version = "14.0.0" 478 | source = { registry = "https://pypi.org/simple" } 479 | dependencies = [ 480 | { name = "markdown-it-py" }, 481 | { name = "pygments" }, 482 | ] 483 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } 484 | wheels = [ 485 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, 486 | ] 487 | 488 | [[package]] 489 | name = "rich-toolkit" 490 | version = "0.14.7" 491 | source = { registry = "https://pypi.org/simple" } 492 | dependencies = [ 493 | { name = "click" }, 494 | { name = "rich" }, 495 | { name = "typing-extensions" }, 496 | ] 497 | sdist = { url = "https://files.pythonhosted.org/packages/5b/7a/cb48b7024b247631ce39b1f14a0f1abedf311fb27b892b0e0387d809d4b5/rich_toolkit-0.14.7.tar.gz", hash = "sha256:6cca5a68850cc5778915f528eb785662c27ba3b4b2624612cce8340fa9701c5e", size = 104977 } 498 | wheels = [ 499 | { url = "https://files.pythonhosted.org/packages/0f/2e/95fde5b818dac9a37683ea064096323f593442d0f6358923c5f635974393/rich_toolkit-0.14.7-py3-none-any.whl", hash = "sha256:def05cc6e0f1176d6263b6a26648f16a62c4563b277ca2f8538683acdba1e0da", size = 24870 }, 500 | ] 501 | 502 | [[package]] 503 | name = "ruff" 504 | version = "0.6.9" 505 | source = { registry = "https://pypi.org/simple" } 506 | sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355 } 507 | wheels = [ 508 | { url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526 }, 509 | { url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612 }, 510 | { url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197 }, 511 | { url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855 }, 512 | { url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889 }, 513 | { url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678 }, 514 | { url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682 }, 515 | { url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446 }, 516 | { url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048 }, 517 | { url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855 }, 518 | { url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007 }, 519 | { url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594 }, 520 | { url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024 }, 521 | { url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085 }, 522 | { url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088 }, 523 | { url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275 }, 524 | { url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879 }, 525 | ] 526 | 527 | [[package]] 528 | name = "shellingham" 529 | version = "1.5.4" 530 | source = { registry = "https://pypi.org/simple" } 531 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 532 | wheels = [ 533 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 534 | ] 535 | 536 | [[package]] 537 | name = "six" 538 | version = "1.17.0" 539 | source = { registry = "https://pypi.org/simple" } 540 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 541 | wheels = [ 542 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, 543 | ] 544 | 545 | [[package]] 546 | name = "sniffio" 547 | version = "1.3.1" 548 | source = { registry = "https://pypi.org/simple" } 549 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 550 | wheels = [ 551 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 552 | ] 553 | 554 | [[package]] 555 | name = "starlette" 556 | version = "0.46.2" 557 | source = { registry = "https://pypi.org/simple" } 558 | dependencies = [ 559 | { name = "anyio" }, 560 | ] 561 | sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } 562 | wheels = [ 563 | { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, 564 | ] 565 | 566 | [[package]] 567 | name = "ty" 568 | version = "0.0.1a7" 569 | source = { registry = "https://pypi.org/simple" } 570 | sdist = { url = "https://files.pythonhosted.org/packages/c4/c1/7d47b8b385bc5b43e3b3ec33305643b01e7e5dc061d9a9a2921d100d5ce5/ty-0.0.1a7.tar.gz", hash = "sha256:826945b5259d36276b5ac743d9ce660a97488fc87f9fe9679f9bf7084521c01f", size = 2909489 } 571 | wheels = [ 572 | { url = "https://files.pythonhosted.org/packages/e2/50/e8946b957e0317674fff40470e3b4bf57ff5bf37359e75ac3e0f17c89870/ty-0.0.1a7-py3-none-linux_armv6l.whl", hash = "sha256:f9d825914fa6cfe124b1c9c104cd2f69b186919a4fc41d1b04caaf84f9c805b7", size = 6209330 }, 573 | { url = "https://files.pythonhosted.org/packages/b3/23/f95ecc00e714ab24ff3db1c4b97e2a40a3512db6b08be12efe2974b92951/ty-0.0.1a7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7f72619d6a1c2fd643d58f134fb490182a215307d58cf78ad6b119bace16bf31", size = 6328658 }, 574 | { url = "https://files.pythonhosted.org/packages/f1/5c/2197b709ae6157820f2285a304a75577f406768fdc9ed2c609895c0762e1/ty-0.0.1a7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5802e366963b1d39425cdb4a9246ad66cf9400b1eefa9250c45461bcf8606eb0", size = 5993507 }, 575 | { url = "https://files.pythonhosted.org/packages/a2/c4/e8c2aba56a00d387d95b1acffaa2bc1d68044530d98cf705da743efa349a/ty-0.0.1a7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b19954624f6f8c6981ed082a7ce6050ea1842cc3c3fb232cbc38b4148e83d740", size = 6123469 }, 576 | { url = "https://files.pythonhosted.org/packages/08/cc/31daab25e93ca9341d69c6171d2190e69057f2c809e03c4034ee02798664/ty-0.0.1a7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15798a8feb68401cc323083a54680d8af1a5988578258c2eba33859351eb5c50", size = 6093963 }, 577 | { url = "https://files.pythonhosted.org/packages/cd/33/766c73bde1125aa454c2f0c6f9f29ae058fc74d44c6b1322dd2d71da9bb3/ty-0.0.1a7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38674b4c216bce14df142069e9eff093ad6adc474c96cc2313f80965e1d71d0c", size = 6787657 }, 578 | { url = "https://files.pythonhosted.org/packages/1f/e2/67b379bc8f55b20e791807e9f867268fe1b4e632b1a01c3b0971bf913479/ty-0.0.1a7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4711bad6e84170580d264ae8f31f00faab6e45dc38ddebbfeb5b82338488c84b", size = 7214322 }, 579 | { url = "https://files.pythonhosted.org/packages/0c/49/aaf090c5c066c63ea68dbf8342606eea4e443e05a0cda1fdf04296733dda/ty-0.0.1a7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1805fd176c623e2c5beddc43fc5b357b653673a4a29bf0b30fd1d1e399ce8c0d", size = 6892732 }, 580 | { url = "https://files.pythonhosted.org/packages/6c/5e/1b9b522e12c2ac08a7ff3de49313860c390009a93bca094e45b996ecce8c/ty-0.0.1a7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86ef9b9a42d3ad386b7d585e3b3f22b506d550407a35e424dadb24c60630de8e", size = 6781645 }, 581 | { url = "https://files.pythonhosted.org/packages/46/f1/d6f2d3ffcbce96a1bc6aaacd79f23c478e0b86d4f16d85b35c90ceed48ed/ty-0.0.1a7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6239ae28d34796ce54b8ffa9c971399170d41039bc7a692b9ccc1ca43210b25d", size = 6605269 }, 582 | { url = "https://files.pythonhosted.org/packages/35/a0/6ff5247b8c316ac1d63be0922549b17dcaccecc22b23c37db281aaef8adb/ty-0.0.1a7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2e0ba8a1187e413c7e536a5dadd340a4cd273b2ac3bbe3a2c76dbcbc5c28acfb", size = 6046599 }, 583 | { url = "https://files.pythonhosted.org/packages/e7/a6/5a196598c0a25fb4b8705ed09baf8adf60221f39544b1941084b0580368c/ty-0.0.1a7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acf6a8aa797a1578d74b453190076f07192a1e5ca72cfe42b1b63a2904a1ecd4", size = 6115961 }, 584 | { url = "https://files.pythonhosted.org/packages/ca/02/5f74d8dd9f6367419393fc55fbb2929a183a582ac1e82df1dd021c30700a/ty-0.0.1a7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:069a89f2a51c2ee8797c6ce0d7d16ced0045bcc558ad335948fa6d2787c10292", size = 6492053 }, 585 | { url = "https://files.pythonhosted.org/packages/77/34/1c31eebe1c54a33802b1b4ba485805f0d04e42fab6ac97a68b63c9e1febd/ty-0.0.1a7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b406a7f83757e027937a1b6b2fa02e53ba8ce587cbb37fb527a508251a3ce9f0", size = 6665996 }, 586 | { url = "https://files.pythonhosted.org/packages/61/36/cf0075f7de2bd3c9821ac54b749c91f5f3f181326cdab88a8c6a095340a8/ty-0.0.1a7-py3-none-win32.whl", hash = "sha256:02f9e82bc3180ca6d72cca780ac5909b97753cfb1d4abfb0e6fd8cf2190dd2c5", size = 5924237 }, 587 | { url = "https://files.pythonhosted.org/packages/2c/49/02aec0611f6967087d562a87ffc218b8f9c8c9bba205a67a24d142e8c9e0/ty-0.0.1a7-py3-none-win_amd64.whl", hash = "sha256:b6cea4d80b4cbdfa21c04b4e2bcbe964dcd781462165d3c06b84781b046a163f", size = 6407997 }, 588 | { url = "https://files.pythonhosted.org/packages/4a/94/e048fd4d5bd3f9518dcbc07e83b06a286512db310df17262af8adc32a29d/ty-0.0.1a7-py3-none-win_arm64.whl", hash = "sha256:b5a40b77f91bf86d4a2d6740e2ba1b09ae70e94d152074a7a6bc80aafe83bb06", size = 6061856 }, 589 | ] 590 | 591 | [[package]] 592 | name = "typer" 593 | version = "0.16.0" 594 | source = { registry = "https://pypi.org/simple" } 595 | dependencies = [ 596 | { name = "click" }, 597 | { name = "rich" }, 598 | { name = "shellingham" }, 599 | { name = "typing-extensions" }, 600 | ] 601 | sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625 } 602 | wheels = [ 603 | { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 }, 604 | ] 605 | 606 | [[package]] 607 | name = "typing-extensions" 608 | version = "4.13.2" 609 | source = { registry = "https://pypi.org/simple" } 610 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } 611 | wheels = [ 612 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, 613 | ] 614 | 615 | [[package]] 616 | name = "typing-inspection" 617 | version = "0.4.1" 618 | source = { registry = "https://pypi.org/simple" } 619 | dependencies = [ 620 | { name = "typing-extensions" }, 621 | ] 622 | sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } 623 | wheels = [ 624 | { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, 625 | ] 626 | 627 | [[package]] 628 | name = "uvicorn" 629 | version = "0.34.3" 630 | source = { registry = "https://pypi.org/simple" } 631 | dependencies = [ 632 | { name = "click" }, 633 | { name = "h11" }, 634 | ] 635 | sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631 } 636 | wheels = [ 637 | { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431 }, 638 | ] 639 | 640 | [package.optional-dependencies] 641 | standard = [ 642 | { name = "colorama", marker = "sys_platform == 'win32'" }, 643 | { name = "httptools" }, 644 | { name = "python-dotenv" }, 645 | { name = "pyyaml" }, 646 | { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, 647 | { name = "watchfiles" }, 648 | { name = "websockets" }, 649 | ] 650 | 651 | [[package]] 652 | name = "uvloop" 653 | version = "0.21.0" 654 | source = { registry = "https://pypi.org/simple" } 655 | sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } 656 | wheels = [ 657 | { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, 658 | { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, 659 | { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, 660 | { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, 661 | { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, 662 | { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, 663 | ] 664 | 665 | [[package]] 666 | name = "watchdog" 667 | version = "6.0.0" 668 | source = { registry = "https://pypi.org/simple" } 669 | sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } 670 | wheels = [ 671 | { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, 672 | { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, 673 | { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, 674 | { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, 675 | { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, 676 | { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, 677 | { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, 678 | { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, 679 | { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, 680 | { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, 681 | { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, 682 | { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, 683 | { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, 684 | ] 685 | 686 | [[package]] 687 | name = "watchfiles" 688 | version = "1.0.5" 689 | source = { registry = "https://pypi.org/simple" } 690 | dependencies = [ 691 | { name = "anyio" }, 692 | ] 693 | sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537 } 694 | wheels = [ 695 | { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511 }, 696 | { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715 }, 697 | { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138 }, 698 | { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592 }, 699 | { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532 }, 700 | { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865 }, 701 | { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887 }, 702 | { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498 }, 703 | { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663 }, 704 | { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410 }, 705 | { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965 }, 706 | { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693 }, 707 | { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287 }, 708 | ] 709 | 710 | [[package]] 711 | name = "websockets" 712 | version = "15.0.1" 713 | source = { registry = "https://pypi.org/simple" } 714 | sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } 715 | wheels = [ 716 | { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, 717 | { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, 718 | { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, 719 | { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, 720 | { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, 721 | { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, 722 | { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, 723 | { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, 724 | { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, 725 | { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, 726 | { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, 727 | { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, 728 | ] 729 | --------------------------------------------------------------------------------