├── app ├── __init__.py ├── stress_recovery.py └── __main__.py ├── media └── HA.png ├── .gitmodules ├── .gitignore ├── .env.example ├── pyproject.toml ├── .pre-commit-config.yaml ├── docker-compose.yml ├── Dockerfile ├── .ruff.toml ├── .dockerignore ├── .github └── workflows │ └── docker-image.yml ├── test └── test_stress_recovery.py ├── README.md └── uv.lock /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/HA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALERTua/styletts2-ukrainian-openai-tts-api/HEAD/media/HA.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "script"] 2 | path = script 3 | url = https://github.com/ALERTua/script.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | .idea 9 | .env 10 | .dockerenv 11 | # Virtual environments 12 | .venv 13 | */feed.xml 14 | tryouts*.py 15 | data/ 16 | *.*~ 17 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | CONTAINER_PREFIX=tts_ukrainian_ 2 | 3 | # Gradio 4 | GRADIO_SERVER_NAME=0.0.0.0 5 | GRADIO_SERVER_PORT=7860 6 | GRADIO_ANALYTICS_ENABLED=True 7 | 8 | # API 9 | UVICORN_HOST=0.0.0.0 10 | UVICORN_PORT=8000 11 | GRADIO_URL=http://${CONTAINER_PREFIX}gradio:${GRADIO_SERVER_PORT} 12 | AUTO_USE_VERBALIZER=1 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "styletts2-ukrainian-openai-tts-api" 3 | version = "0" 4 | description = "" 5 | readme = "README.md" 6 | requires-python = "==3.13.*" 7 | dependencies = [ 8 | "fastapi>=0.119.0", 9 | "gradio-client>=1.13.3", 10 | "python-dotenv>=1.1.1", 11 | "soundfile>=0.13.1", 12 | "uvicorn>=0.37.0", 13 | ] 14 | 15 | [tool.uv.workspace] 16 | members = ["styletts2-ukrainian"] 17 | 18 | [dependency-groups] 19 | dev = [ 20 | "pre-commit-uv>=4.2.0", 21 | "pytest>=8.4.2", 22 | "ruff>=0.14.0", 23 | ] 24 | 25 | [tool.pytest.ini_options] 26 | disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true 27 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: check-added-large-files 7 | - id: check-builtin-literals 8 | - id: check-case-conflict 9 | - id: check-toml 10 | - id: check-yaml 11 | - id: check-docstring-first 12 | - id: end-of-file-fixer 13 | - id: mixed-line-ending 14 | - id: trailing-whitespace 15 | 16 | # - repo: https://github.com/astral-sh/ruff-pre-commit 17 | # rev: v0.11.3 18 | # hooks: 19 | # # Run the linter. 20 | # - id: ruff 21 | # args: [ --fix ] 22 | # # Run the formatter. 23 | # - id: ruff-format 24 | 25 | - repo: https://github.com/astral-sh/uv-pre-commit 26 | rev: 0.9.2 27 | hooks: 28 | - id: uv-lock 29 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | api: 3 | image: ghcr.io/alertua/styletts2-ukrainian-openai-tts-api:latest 4 | container_name: ${CONTAINER_PREFIX}api 5 | env_file: 6 | - .env 7 | ports: 8 | # host_port:container_port 9 | - ${UVICORN_PORT}:${UVICORN_PORT} 10 | depends_on: 11 | gradio: 12 | condition: service_healthy 13 | 14 | gradio: 15 | image: ghcr.io/alertua/patriotyk_styletts2_ukrainian_docker:latest 16 | container_name: ${CONTAINER_PREFIX}gradio 17 | env_file: 18 | - .env 19 | ports: 20 | # host_port:container_port 21 | - ${GRADIO_SERVER_PORT}:${GRADIO_SERVER_PORT} 22 | volumes: 23 | # host_path:container_path 24 | - ./${CONTAINER_PREFIX}data:/data 25 | healthcheck: 26 | test: python -c "import sys, http.client; c=http.client.HTTPConnection('127.0.0.1', ${GRADIO_SERVER_PORT}, timeout=5); c.request('HEAD', '/'); r=c.getresponse(); sys.exit(0 if r.status==200 else 1)" 27 | interval: 15s 28 | timeout: 5s 29 | start_period: 3s 30 | retries: 100 31 | 32 | # # uncomment this block to use the NVIDIA GPU 33 | # deploy: 34 | # resources: 35 | # reservations: 36 | # devices: 37 | # - driver: nvidia 38 | # count: 1 39 | # capabilities: [gpu] 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS production 2 | 3 | LABEL maintainer="ALERT " 4 | 5 | ENV \ 6 | GRADIO_URL="http://gradio:7860" \ 7 | UVICORN_PORT=8000 \ 8 | UVICORN_HOST=0.0.0.0 9 | 10 | EXPOSE $UVICORN_PORT 11 | 12 | ENV \ 13 | # uv 14 | UV_COMPILE_BYTECODE=1 \ 15 | UV_LINK_MODE=copy \ 16 | UV_FROZEN=1 \ 17 | UV_NO_PROGRESS=true \ 18 | UV_CACHE_DIR=.uv_cache \ 19 | # Python 20 | PYTHONUNBUFFERED=1 \ 21 | PYTHONDONTWRITEBYTECODE=1 \ 22 | PYTHONIOENCODING=utf-8 \ 23 | LANG=en_US.UTF-8 \ 24 | LANGUAGE=en_US.UTF-8 \ 25 | # pip 26 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 27 | # app 28 | APP_DIR=/app \ 29 | SOURCE_DIR_NAME=app 30 | 31 | 32 | WORKDIR $APP_DIR 33 | 34 | RUN --mount=type=cache,target=$UV_CACHE_DIR \ 35 | --mount=type=bind,source=uv.lock,target=uv.lock \ 36 | --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ 37 | uv sync --frozen --no-install-project --all-packages --no-dev 38 | 39 | COPY . . 40 | 41 | HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=5 \ 42 | CMD python -c "import urllib.request as u; u.urlopen('http://127.0.0.1:${UVICORN_PORT}/health', timeout=1)" 43 | 44 | ENTRYPOINT [] 45 | 46 | CMD uv run uvicorn $SOURCE_DIR_NAME.__main__:app --host ${UVICORN_HOST} --port ${UVICORN_PORT} 47 | -------------------------------------------------------------------------------- /.ruff.toml: -------------------------------------------------------------------------------- 1 | [lint] 2 | select = [ 3 | "ALL", 4 | ] 5 | 6 | ignore = [ 7 | "ANN401", # Dynamically typed expressions (typing.Any) are disallowed 8 | "D203", # no-blank-line-before-class (incompatible with formatter) 9 | "D212", # multi-line-summary-first-line (incompatible with formatter) 10 | "COM812", # incompatible with formatter 11 | "ISC001", # incompatible with formatter 12 | "A005", # Module `select` shadows a Python standard-library module 13 | "D100", # Missing docstring in public module 14 | "G004", # Logging statement uses f-string 15 | "D104", # Missing docstring in public package 16 | ] 17 | [lint.extend-per-file-ignores] 18 | "test/**/*.py" = [ 19 | "S101", 20 | "ANN001", 21 | "D", 22 | "PT006", 23 | "E501", 24 | "RUF001", 25 | "ANN201", 26 | 27 | "INP001", 28 | ] 29 | "stress_recovery.py" = [ 30 | "B007", 31 | ] 32 | 33 | [lint.flake8-pytest-style] 34 | fixture-parentheses = false 35 | 36 | [lint.pyupgrade] 37 | keep-runtime-typing = true 38 | 39 | [lint.mccabe] 40 | max-complexity = 25 41 | 42 | [format] 43 | docstring-code-format = true 44 | docstring-code-line-length = 120 45 | quote-style = "preserve" 46 | 47 | [lint.pycodestyle] 48 | max-doc-length = 120 49 | max-line-length = 120 50 | 51 | [lint.pylint] 52 | max-args = 10 53 | max-branches = 15 54 | max-nested-blocks = 10 55 | max-positional-args = 3 56 | max-public-methods = 30 57 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | *.sqlite3 2 | *.session 3 | litellm_uuid.txt 4 | *.cmd 5 | biome.json 6 | Modelfile 7 | data/ 8 | script/ 9 | 10 | # Git 11 | .git/ 12 | .gitignore 13 | .gitattributes 14 | 15 | 16 | # CI 17 | .codeclimate.yml 18 | .travis.yml 19 | .taskcluster.yml 20 | .github/ 21 | 22 | # Docker 23 | docker-compose.yml 24 | Dockerfile* 25 | .docker 26 | .dockerignore 27 | 28 | # Byte-compiled / optimized / DLL files 29 | **/__pycache__/ 30 | **/*.py[cod] 31 | 32 | # C extensions 33 | *.so 34 | 35 | # Distribution / packaging 36 | .Python 37 | env/ 38 | build/ 39 | develop-eggs/ 40 | dist/ 41 | downloads/ 42 | eggs/ 43 | lib/ 44 | lib64/ 45 | parts/ 46 | sdist/ 47 | var/ 48 | *.egg-info/ 49 | .installed.cfg 50 | *.egg 51 | 52 | # PyInstaller 53 | # Usually these files are written by a python script from a template 54 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 55 | *.manifest 56 | *.spec 57 | 58 | # Unit test / coverage reports 59 | htmlcov/ 60 | .tox/ 61 | .coverage 62 | .cache 63 | nosetests.xml 64 | coverage.xml 65 | tests/ 66 | 67 | # Translations 68 | *.mo 69 | *.pot 70 | 71 | # Django stuff: 72 | *.log 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | *.rst 77 | *.md 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Virtual environment 83 | .env 84 | .venv/ 85 | venv/ 86 | 87 | # PyCharm 88 | .idea 89 | 90 | # Python mode for VIM 91 | .ropeproject 92 | **/.ropeproject 93 | 94 | # Vim swap files 95 | **/*.swp 96 | 97 | # VS Code 98 | .vscode/ 99 | -------------------------------------------------------------------------------- /app/stress_recovery.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | 3 | 4 | def recover_stress(original_input: str, verbalized_text: str, stress_symbol: str) -> str: 5 | """ 6 | Recovers stress symbols in the verbalized text based on the original input. 7 | 8 | This function takes an original input string with stress symbols, a verbalized text string without stress symbols, 9 | and a stress symbol. It aligns the de-stressed version of the original input with the verbalized text and then 10 | re-applies the stress symbols to the verbalized text where appropriate. 11 | 12 | Args: 13 | original_input (str): The original input string with stress symbols. 14 | verbalized_text (str): The verbalized text string without stress symbols. 15 | stress_symbol (str): The symbol used to indicate stress in the original input. 16 | 17 | Returns: 18 | str: The verbalized text with stress symbols re-applied where appropriate. 19 | 20 | """ 21 | # Step 1: Tokenize original input and create de-stressed version 22 | original_tokens = original_input.split() 23 | original_de_stressed_tokens = [token.replace(stress_symbol, "") for token in original_tokens] 24 | 25 | # Step 2: Tokenize verbalized text 26 | verbalized_tokens = verbalized_text.split() 27 | 28 | # Step 3: Align de-stressed tokens with verbalized tokens 29 | matcher = difflib.SequenceMatcher(None, original_de_stressed_tokens, verbalized_tokens) 30 | alignment = matcher.get_opcodes() 31 | 32 | # Step 4: Prepare a list to hold the modified verbalized tokens 33 | modified_tokens = list(verbalized_tokens) 34 | 35 | # Step 5: Apply stress symbols to matching tokens 36 | for tag, i1, i2, j1, j2 in alignment: 37 | if tag == "equal": 38 | for i in range(i2 - i1): 39 | original_index = i1 + i 40 | verbalized_index = j1 + i 41 | 42 | if verbalized_index < len(modified_tokens): 43 | # Replace the verbalized token with the original (with stress) 44 | modified_tokens[verbalized_index] = original_tokens[original_index] 45 | 46 | # Step 6: Reconstruct the final text 47 | return " ".join(modified_tokens) 48 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image Latest 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | branches: 8 | - "main" 9 | - "dev" 10 | 11 | tags: 12 | - '*' 13 | 14 | paths-ignore: 15 | - ".pre-commit-config.yaml" 16 | - "script/" 17 | - "**/*.md" 18 | - "**/*.rst" 19 | - "tests/" 20 | - ".github/" 21 | 22 | 23 | env: 24 | REGISTRY: ghcr.io 25 | 26 | 27 | concurrency: 28 | group: ${{ github.workflow }}-${{ github.ref }} 29 | cancel-in-progress: true 30 | 31 | jobs: 32 | 33 | build-and-publish-latest: 34 | runs-on: ubuntu-latest 35 | 36 | permissions: 37 | contents: read 38 | packages: write 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v5 43 | 44 | - name: Generate Docker metadata 45 | id: meta 46 | uses: docker/metadata-action@v5 47 | with: 48 | # docker.io/${{ github.repository }} 49 | images: | 50 | ghcr.io/${{ github.repository }} 51 | tags: | 52 | type=ref,event=branch 53 | type=ref,event=tag 54 | type=semver,pattern={{version}} 55 | type=semver,pattern={{major}}.{{minor}}.{{patch}} 56 | type=semver,pattern={{major}}.{{minor}} 57 | type=semver,pattern={{major}} 58 | flavor: | 59 | latest=auto 60 | 61 | - name: Set up Docker Buildx 62 | uses: docker/setup-buildx-action@v3 63 | 64 | - name: Log in to the Container registry 65 | uses: docker/login-action@v3 66 | with: 67 | registry: ${{ env.REGISTRY }} 68 | username: ${{ github.actor }} 69 | password: ${{ secrets.GITHUB_TOKEN }} 70 | 71 | # - name: Login to DockerHub 72 | # uses: docker/login-action@v1 73 | # with: 74 | # username: ${{ github.repository_owner }} 75 | # password: ${{ secrets.DOCKER_PASSWORD }} 76 | 77 | - name: Build and push 78 | uses: docker/build-push-action@v6 79 | env: 80 | DOCKER_BUILDKIT: 1 81 | with: 82 | context: . 83 | push: true 84 | tags: ${{ steps.meta.outputs.tags }} 85 | labels: ${{ steps.meta.outputs.labels }} 86 | -------------------------------------------------------------------------------- /test/test_stress_recovery.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from app.stress_recovery import ( 4 | recover_stress, 5 | ) 6 | 7 | test_cases = [ 8 | # Basic case with stress symbols and number conversion 9 | ( 10 | "правильно читати `оцет, а не оц`ет. 25 разів повторював про той оцет.", 11 | "правильно читати оцет, а не оцет. двадцять п'ять разів повторював про той оцет.", 12 | "правильно читати `оцет, а не оц`ет. двадцять п'ять разів повторював про той оцет.", 13 | ), 14 | ( 15 | "правильно читати `оцет, а не оц`ет. 25 разів повторював про той оцет.", 16 | "правильно читати оцет, а не оцет. двадцять п'ять разів повторював про той оцет.", 17 | "правильно читати `оцет, а не оц`ет. двадцять п'ять разів повторював про той оцет.", 18 | ), 19 | # Additional stress symbol at the end of a word 20 | ( 21 | "правильно читати `оцет, а не оц`ет. 25 разів повторював про той `оцет.", 22 | "правильно читати оцет, а не оцет. двадцять п'ять разів повторював про той оцет.", 23 | "правильно читати `оцет, а не оц`ет. двадцять п'ять разів повторював про той `оцет.", 24 | ), 25 | # Stress in different positions and number conversion 26 | ( 27 | "правильно читати `оцет, а не 23 оц`ет. 25 разів повторював про той оц`ет.", 28 | "правильно читати оцет, а не двадцять три оцет. двадцять п'ять разів повторював про той оцет.", 29 | "правильно читати `оцет, а не двадцять три оц`ет. двадцять п'ять разів повторював про той оц`ет.", 30 | ), 31 | # Single word with stress 32 | ("оцет`", "оцет", "оцет`"), 33 | # Number converted to multiple words, stress on following word 34 | ("25 разів`", "двадцять п'ять разів", "двадцять п'ять разів`"), 35 | # Multiple words with stress 36 | ("привіт` світ`", "привіт світ", "привіт` світ`"), 37 | # Punctuation handling 38 | ("оцет`, оцет.", "оцет, оцет.", "оцет`, оцет."), 39 | # Stress on number (should not propagate) 40 | ("25` разів`", "двадцять п'ять разів", "двадцять п'ять разів`"), 41 | ] 42 | 43 | 44 | # Parametrize the test function 45 | @pytest.mark.parametrize("original, verbalized, expected", test_cases) 46 | def test_recover_stress(original, verbalized, expected): 47 | stress_symbol = "`" 48 | result = recover_stress(original, verbalized, stress_symbol) 49 | assert result == expected, f"Expected:\n{expected}\nGot:\n{result}" 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg)](https://stand-with-ukraine.pp.ua) 2 | [![Made in Ukraine](https://img.shields.io/badge/made_in-Ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua) 3 | [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) 4 | [![Russian Warship Go Fuck Yourself](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/RussianWarship.svg)](https://stand-with-ukraine.pp.ua) 5 | 6 | Repository: [ALERTua/styletts2-ukrainian-openai-tts-api](https://github.com/ALERTua/styletts2-ukrainian-openai-tts-api) 7 | 8 | GitHub Docker Registry: https://github.com/ALERTua/styletts2-ukrainian-openai-tts-api/pkgs/container/styletts2-ukrainian-openai-tts-api 9 | 10 | 11 | ## Description 12 | 13 | Provides OpenAI TTS API endpoints for the [patriotyk/styletts2-ukrainian](https://huggingface.co/spaces/patriotyk/styletts2-ukrainian) Gradio app, based on the Docker image [ALERTua/patriotyk_styletts2_ukrainian_docker](https://github.com/ALERTua/patriotyk_styletts2_ukrainian_docker) 14 | 15 | I didn't mean to make this as a full-fledged product. It's just a quick and dirty solution to get the model working with Home Assistant until a better option arrives. 16 | By better option I mean a model of at least the same quality but in Piper-compatible format for easy Home Assistant usage. 17 | 18 | 19 | ### Docker-compose Environment Variables 20 | 21 | - `AUTO_USE_VERBALIZER`: automatically use the verbalizer (convert numbers to words) (default: 1) 22 | - `GRADIO_URL`: URL of the Gradio Web UI (default: http://gradio:7860) 23 | - `UVICORN_PORT`: port for the OpenAI TTS API (default: 8000) 24 | - `UVICORN_HOST`: listen interface for the OpenAI TTS API (default: 0.0.0.0) 25 | - `GRADIO_PORT`: port for Gradio Web UI (default: 7860) 26 | - `GRADIO_SERVER_NAME`: listen interface for the Gradio App (default: 0.0.0.0) 27 | 28 | 29 | ### Deployment 30 | 31 | The best way is to use the [docker-compose.yml](/docker-compose.yml) 32 | Don't forget to create `.env` from [.env.example](/.env.example). 33 | You can also deploy the Gradio App the way you like and just point this application to it using the `GRADIO_URL` environment variable. 34 | 35 | 36 | ### Resources usage 37 | - tag `latest` uses ~110 MiB of RAM 38 | 39 | 40 | ### Things to do that depend on the author's code 41 | 42 | - [ ] A separate endpoint that lists all voices 43 | 44 | 45 | ## TODO 46 | 47 | - [ ] a separate verbalization endpoint 48 | 49 | 50 | ### Usage in Home Assistant 51 | 52 | - Add https://github.com/sfortis/openai_tts integration to your Home Assistant instance. 53 | - Provide it with the url to this container UVICORN forwarded port. 54 | - You can leave the API key empty, as the endpoint does not check for it. 55 | - Use one of these models: multi, single 56 | - Use one of the voices available: https://huggingface.co/spaces/patriotyk/styletts2-ukrainian/tree/main/voices (filename without .pt). E.g. `Марина Панас` 57 | - If a stress is wrong use ``` ` ``` symbol before a syllable to stress it, e.g. ```русн`я```. 58 | - The model handles short messages poorly so at least end each syntax with a dot. 59 | 60 | ![image](media/HA.png) 61 | 62 | ```yaml 63 | action: tts.speak 64 | data: 65 | media_player_entity_id: media_player.office_speaker 66 | message: тестове повідомлення 123. 67 | cache: false 68 | target: 69 | entity_id: tts.styletts2 70 | ``` 71 | 72 | ### Endpoints 73 | 74 | #### Synthesize Speech 75 | 76 | **Endpoint:** `POST /v1/audio/speech` 77 | 78 | This endpoint synthesizes speech from the given text using the specified voice and parameters. 79 | 80 | **Example Request:** 81 | 82 | ```bash 83 | #!/bin/bash 84 | curl -X POST "http://127.0.0.1:8000/v1/audio/speech" \ 85 | -H "accept: audio/wav" \ 86 | -H "Content-Type: application/json" \ 87 | -d '{ 88 | "input": "Русн+я вже майже вся подохла. Залишилося ще трохи почекати, і перемога буде за нами.", 89 | "voice": "Марина Панас", 90 | "speed": 1.0, 91 | "verbalize": 1 92 | }' 93 | ``` 94 | ```powershell 95 | # powershell 96 | $headers = @{ 97 | "Content-Type" = "application/json" 98 | } 99 | 100 | Invoke-RestMethod -Uri 'http://127.0.0.1:8000/v1/audio/speech' ` 101 | -Method POST ` 102 | -Headers $headers ` 103 | -Body '{ 104 | "input": "Зустр`іч признач`ена на 15:30 12.05.2025 у конференц-зал`і №3." 105 | }' ` 106 | -OutFile 'output.mp3' 107 | 108 | explorer.exe output.mp3 109 | ``` 110 | 111 | #### Request Body Parameters 112 | 113 | - **model** (string): model name: multi or single 114 | - **input** (string): The text to generate audio for. 115 | - **voice** (string): The voice to use for synthesis. 116 | - **speed** (float): The speed of the speech. Default is `1.0`. 117 | - ~~**response_format** (string): The format of the audio output. Supported formats are `wav` and `mp3`. Default is `wav`.~~ 118 | - ~~**sample_rate** (int): The sample rate of the audio. Default is `24000`.~~ 119 | 120 | 121 | ### Caveats 122 | 123 | - If the verbalizer is off, the model does not handle numbers(!) so make sure to replace them with words. 124 | ```python 125 | # pip install num2words 126 | 127 | cases = [ 128 | "nominative", 129 | "genitive", 130 | "dative", 131 | "accusative", 132 | "instrumental", 133 | "locative" 134 | ] 135 | 136 | a = num2words(42, lang='uk', to='ordinal') # сорок другий 137 | c = num2words(42, lang='uk', to='cardinal', gender='feminine', case="genitive") # сорока двох 138 | d = num2words(1442, lang='uk', to='year') # одна тисяча чотириста сорок два 139 | e = num2words(1444.10, lang='uk', to='currency', currency='USD', cents=False, separator='', adjective=True) # одна тисяча чотириста сорок чотири долари 10 центів 140 | ``` 141 | - The verbalization is tricky and may not work as expected. For example: 142 | `це тестове повідомлення номер 2012, яке було видане в 1954 році` 143 | will be verbalized as 144 | `це тестове повідомлення номер дві тисячі дванадцятого року, яке було видане в тисяча дев'ятсот п'ятдесят четвертому році` 145 | 146 | 147 | Я готовий розширити це readme будь-якою корисною інформацією, лише [скажіть](https://github.com/ALERTua/styletts2-ukrainian-openai-tts-api/discussions/new/choose), чого вам не вистачає. 148 | -------------------------------------------------------------------------------- /app/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import io 4 | import logging 5 | import os 6 | import time 7 | from typing import Any, Literal 8 | 9 | from fastapi import FastAPI, status 10 | from fastapi.middleware.cors import CORSMiddleware 11 | from fastapi.responses import RedirectResponse, Response, StreamingResponse 12 | from pydantic import BaseModel, Field 13 | 14 | try: 15 | from .stress_recovery import recover_stress 16 | except: # noqa: E722 17 | from app.stress_recovery import recover_stress 18 | 19 | import soundfile as sf 20 | from dotenv import load_dotenv 21 | from gradio_client import Client 22 | 23 | load_dotenv() 24 | 25 | STRESS_SYMBOL = "`" 26 | STRESS_SYMBOL_REPLACEMENT = "\u0301" 27 | 28 | 29 | def strtobool(val: str | bool | int) -> int: # distutil strtobool 30 | """ 31 | Convert a string representation of truth to true (1) or false (0). 32 | 33 | True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values 34 | are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 35 | 'val' is anything else. 36 | """ 37 | val = val.lower() 38 | if val in ("y", "yes", "t", "true", "on", "1", True, 1): 39 | return 1 40 | if val in ("n", "no", "f", "false", "off", "0", False, 0): 41 | return 0 42 | msg = f"invalid truth value {val!r}" 43 | raise ValueError(msg) 44 | 45 | 46 | logging.basicConfig(level=logging.DEBUG) 47 | LOG = logging.getLogger("app") 48 | 49 | app = FastAPI() 50 | # noinspection PyTypeChecker 51 | app.add_middleware( 52 | CORSMiddleware, 53 | allow_credentials=True, 54 | allow_methods=["*"], 55 | allow_headers=["*"], 56 | ) 57 | GRADIO_URL = os.getenv("GRADIO_URL", "http://gradio:7860") 58 | AUTO_USE_VERBALIZER = strtobool(os.getenv("AUTO_USE_VERBALIZER", "1")) 59 | 60 | type ResponseFormat = Literal["mp3", "flac", "wav", "pcm"] 61 | SUPPORTED_RESPONSE_FORMATS = ("mp3", "wav") 62 | UNSUPORTED_RESPONSE_FORMATS = ("opus", "aac", "flac", "pcm") 63 | DEFAULT_RESPONSE_FORMAT = "wav" 64 | 65 | DEFAULT_VOICE = "Марина Панас" 66 | 67 | models = ["multi", "single"] 68 | # noinspection PyTypeHints 69 | type Model = Literal[*models] 70 | DEFAULT_MODEL = models[0] 71 | 72 | MIN_SAMPLE_RATE = 8000 73 | MAX_SAMPLE_RATE = 48000 74 | DEFAULT_SAMPLE_RATE = 24000 75 | 76 | gr_client = None 77 | while gr_client is None: 78 | try: 79 | gr_client = Client(GRADIO_URL) 80 | except Exception: 81 | LOG.exception("Failed to connect to gradio. Retrying in 10 seconds") 82 | time.sleep(10) 83 | 84 | 85 | def convert_gradio_audio_to_streaming_response( 86 | filepath: str, 87 | response_format: ResponseFormat, 88 | ) -> StreamingResponse: 89 | """ 90 | Convert a Gradio audio file to a streaming response. 91 | 92 | This function reads an audio file from the specified filepath, converts it to the 93 | specified response format, and returns it as a streaming response. The audio data 94 | is read using the `soundfile` library and written to an in-memory buffer, which is 95 | then used to create a `StreamingResponse`. 96 | 97 | Parameters 98 | ---------- 99 | filepath : str 100 | The path to the audio file to be converted. 101 | response_format : ResponseFormat 102 | The format in which the audio data should be returned. 103 | 104 | Returns 105 | ------- 106 | StreamingResponse 107 | A streaming response containing the audio data in the specified format. 108 | 109 | """ 110 | audio_data, sample_rate = sf.read(filepath) 111 | audio_buffer = io.BytesIO() 112 | sf.write(audio_buffer, audio_data, samplerate=sample_rate, format=response_format) 113 | audio_buffer.seek(0) 114 | return StreamingResponse(audio_buffer, media_type=f"audio/{response_format}") 115 | 116 | 117 | class CreateSpeechRequestBody(BaseModel): 118 | """ 119 | Request body model for creating speech synthesis. 120 | 121 | This class represents the structure of the request body expected by the 122 | speech synthesis endpoint. It includes various attributes that define the 123 | parameters for generating speech, such as the text to be synthesized, the voice 124 | to be used, and other optional settings. 125 | 126 | Attributes 127 | ---------- 128 | text : str 129 | The text to be synthesized into speech. 130 | voice : str 131 | The identifier of the voice to be used for synthesis. 132 | speed : float, optional 133 | The speed of the synthesized speech. Defaults to 1.0. 134 | pitch : float, optional 135 | The pitch of the synthesized speech. Defaults to 1.0. 136 | volume : float, optional 137 | The volume of the synthesized speech. Defaults to 1.0. 138 | 139 | """ 140 | 141 | model: Model = Field( 142 | DEFAULT_MODEL, 143 | description=f"Model. Supported models are {', '.join(models)}.", 144 | examples=models, 145 | ) 146 | input: str = Field( 147 | ..., 148 | description="The text to generate audio for. ", 149 | examples=[ 150 | "A rainbow is an optical phenomenon caused by refraction," 151 | " internal reflection and dispersion of light in water droplets resulting in a continuous spectrum" 152 | " of light appearing in the sky. The rainbow takes the form of a multicoloured circular arc." 153 | " Rainbows caused by sunlight always appear in the section of sky directly opposite the Sun." 154 | " Rainbows can be caused by many forms of airborne water." 155 | " These include not only rain, but also mist, spray, and airborne dew." 156 | ], 157 | ) 158 | voice: str = Field( 159 | DEFAULT_VOICE, 160 | description="Audio voice name to use.", 161 | ) 162 | language: Any | None = None 163 | response_format: ResponseFormat = Field( 164 | DEFAULT_RESPONSE_FORMAT, 165 | description=f"The format to audio in. Supported formats are" 166 | f" {', '.join(SUPPORTED_RESPONSE_FORMATS)}." 167 | f" {', '.join(UNSUPORTED_RESPONSE_FORMATS)} are not supported", 168 | examples=list(SUPPORTED_RESPONSE_FORMATS), 169 | ) 170 | speed: float = Field(1.0) 171 | sample_rate: int | None = Field(DEFAULT_SAMPLE_RATE, ge=MIN_SAMPLE_RATE, le=MAX_SAMPLE_RATE) 172 | verbalize: bool | None = Field(AUTO_USE_VERBALIZER) 173 | 174 | 175 | # https://platform.openai.com/docs/api-reference/audio/createSpeech 176 | @app.post("/v1/audio/speech") 177 | async def synthesize(body: CreateSpeechRequestBody) -> StreamingResponse: 178 | """ 179 | Synthesize speech from the provided text. 180 | 181 | This endpoint takes a CreateSpeechRequestBody containing the text to be synthesized 182 | and returns a StreamingResponse with the synthesized audio. 183 | 184 | Parameters 185 | ---------- 186 | body : CreateSpeechRequestBody 187 | The request body containing the text to be synthesized. 188 | 189 | Returns 190 | ------- 191 | StreamingResponse 192 | A streaming response containing the synthesized audio. 193 | 194 | """ 195 | input_ = body.input 196 | model_name = body.model 197 | speed = body.speed 198 | response_format = body.response_format 199 | sample_rate = body.sample_rate 200 | verbalize = body.verbalize 201 | voice = body.voice if model_name == "multi" else None 202 | 203 | LOG.info(f"{input_=}, {voice=}, {speed=}, {response_format=}, {sample_rate=}") 204 | if verbalize: 205 | original_input = input_ 206 | try: 207 | verbalized_input = gr_client.predict( 208 | text=original_input.replace(STRESS_SYMBOL, ""), 209 | api_name="/verbalize", 210 | ) 211 | if STRESS_SYMBOL in original_input: 212 | input_ = recover_stress( 213 | original_input, verbalized_input, stress_symbol=STRESS_SYMBOL 214 | ) 215 | input_ = input_.replace(STRESS_SYMBOL, STRESS_SYMBOL_REPLACEMENT) 216 | LOG.info(f"Verbalized and stress recovered output: {input_}") 217 | else: 218 | input_ = verbalized_input 219 | LOG.info(f"Verbalized output: {input_}") 220 | except Exception as e: 221 | msg = f"Error verbalizing speech: {e}" 222 | LOG.exception(msg) 223 | # noinspection PyTypeChecker 224 | return Response(content=msg, status_code=500) 225 | 226 | LOG.info(f"verbalized {input_=}, {voice=}, {speed=}, {response_format=}, {sample_rate=}") 227 | 228 | try: 229 | filepath = gr_client.predict( 230 | model_name=model_name, 231 | text=input_, 232 | speed=speed, 233 | voice_name=voice, 234 | api_name="/synthesize", 235 | ) 236 | except Exception as e: 237 | msg = f"Error synthesizing speech: {e}" 238 | LOG.exception(msg) 239 | # noinspection PyTypeChecker 240 | return Response(content=msg, status_code=500) 241 | 242 | return convert_gradio_audio_to_streaming_response(filepath=filepath, response_format=response_format) 243 | 244 | 245 | class HealthCheck(BaseModel): 246 | """Response model to validate and return when performing a health check.""" 247 | 248 | status: str = "OK" 249 | 250 | 251 | @app.get( 252 | "/health", 253 | tags=["healthcheck"], 254 | summary="Perform a Health Check", 255 | response_description="Return HTTP Status Code 200 (OK)", 256 | status_code=status.HTTP_200_OK, 257 | response_model=HealthCheck, 258 | ) 259 | def get_health() -> HealthCheck: 260 | """ 261 | ## Perform a Health Check. 262 | 263 | Endpoint to perform a healthcheck on. This endpoint can primarily be used Docker 264 | to ensure a robust container orchestration and management is in place. Other 265 | services which rely on proper functioning of the API service will not deploy if this 266 | endpoint returns any other HTTP status code except 200 (OK). 267 | 268 | Returns 269 | ------- 270 | HealthCheck: Returns a JSON response with the health status 271 | 272 | """ 273 | return HealthCheck(status="OK") 274 | 275 | 276 | @app.get("/") 277 | async def root() -> RedirectResponse: 278 | """Redirect to the Gradio UI.""" 279 | return RedirectResponse(url=GRADIO_URL) 280 | 281 | 282 | if __name__ == "__main__": 283 | import uvicorn 284 | 285 | uvicorn.run(app, host="127.0.0.1", port=int(os.getenv("UVICORN_PORT", "8000")), log_level="debug") 286 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 3 3 | requires-python = "==3.13.*" 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, upload-time = "2024-05-20T21:33:25.928Z" } 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, upload-time = "2024-05-20T21:33:24.1Z" }, 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 | ] 22 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } 23 | wheels = [ 24 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, 25 | ] 26 | 27 | [[package]] 28 | name = "certifi" 29 | version = "2025.1.31" 30 | source = { registry = "https://pypi.org/simple" } 31 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" } 32 | wheels = [ 33 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" }, 34 | ] 35 | 36 | [[package]] 37 | name = "cffi" 38 | version = "1.17.1" 39 | source = { registry = "https://pypi.org/simple" } 40 | dependencies = [ 41 | { name = "pycparser" }, 42 | ] 43 | sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } 44 | wheels = [ 45 | { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, 46 | { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, 47 | { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, 48 | { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, 49 | { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, 50 | { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, 51 | { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, 52 | { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, 53 | { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, 54 | { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, 55 | { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, 56 | ] 57 | 58 | [[package]] 59 | name = "cfgv" 60 | version = "3.4.0" 61 | source = { registry = "https://pypi.org/simple" } 62 | sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } 63 | wheels = [ 64 | { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, 65 | ] 66 | 67 | [[package]] 68 | name = "charset-normalizer" 69 | version = "3.4.1" 70 | source = { registry = "https://pypi.org/simple" } 71 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } 72 | wheels = [ 73 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" }, 74 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" }, 75 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" }, 76 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" }, 77 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" }, 78 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" }, 79 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" }, 80 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" }, 81 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" }, 82 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" }, 83 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, 84 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, 85 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, 86 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, 87 | ] 88 | 89 | [[package]] 90 | name = "click" 91 | version = "8.1.8" 92 | source = { registry = "https://pypi.org/simple" } 93 | dependencies = [ 94 | { name = "colorama", marker = "sys_platform == 'win32'" }, 95 | ] 96 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } 97 | wheels = [ 98 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, 99 | ] 100 | 101 | [[package]] 102 | name = "colorama" 103 | version = "0.4.6" 104 | source = { registry = "https://pypi.org/simple" } 105 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 106 | wheels = [ 107 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 108 | ] 109 | 110 | [[package]] 111 | name = "distlib" 112 | version = "0.4.0" 113 | source = { registry = "https://pypi.org/simple" } 114 | sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } 115 | wheels = [ 116 | { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, 117 | ] 118 | 119 | [[package]] 120 | name = "fastapi" 121 | version = "0.119.0" 122 | source = { registry = "https://pypi.org/simple" } 123 | dependencies = [ 124 | { name = "pydantic" }, 125 | { name = "starlette" }, 126 | { name = "typing-extensions" }, 127 | ] 128 | sdist = { url = "https://files.pythonhosted.org/packages/0a/f9/5c5bcce82a7997cc0eb8c47b7800f862f6b56adc40486ed246e5010d443b/fastapi-0.119.0.tar.gz", hash = "sha256:451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7", size = 336756, upload-time = "2025-10-11T17:13:40.53Z" } 129 | wheels = [ 130 | { url = "https://files.pythonhosted.org/packages/ce/70/584c4d7cad80f5e833715c0a29962d7c93b4d18eed522a02981a6d1b6ee5/fastapi-0.119.0-py3-none-any.whl", hash = "sha256:90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2", size = 107095, upload-time = "2025-10-11T17:13:39.048Z" }, 131 | ] 132 | 133 | [[package]] 134 | name = "filelock" 135 | version = "3.18.0" 136 | source = { registry = "https://pypi.org/simple" } 137 | sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } 138 | wheels = [ 139 | { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, 140 | ] 141 | 142 | [[package]] 143 | name = "fsspec" 144 | version = "2025.3.2" 145 | source = { registry = "https://pypi.org/simple" } 146 | sdist = { url = "https://files.pythonhosted.org/packages/45/d8/8425e6ba5fcec61a1d16e41b1b71d2bf9344f1fe48012c2b48b9620feae5/fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6", size = 299281, upload-time = "2025-03-31T15:27:08.524Z" } 147 | wheels = [ 148 | { url = "https://files.pythonhosted.org/packages/44/4b/e0cfc1a6f17e990f3e64b7d941ddc4acdc7b19d6edd51abf495f32b1a9e4/fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711", size = 194435, upload-time = "2025-03-31T15:27:07.028Z" }, 149 | ] 150 | 151 | [[package]] 152 | name = "gradio-client" 153 | version = "1.13.3" 154 | source = { registry = "https://pypi.org/simple" } 155 | dependencies = [ 156 | { name = "fsspec" }, 157 | { name = "httpx" }, 158 | { name = "huggingface-hub" }, 159 | { name = "packaging" }, 160 | { name = "typing-extensions" }, 161 | { name = "websockets" }, 162 | ] 163 | sdist = { url = "https://files.pythonhosted.org/packages/3e/a9/a3beb0ece8c05c33e6376b790fa42e0dd157abca8220cf639b249a597467/gradio_client-1.13.3.tar.gz", hash = "sha256:869b3e67e0f7a0f40df8c48c94de99183265cf4b7b1d9bd4623e336d219ffbe7", size = 323253, upload-time = "2025-09-26T19:51:21.7Z" } 164 | wheels = [ 165 | { url = "https://files.pythonhosted.org/packages/6e/0b/337b74504681b5dde39f20d803bb09757f9973ecdc65fd4e819d4b11faf7/gradio_client-1.13.3-py3-none-any.whl", hash = "sha256:3f63e4d33a2899c1a12b10fe3cf77b82a6919ff1a1fb6391f6aa225811aa390c", size = 325350, upload-time = "2025-09-26T19:51:20.288Z" }, 166 | ] 167 | 168 | [[package]] 169 | name = "h11" 170 | version = "0.14.0" 171 | source = { registry = "https://pypi.org/simple" } 172 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } 173 | wheels = [ 174 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, 175 | ] 176 | 177 | [[package]] 178 | name = "httpcore" 179 | version = "1.0.7" 180 | source = { registry = "https://pypi.org/simple" } 181 | dependencies = [ 182 | { name = "certifi" }, 183 | { name = "h11" }, 184 | ] 185 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196, upload-time = "2024-11-15T12:30:47.531Z" } 186 | wheels = [ 187 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload-time = "2024-11-15T12:30:45.782Z" }, 188 | ] 189 | 190 | [[package]] 191 | name = "httpx" 192 | version = "0.28.1" 193 | source = { registry = "https://pypi.org/simple" } 194 | dependencies = [ 195 | { name = "anyio" }, 196 | { name = "certifi" }, 197 | { name = "httpcore" }, 198 | { name = "idna" }, 199 | ] 200 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 201 | wheels = [ 202 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 203 | ] 204 | 205 | [[package]] 206 | name = "huggingface-hub" 207 | version = "0.30.2" 208 | source = { registry = "https://pypi.org/simple" } 209 | dependencies = [ 210 | { name = "filelock" }, 211 | { name = "fsspec" }, 212 | { name = "packaging" }, 213 | { name = "pyyaml" }, 214 | { name = "requests" }, 215 | { name = "tqdm" }, 216 | { name = "typing-extensions" }, 217 | ] 218 | sdist = { url = "https://files.pythonhosted.org/packages/df/22/8eb91736b1dcb83d879bd49050a09df29a57cc5cd9f38e48a4b1c45ee890/huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466", size = 400868, upload-time = "2025-04-08T08:32:45.26Z" } 219 | wheels = [ 220 | { url = "https://files.pythonhosted.org/packages/93/27/1fb384a841e9661faad1c31cbfa62864f59632e876df5d795234da51c395/huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28", size = 481433, upload-time = "2025-04-08T08:32:43.305Z" }, 221 | ] 222 | 223 | [[package]] 224 | name = "identify" 225 | version = "2.6.15" 226 | source = { registry = "https://pypi.org/simple" } 227 | sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } 228 | wheels = [ 229 | { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, 230 | ] 231 | 232 | [[package]] 233 | name = "idna" 234 | version = "3.10" 235 | source = { registry = "https://pypi.org/simple" } 236 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } 237 | wheels = [ 238 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, 239 | ] 240 | 241 | [[package]] 242 | name = "iniconfig" 243 | version = "2.1.0" 244 | source = { registry = "https://pypi.org/simple" } 245 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } 246 | wheels = [ 247 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, 248 | ] 249 | 250 | [[package]] 251 | name = "nodeenv" 252 | version = "1.9.1" 253 | source = { registry = "https://pypi.org/simple" } 254 | sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } 255 | wheels = [ 256 | { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, 257 | ] 258 | 259 | [[package]] 260 | name = "numpy" 261 | version = "2.2.4" 262 | source = { registry = "https://pypi.org/simple" } 263 | sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701, upload-time = "2025-03-16T18:27:00.648Z" } 264 | wheels = [ 265 | { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623, upload-time = "2025-03-16T18:13:43.231Z" }, 266 | { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681, upload-time = "2025-03-16T18:14:08.031Z" }, 267 | { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759, upload-time = "2025-03-16T18:14:18.613Z" }, 268 | { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092, upload-time = "2025-03-16T18:14:31.386Z" }, 269 | { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422, upload-time = "2025-03-16T18:14:54.83Z" }, 270 | { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202, upload-time = "2025-03-16T18:15:22.035Z" }, 271 | { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131, upload-time = "2025-03-16T18:15:48.546Z" }, 272 | { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270, upload-time = "2025-03-16T18:16:20.274Z" }, 273 | { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141, upload-time = "2025-03-16T18:20:15.297Z" }, 274 | { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885, upload-time = "2025-03-16T18:20:36.982Z" }, 275 | { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829, upload-time = "2025-03-16T18:16:56.191Z" }, 276 | { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419, upload-time = "2025-03-16T18:17:22.811Z" }, 277 | { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414, upload-time = "2025-03-16T18:17:34.066Z" }, 278 | { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379, upload-time = "2025-03-16T18:17:47.466Z" }, 279 | { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725, upload-time = "2025-03-16T18:18:11.904Z" }, 280 | { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638, upload-time = "2025-03-16T18:18:40.749Z" }, 281 | { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717, upload-time = "2025-03-16T18:19:04.512Z" }, 282 | { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998, upload-time = "2025-03-16T18:19:32.52Z" }, 283 | { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896, upload-time = "2025-03-16T18:19:43.55Z" }, 284 | { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload-time = "2025-03-16T18:20:03.94Z" }, 285 | ] 286 | 287 | [[package]] 288 | name = "packaging" 289 | version = "24.2" 290 | source = { registry = "https://pypi.org/simple" } 291 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } 292 | wheels = [ 293 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, 294 | ] 295 | 296 | [[package]] 297 | name = "platformdirs" 298 | version = "4.5.0" 299 | source = { registry = "https://pypi.org/simple" } 300 | sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, 303 | ] 304 | 305 | [[package]] 306 | name = "pluggy" 307 | version = "1.5.0" 308 | source = { registry = "https://pypi.org/simple" } 309 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } 310 | wheels = [ 311 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, 312 | ] 313 | 314 | [[package]] 315 | name = "pre-commit" 316 | version = "4.3.0" 317 | source = { registry = "https://pypi.org/simple" } 318 | dependencies = [ 319 | { name = "cfgv" }, 320 | { name = "identify" }, 321 | { name = "nodeenv" }, 322 | { name = "pyyaml" }, 323 | { name = "virtualenv" }, 324 | ] 325 | sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } 326 | wheels = [ 327 | { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, 328 | ] 329 | 330 | [[package]] 331 | name = "pre-commit-uv" 332 | version = "4.2.0" 333 | source = { registry = "https://pypi.org/simple" } 334 | dependencies = [ 335 | { name = "pre-commit" }, 336 | { name = "uv" }, 337 | ] 338 | sdist = { url = "https://files.pythonhosted.org/packages/f6/42/84372bc99a841bfdd8b182a50186471a7f5e873d8e8bcec0d0cb6dabcbb0/pre_commit_uv-4.2.0.tar.gz", hash = "sha256:c32bb1d90235507726eee2aeef2be5fdab431a6f1906e3f1addb0a4e99b369d1", size = 6912, upload-time = "2025-10-09T19:30:48.354Z" } 339 | wheels = [ 340 | { url = "https://files.pythonhosted.org/packages/87/9f/ec8491f6b3022489a4d36ce372214c10a34f90b425aa61ff2e0a8dc5b9d5/pre_commit_uv-4.2.0-py3-none-any.whl", hash = "sha256:cc1b56641e6c62d90a4d8b4f0af6f2610f1c397ce81af024e768c0f33715cb81", size = 5650, upload-time = "2025-10-09T19:30:47.257Z" }, 341 | ] 342 | 343 | [[package]] 344 | name = "pycparser" 345 | version = "2.22" 346 | source = { registry = "https://pypi.org/simple" } 347 | sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } 348 | wheels = [ 349 | { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, 350 | ] 351 | 352 | [[package]] 353 | name = "pydantic" 354 | version = "2.11.2" 355 | source = { registry = "https://pypi.org/simple" } 356 | dependencies = [ 357 | { name = "annotated-types" }, 358 | { name = "pydantic-core" }, 359 | { name = "typing-extensions" }, 360 | { name = "typing-inspection" }, 361 | ] 362 | sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742, upload-time = "2025-04-03T13:12:49.947Z" } 363 | wheels = [ 364 | { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295, upload-time = "2025-04-03T13:12:47.995Z" }, 365 | ] 366 | 367 | [[package]] 368 | name = "pydantic-core" 369 | version = "2.33.1" 370 | source = { registry = "https://pypi.org/simple" } 371 | dependencies = [ 372 | { name = "typing-extensions" }, 373 | ] 374 | sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395, upload-time = "2025-04-02T09:49:41.8Z" } 375 | wheels = [ 376 | { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551, upload-time = "2025-04-02T09:47:51.648Z" }, 377 | { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785, upload-time = "2025-04-02T09:47:53.149Z" }, 378 | { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758, upload-time = "2025-04-02T09:47:55.006Z" }, 379 | { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109, upload-time = "2025-04-02T09:47:56.532Z" }, 380 | { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159, upload-time = "2025-04-02T09:47:58.088Z" }, 381 | { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222, upload-time = "2025-04-02T09:47:59.591Z" }, 382 | { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980, upload-time = "2025-04-02T09:48:01.397Z" }, 383 | { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840, upload-time = "2025-04-02T09:48:03.056Z" }, 384 | { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518, upload-time = "2025-04-02T09:48:04.662Z" }, 385 | { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025, upload-time = "2025-04-02T09:48:06.226Z" }, 386 | { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991, upload-time = "2025-04-02T09:48:08.114Z" }, 387 | { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262, upload-time = "2025-04-02T09:48:09.708Z" }, 388 | { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626, upload-time = "2025-04-02T09:48:11.288Z" }, 389 | { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590, upload-time = "2025-04-02T09:48:12.861Z" }, 390 | { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963, upload-time = "2025-04-02T09:48:14.553Z" }, 391 | { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896, upload-time = "2025-04-02T09:48:16.222Z" }, 392 | { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload-time = "2025-04-02T09:48:17.97Z" }, 393 | ] 394 | 395 | [[package]] 396 | name = "pygments" 397 | version = "2.19.2" 398 | source = { registry = "https://pypi.org/simple" } 399 | sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } 400 | wheels = [ 401 | { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, 402 | ] 403 | 404 | [[package]] 405 | name = "pytest" 406 | version = "8.4.2" 407 | source = { registry = "https://pypi.org/simple" } 408 | dependencies = [ 409 | { name = "colorama", marker = "sys_platform == 'win32'" }, 410 | { name = "iniconfig" }, 411 | { name = "packaging" }, 412 | { name = "pluggy" }, 413 | { name = "pygments" }, 414 | ] 415 | sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } 416 | wheels = [ 417 | { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, 418 | ] 419 | 420 | [[package]] 421 | name = "python-dotenv" 422 | version = "1.1.1" 423 | source = { registry = "https://pypi.org/simple" } 424 | sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } 425 | wheels = [ 426 | { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, 427 | ] 428 | 429 | [[package]] 430 | name = "pyyaml" 431 | version = "6.0.2" 432 | source = { registry = "https://pypi.org/simple" } 433 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } 434 | wheels = [ 435 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, 436 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, 437 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, 438 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, 439 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, 440 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, 441 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, 442 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, 443 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, 444 | ] 445 | 446 | [[package]] 447 | name = "requests" 448 | version = "2.32.3" 449 | source = { registry = "https://pypi.org/simple" } 450 | dependencies = [ 451 | { name = "certifi" }, 452 | { name = "charset-normalizer" }, 453 | { name = "idna" }, 454 | { name = "urllib3" }, 455 | ] 456 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } 457 | wheels = [ 458 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, 459 | ] 460 | 461 | [[package]] 462 | name = "ruff" 463 | version = "0.14.0" 464 | source = { registry = "https://pypi.org/simple" } 465 | sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57", size = 5452071, upload-time = "2025-10-07T18:21:55.763Z" } 466 | wheels = [ 467 | { url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3", size = 12494532, upload-time = "2025-10-07T18:21:00.373Z" }, 468 | { url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8", size = 13160768, upload-time = "2025-10-07T18:21:04.73Z" }, 469 | { url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8", size = 12363376, upload-time = "2025-10-07T18:21:07.833Z" }, 470 | { url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7", size = 12608055, upload-time = "2025-10-07T18:21:10.72Z" }, 471 | { url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7", size = 12318544, upload-time = "2025-10-07T18:21:13.741Z" }, 472 | { url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2", size = 14001280, upload-time = "2025-10-07T18:21:16.411Z" }, 473 | { url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c", size = 15027286, upload-time = "2025-10-07T18:21:19.577Z" }, 474 | { url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e", size = 14451506, upload-time = "2025-10-07T18:21:22.779Z" }, 475 | { url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206", size = 13437384, upload-time = "2025-10-07T18:21:25.758Z" }, 476 | { url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e", size = 13447976, upload-time = "2025-10-07T18:21:28.83Z" }, 477 | { url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd", size = 13682850, upload-time = "2025-10-07T18:21:31.842Z" }, 478 | { url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d", size = 12449825, upload-time = "2025-10-07T18:21:35.074Z" }, 479 | { url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f", size = 12272599, upload-time = "2025-10-07T18:21:38.08Z" }, 480 | { url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02", size = 13193828, upload-time = "2025-10-07T18:21:41.216Z" }, 481 | { url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296", size = 13628617, upload-time = "2025-10-07T18:21:44.04Z" }, 482 | { url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543", size = 12367872, upload-time = "2025-10-07T18:21:46.67Z" }, 483 | { url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2", size = 13464628, upload-time = "2025-10-07T18:21:50.318Z" }, 484 | { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" }, 485 | ] 486 | 487 | [[package]] 488 | name = "sniffio" 489 | version = "1.3.1" 490 | source = { registry = "https://pypi.org/simple" } 491 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } 492 | wheels = [ 493 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, 494 | ] 495 | 496 | [[package]] 497 | name = "soundfile" 498 | version = "0.13.1" 499 | source = { registry = "https://pypi.org/simple" } 500 | dependencies = [ 501 | { name = "cffi" }, 502 | { name = "numpy" }, 503 | ] 504 | sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } 505 | wheels = [ 506 | { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, 507 | { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, 508 | { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, 509 | { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, 510 | { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, 511 | { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, 512 | { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, 513 | ] 514 | 515 | [[package]] 516 | name = "starlette" 517 | version = "0.46.1" 518 | source = { registry = "https://pypi.org/simple" } 519 | dependencies = [ 520 | { name = "anyio" }, 521 | ] 522 | sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102, upload-time = "2025-03-08T10:55:34.504Z" } 523 | wheels = [ 524 | { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995, upload-time = "2025-03-08T10:55:32.662Z" }, 525 | ] 526 | 527 | [[package]] 528 | name = "styletts2-ukrainian-openai-tts-api" 529 | version = "0" 530 | source = { virtual = "." } 531 | dependencies = [ 532 | { name = "fastapi" }, 533 | { name = "gradio-client" }, 534 | { name = "python-dotenv" }, 535 | { name = "soundfile" }, 536 | { name = "uvicorn" }, 537 | ] 538 | 539 | [package.dev-dependencies] 540 | dev = [ 541 | { name = "pre-commit-uv" }, 542 | { name = "pytest" }, 543 | { name = "ruff" }, 544 | ] 545 | 546 | [package.metadata] 547 | requires-dist = [ 548 | { name = "fastapi", specifier = ">=0.119.0" }, 549 | { name = "gradio-client", specifier = ">=1.13.3" }, 550 | { name = "python-dotenv", specifier = ">=1.1.1" }, 551 | { name = "soundfile", specifier = ">=0.13.1" }, 552 | { name = "uvicorn", specifier = ">=0.37.0" }, 553 | ] 554 | 555 | [package.metadata.requires-dev] 556 | dev = [ 557 | { name = "pre-commit-uv", specifier = ">=4.2.0" }, 558 | { name = "pytest", specifier = ">=8.4.2" }, 559 | { name = "ruff", specifier = ">=0.14.0" }, 560 | ] 561 | 562 | [[package]] 563 | name = "tqdm" 564 | version = "4.67.1" 565 | source = { registry = "https://pypi.org/simple" } 566 | dependencies = [ 567 | { name = "colorama", marker = "sys_platform == 'win32'" }, 568 | ] 569 | sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } 570 | wheels = [ 571 | { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, 572 | ] 573 | 574 | [[package]] 575 | name = "typing-extensions" 576 | version = "4.13.1" 577 | source = { registry = "https://pypi.org/simple" } 578 | sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633, upload-time = "2025-04-03T16:11:20.955Z" } 579 | wheels = [ 580 | { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739, upload-time = "2025-04-03T16:11:19.281Z" }, 581 | ] 582 | 583 | [[package]] 584 | name = "typing-inspection" 585 | version = "0.4.0" 586 | source = { registry = "https://pypi.org/simple" } 587 | dependencies = [ 588 | { name = "typing-extensions" }, 589 | ] 590 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } 591 | wheels = [ 592 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, 593 | ] 594 | 595 | [[package]] 596 | name = "urllib3" 597 | version = "2.3.0" 598 | source = { registry = "https://pypi.org/simple" } 599 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" } 600 | wheels = [ 601 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" }, 602 | ] 603 | 604 | [[package]] 605 | name = "uv" 606 | version = "0.9.2" 607 | source = { registry = "https://pypi.org/simple" } 608 | sdist = { url = "https://files.pythonhosted.org/packages/2e/23/70eb7805be75d698c244d5fb085d6af454e7d0417eea18f9ad8cbabd6df9/uv-0.9.2.tar.gz", hash = "sha256:78d58c1489dcff2fa9de8c1829a627c65a04571732dfc862e4dc7b88874df01b", size = 3693596, upload-time = "2025-10-10T19:02:06.236Z" } 609 | wheels = [ 610 | { url = "https://files.pythonhosted.org/packages/12/af/2fb37e18842148e90327a284ad8db7c89a78c8b884fce298173fda44edb3/uv-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:9e3ad7f9ca7f327c4d507840b817592a3599746e138d545791ebb2eca15f34a1", size = 20606301, upload-time = "2025-10-10T19:01:19.2Z" }, 611 | { url = "https://files.pythonhosted.org/packages/0d/ef/6dc7d0506c69edbfbba595768f96a035a85249671a57d163e31ed47c829b/uv-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6bd0e1b4135789ee3855d38da17eca8cc9d5b2e3f96023be191422bd6751f0b8", size = 19593291, upload-time = "2025-10-10T19:01:23.801Z" }, 612 | { url = "https://files.pythonhosted.org/packages/01/b6/d422f2353482ca7c5b8175a35d2e07d14d700f53bd4f95d5e86a3d451996/uv-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:939bdd13e37330d8fb43683a10a2586c0d21c353184d9ca28251842e249356e4", size = 18175720, upload-time = "2025-10-10T19:01:26.052Z" }, 613 | { url = "https://files.pythonhosted.org/packages/d9/ca/53b5819315fe01bec2911b48a2bdb800ac9ab1cf76c5d959199271540eb5/uv-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b75e8762b7b3f7fc15a065bd6fcb56007c19d952c94b9e45968e47bdd7adc289", size = 20024004, upload-time = "2025-10-10T19:01:28.661Z" }, 614 | { url = "https://files.pythonhosted.org/packages/3c/77/4a1d5b132eb072388250855e2e507d4ce5dbd31045f172d6a6266e6e1c95/uv-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47a29843d6c2c14533492de585b5b7826a48f54e4796e47db4511b78f7738af5", size = 20199272, upload-time = "2025-10-10T19:01:31.436Z" }, 615 | { url = "https://files.pythonhosted.org/packages/44/ad/452124cd1ec0127f6ce277052fabd709aa18f51476a809fba8abb09cc734/uv-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cbe195d9a232344a8cf082e4fc4326a1f269fd4efe745d797a84d35008364cf", size = 21139676, upload-time = "2025-10-10T19:01:33.631Z" }, 616 | { url = "https://files.pythonhosted.org/packages/39/f7/54871ac979c31ba0f5897703d884b7b73399fdab83c6c054ec92c624c45a/uv-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:43ae1b5e4cb578a13299b4b105fc099e4300885d3ac0321b735d8c23d488bb1a", size = 22583678, upload-time = "2025-10-10T19:01:36.008Z" }, 617 | { url = "https://files.pythonhosted.org/packages/75/36/bc10c28b76b565b18b01b1b4a04f51ba19837db8adc8f4f0c9982c125a04/uv-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46e0ac8796d888d7885af9243f4ec265e88872a74582bf3a8072c0e75578698", size = 22223038, upload-time = "2025-10-10T19:01:38.444Z" }, 618 | { url = "https://files.pythonhosted.org/packages/fe/91/64f498195168891ae804489386dccd08a5c6a80fd9f35658161c9af8e33a/uv-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40b722a1891b42edf16573794281000479c0001b52574c10032206f3fb77870a", size = 21358696, upload-time = "2025-10-10T19:01:41.126Z" }, 619 | { url = "https://files.pythonhosted.org/packages/e0/77/0ceb3c0fed4f43f3cb387a76170115bdb873822c5c2dc6036d05dd5b2070/uv-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50128cbeae27c4cb58973c39a2110169d13676525397a3c2de5394448ea5c58f", size = 21244422, upload-time = "2025-10-10T19:01:43.91Z" }, 620 | { url = "https://files.pythonhosted.org/packages/e8/1c/5d6c9f6f648eda9db1567401c9d921d4f59afbbb4af83b5230b91a96aa48/uv-0.9.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:c6888f2a0d49819311780e158df0d09750f701095e46a59c3f5712f76bae952c", size = 20123453, upload-time = "2025-10-10T19:01:46.489Z" }, 621 | { url = "https://files.pythonhosted.org/packages/78/f5/d3896606ca57a358c175a343b34b3e1ebf29b31a6ae6ae6f3daf266db202/uv-0.9.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b7f6f3e1bfc0d2bdadc12e01814169dae4fbd60cedc8f634987d66ae68aab99a", size = 21236450, upload-time = "2025-10-10T19:01:49.292Z" }, 622 | { url = "https://files.pythonhosted.org/packages/92/6e/e5b3d1500e0c21b1dcb5be798322885d43b3ca0e696309e30029d55ffa6d/uv-0.9.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0cc3808e24f169869c7a0ad311cef672fef53cebcf6cc384a17be35c60146f4a", size = 20146874, upload-time = "2025-10-10T19:01:51.565Z" }, 623 | { url = "https://files.pythonhosted.org/packages/d6/04/966fe4aed5f4753f2c04af611263a0cbc64e84d21b13979258a64c08e801/uv-0.9.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:042f8b3773160b769efbbf34f704f99efddb248283325309bb78ffe6e7c96425", size = 20527007, upload-time = "2025-10-10T19:01:53.893Z" }, 624 | { url = "https://files.pythonhosted.org/packages/7d/fc/6cb19e86592ffe51c9d2b33ca51dce700e22a42da3de9f4245f735357cb2/uv-0.9.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d7072e10e2d4e3342476a831e4adf1a7a490d8a3a99c5538d3e13400c4849b29", size = 21469236, upload-time = "2025-10-10T19:01:56.465Z" }, 625 | { url = "https://files.pythonhosted.org/packages/24/95/1e539dae42d124749faaae22d27e239911e5b1676fedab716434c56aa869/uv-0.9.2-py3-none-win32.whl", hash = "sha256:ed2ce9a58e3e9df51387907163da649129e71b75c98ab7fec1d23c57640c62ae", size = 19376589, upload-time = "2025-10-10T19:01:58.794Z" }, 626 | { url = "https://files.pythonhosted.org/packages/27/33/f1b73f9d019dcfe83fb545e5d0d080952300248d547433bb975ad7fd3331/uv-0.9.2-py3-none-win_amd64.whl", hash = "sha256:27815edaa0be4f6346b24f3b6a4389f7747ae98538f4e2e8946090bb959389cc", size = 21359072, upload-time = "2025-10-10T19:02:01.13Z" }, 627 | { url = "https://files.pythonhosted.org/packages/39/aa/e23b96625a51266f2218cf0e0751ada65f138a57910fc0810cdd9d28d76f/uv-0.9.2-py3-none-win_arm64.whl", hash = "sha256:86e8fc3424266c9afca829d3936e91b677027658a253c7bb2a299fafd97aab0d", size = 19810880, upload-time = "2025-10-10T19:02:03.868Z" }, 628 | ] 629 | 630 | [[package]] 631 | name = "uvicorn" 632 | version = "0.37.0" 633 | source = { registry = "https://pypi.org/simple" } 634 | dependencies = [ 635 | { name = "click" }, 636 | { name = "h11" }, 637 | ] 638 | sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } 639 | wheels = [ 640 | { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, 641 | ] 642 | 643 | [[package]] 644 | name = "virtualenv" 645 | version = "20.35.3" 646 | source = { registry = "https://pypi.org/simple" } 647 | dependencies = [ 648 | { name = "distlib" }, 649 | { name = "filelock" }, 650 | { name = "platformdirs" }, 651 | ] 652 | sdist = { url = "https://files.pythonhosted.org/packages/a4/d5/b0ccd381d55c8f45d46f77df6ae59fbc23d19e901e2d523395598e5f4c93/virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44", size = 6002907, upload-time = "2025-10-10T21:23:33.178Z" } 653 | wheels = [ 654 | { url = "https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a", size = 5981061, upload-time = "2025-10-10T21:23:30.433Z" }, 655 | ] 656 | 657 | [[package]] 658 | name = "websockets" 659 | version = "15.0.1" 660 | source = { registry = "https://pypi.org/simple" } 661 | sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } 662 | wheels = [ 663 | { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, 664 | { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, 665 | { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, 666 | { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, 667 | { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, 668 | { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, 669 | { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, 670 | { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, 671 | { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, 672 | { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, 673 | { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, 674 | { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, 675 | ] 676 | --------------------------------------------------------------------------------