├── .github └── workflows │ └── cicd.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── action.yml ├── pydantic2ts ├── __init__.py ├── cli │ ├── __init__.py │ └── script.py ├── pydantic_v1.py └── pydantic_v2.py ├── pyproject.toml ├── tests ├── __init__.py ├── expected_results │ ├── computed_fields │ │ ├── output.ts │ │ └── v2_input.py │ ├── excluding_models │ │ ├── output.ts │ │ ├── v1_input.py │ │ └── v2_input.py │ ├── extra_fields │ │ ├── output.ts │ │ ├── v1_input.py │ │ └── v2_input.py │ ├── generics │ │ ├── output.ts │ │ ├── v1_input.py │ │ └── v2_input.py │ ├── single_module │ │ ├── output.ts │ │ ├── v1_input.py │ │ └── v2_input.py │ └── submodules │ │ ├── output.ts │ │ ├── v1_animals │ │ ├── __init__.py │ │ ├── cats.py │ │ └── dogs.py │ │ ├── v1_input.py │ │ ├── v2_animals │ │ ├── __init__.py │ │ ├── cats.py │ │ └── dogs.py │ │ └── v2_input.py └── test_script.py └── uv.lock /.github/workflows/cicd.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | lint: 7 | name: Lint code with ruff 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: astral-sh/ruff-action@v1 12 | with: 13 | src: ./pydantic2ts 14 | test: 15 | name: Run unit tests 16 | needs: lint 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, windows-latest, macOS-latest] 21 | python-version: ["3.9"] 22 | include: 23 | - os: ubuntu-latest 24 | python-version: "3.8" 25 | - os: ubuntu-latest 26 | python-version: "3.10" 27 | - os: ubuntu-latest 28 | python-version: "3.11" 29 | - os: ubuntu-latest 30 | python-version: "3.12" 31 | - os: ubuntu-latest 32 | python-version: "3.13" 33 | steps: 34 | - name: Check out repo 35 | uses: actions/checkout@v4 36 | - name: Install node 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: 20 40 | - name: Install json-schema-to-typescript 41 | run: npm i -g json-schema-to-typescript 42 | - name: Install uv 43 | uses: astral-sh/setup-uv@v3 44 | with: 45 | version: "0.5.2" 46 | - name: Run tests against 'pydantic@latest' 47 | run: | 48 | uv python install ${{ matrix.python-version }} 49 | uv sync --all-extras --dev 50 | uv run pytest --cov=pydantic2ts 51 | - name: (ubuntu 3.9) Run tests against 'pydantic==1.8.2' and generate an LCOV file for Coveralls 52 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' }} 53 | run: | 54 | uv add 'pydantic==1.8.2' 55 | uv run pytest --cov=pydantic2ts --cov-append 56 | uv run coverage lcov 57 | - name: (ubuntu 3.9) Upload to Coveralls 58 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' }} 59 | uses: coverallsapp/github-action@v2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | path-to-lcov: coverage.lcov 63 | build: 64 | name: Build pydantic2ts for distribution 65 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 66 | needs: test 67 | runs-on: ubuntu-latest 68 | steps: 69 | - name: Check out repo 70 | uses: actions/checkout@v4 71 | - name: Install uv 72 | uses: astral-sh/setup-uv@v3 73 | with: 74 | version: "0.5.2" 75 | - name: Install python 3.9 76 | run: uv python install 3.9 77 | - name: Build pydantic2ts 78 | run: uv build 79 | - name: Store the distribution 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: pydantic2ts-dist 83 | path: dist/ 84 | publish-to-pypi: 85 | name: Publish pydantic2ts to PyPI 86 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 87 | needs: build 88 | runs-on: ubuntu-latest 89 | environment: 90 | name: pypi 91 | url: https://pypi.org/p/pydantic-to-typescript 92 | permissions: 93 | id-token: write 94 | steps: 95 | - name: Download all the dists 96 | uses: actions/download-artifact@v4 97 | with: 98 | name: pydantic2ts-dist 99 | path: dist/ 100 | - name: Publish distributions to PyPI 101 | uses: pypa/gh-action-pypi-publish@release/v1 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | .venv-v1 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | # my stuff 142 | .idea 143 | .private.txt 144 | 145 | # VS Code config 146 | .vscode 147 | 148 | # test outputs 149 | output_debug.ts 150 | schema_debug.json -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Phillip Dupuis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pydantic-to-typescript 2 | 3 | [![PyPI version](https://badge.fury.io/py/pydantic-to-typescript.svg)](https://badge.fury.io/py/pydantic-to-typescript) 4 | [![CI/CD](https://github.com/phillipdupuis/pydantic-to-typescript/actions/workflows/cicd.yml/badge.svg)](https://github.com/phillipdupuis/pydantic-to-typescript/actions/workflows/cicd.yml) 5 | [![Coverage Status](https://coveralls.io/repos/github/phillipdupuis/pydantic-to-typescript/badge.svg?branch=master)](https://coveralls.io/github/phillipdupuis/pydantic-to-typescript?branch=master) 6 | 7 | A simple CLI tool for converting pydantic models into typescript interfaces. 8 | It supports all versions of pydantic, with polyfills for older versions to ensure that the resulting typescript definitions are stable and accurate. 9 | 10 | Useful for any scenario in which python and javascript applications are interacting, since it allows you to have a single source of truth for type definitions. 11 | 12 | This tool requires that you have the lovely json2ts CLI utility installed. 13 | Instructions can be found here: https://www.npmjs.com/package/json-schema-to-typescript 14 | 15 | ### Installation 16 | 17 | ```bash 18 | $ pip install pydantic-to-typescript 19 | ``` 20 | 21 | ### Pydantic V2 support 22 | 23 | If you are encountering issues with `pydantic>2`, it is most likely because you're using an old version of `pydantic-to-typescript`. 24 | Run `pip install 'pydantic-to-typescript>2'` and/or add `pydantic-to-typescript>=2` to your project requirements. 25 | 26 | ### CI/CD 27 | 28 | You can now use `pydantic-to-typescript` to automatically validate and/or update typescript definitions as part of your CI/CD pipeline. 29 | 30 | The github action can be found here: https://github.com/marketplace/actions/pydantic-to-typescript. 31 | The available inputs are documented here: https://github.com/phillipdupuis/pydantic-to-typescript/blob/master/action.yml. 32 | 33 | ### CLI 34 | 35 | | Prop | Description | 36 | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 37 | | ‑‑module | name or filepath of the python module you would like to convert. All the pydantic models within it will be converted to typescript interfaces. Discoverable submodules will also be checked. | 38 | | ‑‑output | name of the file the typescript definitions should be written to. Ex: './frontend/apiTypes.ts' | 39 | | ‑‑exclude | name of a pydantic model which should be omitted from the resulting typescript definitions. This option can be defined multiple times, ex: `--exclude Foo --exclude Bar` to exclude both the Foo and Bar models from the output. | 40 | | ‑‑json2ts‑cmd | optional, the command used to invoke json2ts. The default is 'json2ts'. Specify this if you have it installed locally (ex: 'yarn json2ts') or if the exact path to the executable is required (ex: /myproject/node_modules/bin/json2ts) | 41 | 42 | --- 43 | 44 | ### Usage 45 | 46 | Define your pydantic models (ex: /backend/api.py): 47 | 48 | ```python 49 | from fastapi import FastAPI 50 | from pydantic import BaseModel 51 | from typing import List, Optional 52 | 53 | api = FastAPI() 54 | 55 | class LoginCredentials(BaseModel): 56 | username: str 57 | password: str 58 | 59 | class Profile(BaseModel): 60 | username: str 61 | age: Optional[int] 62 | hobbies: List[str] 63 | 64 | class LoginResponseData(BaseModel): 65 | token: str 66 | profile: Profile 67 | 68 | @api.post('/login/', response_model=LoginResponseData) 69 | def login(body: LoginCredentials): 70 | profile = Profile(**body.dict(), age=72, hobbies=['cats']) 71 | return LoginResponseData(token='very-secure', profile=profile) 72 | ``` 73 | 74 | Execute the command for converting these models into typescript definitions, via: 75 | 76 | ```bash 77 | $ pydantic2ts --module backend.api --output ./frontend/apiTypes.ts 78 | ``` 79 | 80 | or: 81 | 82 | ```bash 83 | $ pydantic2ts --module ./backend/api.py --output ./frontend/apiTypes.ts 84 | ``` 85 | 86 | or: 87 | 88 | ```python 89 | from pydantic2ts import generate_typescript_defs 90 | 91 | generate_typescript_defs("backend.api", "./frontend/apiTypes.ts") 92 | ``` 93 | 94 | The models are now defined in typescript... 95 | 96 | ```ts 97 | /* tslint:disable */ 98 | /** 99 | /* This file was automatically generated from pydantic models by running pydantic2ts. 100 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 101 | */ 102 | 103 | export interface LoginCredentials { 104 | username: string; 105 | password: string; 106 | } 107 | export interface LoginResponseData { 108 | token: string; 109 | profile: Profile; 110 | } 111 | export interface Profile { 112 | username: string; 113 | age?: number; 114 | hobbies: string[]; 115 | } 116 | ``` 117 | 118 | ...and can be used in your typescript code with complete confidence. 119 | 120 | ```ts 121 | import { LoginCredentials, LoginResponseData } from "./apiTypes.ts"; 122 | 123 | async function login( 124 | credentials: LoginCredentials, 125 | resolve: (data: LoginResponseData) => void, 126 | reject: (error: string) => void 127 | ) { 128 | try { 129 | const response: Response = await fetch("/login/", { 130 | method: "POST", 131 | headers: { "Content-Type": "application/json" }, 132 | body: JSON.stringify(credentials), 133 | }); 134 | const data: LoginResponseData = await response.json(); 135 | resolve(data); 136 | } catch (error) { 137 | reject(error.message); 138 | } 139 | } 140 | ``` 141 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Pydantic to Typescript 2 | description: | 3 | Convert pydantic models into typescript definitions and ensure that your type definitions are in sync. 4 | author: Phillip Dupuis 5 | inputs: 6 | python-module: 7 | required: true 8 | description: > 9 | name or filepath of the root python module. 10 | Typescript interfaces will be generated for all of the pydantic models 11 | contained within this module and its discoverable submodules. 12 | # TODO: maybe add ts-repo option? In case the typescript stuff is in a different repository. (But default to current repo if unspecified) 13 | ts-file: 14 | required: true 15 | description: > 16 | path to the file where the resulting typescript definitions will be saved. 17 | Example: './frontend/apiTypes.ts'. 18 | exclude-models: 19 | required: false 20 | default: "" 21 | description: > 22 | comma-separated list of the names of any pydantic models which should 23 | be omitted from the resulting typescript definitions. 24 | fail-on-mismatch: 25 | required: false 26 | type: boolean 27 | default: true 28 | description: > 29 | Boolean flag indicating if type definition mismatches should cause this action to fail. 30 | GITHUB_TOKEN: 31 | required: false 32 | description: > 33 | Value for secrets.GITHUB_TOKEN, necessary for pull requests to be automatically opened. 34 | 35 | outputs: 36 | mismatch: 37 | value: ${{ steps.check-ts-defs.outputs.files_changed || 'false' }} 38 | description: > 39 | If true, the current typescript definitions in 'inputs.ts-file' are different 40 | from the ones which were automatically generated from the pydantic models. 41 | 42 | runs: 43 | using: composite 44 | steps: 45 | - name: Set up Python 46 | uses: actions/setup-python@v5 47 | with: 48 | python-version: "3.x" 49 | - name: Install pydantic-to-typescript 50 | shell: bash 51 | run: | 52 | python -m pip install -U pip wheel pydantic-to-typescript 53 | - name: Set up Node.js 20 54 | uses: actions/setup-node@v4 55 | with: 56 | node-version: 20 57 | - name: Install json-schema-to-typescript 58 | shell: bash 59 | run: | 60 | npm i json-schema-to-typescript --location=global 61 | - name: Run pydantic2ts 62 | shell: bash 63 | env: 64 | INPUT_PY_MODULE: ${{ inputs.python-module }} 65 | INPUT_TS_FILE: ${{ inputs.ts-file }} 66 | INPUT_EXCLUDE_MODELS: ${{ inputs.exclude-models }} 67 | run: | 68 | CMD="pydantic2ts --module $INPUT_PY_MODULE --output $INPUT_TS_FILE" 69 | for model in ${INPUT_EXCLUDE_MODELS//,/ } 70 | do 71 | CMD+=" --exclude $model" 72 | done 73 | eval "$CMD" 74 | - name: Check if typescript definitions changed 75 | uses: tj-actions/verify-changed-files@v10.1 76 | id: check-ts-defs 77 | with: 78 | files: ${{ inputs.ts-file }} 79 | - name: create pull request for typescript definition updates 80 | if: steps.check-ts-defs.outputs.files_changed == 'true' 81 | uses: peter-evans/create-pull-request@v4 82 | with: 83 | token: ${{ inputs.GITHUB_TOKEN }} 84 | add-paths: ${{ inputs.ts-file }} 85 | branch: update-ts-definitions 86 | - name: halt execution if type definition mismatch detected 87 | if: inputs.fail-on-mismatch == 'true' && steps.check-ts-defs.outputs.files_changed == 'true' 88 | shell: bash 89 | run: | 90 | echo "Typescript definitions and pydantic models are out of sync" 91 | exit 1 92 | 93 | branding: 94 | icon: check-circle 95 | color: green 96 | -------------------------------------------------------------------------------- /pydantic2ts/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic2ts.cli.script import generate_typescript_defs 2 | 3 | __all__ = ("generate_typescript_defs",) 4 | -------------------------------------------------------------------------------- /pydantic2ts/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phillipdupuis/pydantic-to-typescript/e5b6cdde813ad4dc339b57c8b6e58ff2a182d17e/pydantic2ts/cli/__init__.py -------------------------------------------------------------------------------- /pydantic2ts/cli/script.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import importlib 3 | import inspect 4 | import json 5 | import logging 6 | import os 7 | import shutil 8 | import sys 9 | from contextlib import ExitStack, contextmanager 10 | from importlib.util import module_from_spec, spec_from_file_location 11 | from tempfile import mkdtemp 12 | from types import ModuleType 13 | from typing import ( 14 | TYPE_CHECKING, 15 | Any, 16 | Dict, 17 | Generator, 18 | List, 19 | Optional, 20 | Tuple, 21 | Type, 22 | Union, 23 | ) 24 | from uuid import uuid4 25 | 26 | import pydantic2ts.pydantic_v1 as v1 27 | import pydantic2ts.pydantic_v2 as v2 28 | 29 | if TYPE_CHECKING: # pragma: no cover 30 | from pydantic.config import ConfigDict 31 | from pydantic.v1.config import BaseConfig 32 | from pydantic.v1.fields import ModelField 33 | 34 | 35 | LOG = logging.getLogger("pydantic2ts") 36 | 37 | _USELESS_ENUM_DESCRIPTION = "An enumeration." 38 | _USELESS_STR_DESCRIPTION = inspect.getdoc(str) 39 | 40 | 41 | def _import_module(path: str) -> ModuleType: 42 | """ 43 | Helper which allows modules to be specified by either dotted path notation or by filepath. 44 | 45 | If we import by filepath, we must also assign a name to it and add it to sys.modules BEFORE 46 | calling 'spec.loader.exec_module' because there is code in pydantic which requires that the 47 | definition exist in sys.modules under that name. 48 | """ 49 | try: 50 | if os.path.exists(path): 51 | name = uuid4().hex 52 | spec = spec_from_file_location(name, path, submodule_search_locations=[]) 53 | assert spec is not None, f"spec_from_file_location failed for {path}" 54 | module = module_from_spec(spec) 55 | sys.modules[name] = module 56 | assert spec.loader is not None, f"loader is None for {path}" 57 | spec.loader.exec_module(module) 58 | return module 59 | else: 60 | return importlib.import_module(path) 61 | except Exception as e: 62 | LOG.error( 63 | "The --module argument must be a module path separated by dots or a valid filepath" 64 | ) 65 | raise e 66 | 67 | 68 | def _is_submodule(obj: Any, module_name: str) -> bool: 69 | """ 70 | Return true if an object is a submodule 71 | """ 72 | return inspect.ismodule(obj) and getattr(obj, "__name__", "").startswith(f"{module_name}.") 73 | 74 | 75 | def _is_v1_model(obj: Any) -> bool: 76 | """ 77 | Return true if an object is a 'concrete' pydantic V1 model. 78 | """ 79 | if not inspect.isclass(obj): 80 | return False 81 | elif obj is v1.BaseModel: 82 | return False 83 | elif v1.GenericModel and issubclass(obj, v1.GenericModel): 84 | return bool(obj.__concrete__) 85 | else: 86 | return issubclass(obj, v1.BaseModel) 87 | 88 | 89 | def _is_v2_model(obj: Any) -> bool: 90 | """ 91 | Return true if an object is a 'concrete' pydantic V2 model. 92 | """ 93 | if not v2.enabled: 94 | return False 95 | elif not inspect.isclass(obj): 96 | return False 97 | elif obj is v2.BaseModel: 98 | return False 99 | elif not issubclass(obj, v2.BaseModel): 100 | return False 101 | generic_metadata = getattr(obj, "__pydantic_generic_metadata__", {}) 102 | generic_parameters = generic_metadata.get("parameters") 103 | return not generic_parameters 104 | 105 | 106 | def _is_pydantic_model(obj: Any) -> bool: 107 | """ 108 | Return true if an object is a concrete model for either V1 or V2 of pydantic. 109 | """ 110 | return _is_v1_model(obj) or _is_v2_model(obj) 111 | 112 | 113 | def _is_nullable(schema: Dict[str, Any]) -> bool: 114 | """ 115 | Return true if a JSON schema has 'null' as one of its types. 116 | """ 117 | if schema.get("type") == "null": 118 | return True 119 | if isinstance(schema.get("type"), list) and "null" in schema["type"]: 120 | return True 121 | if isinstance(schema.get("anyOf"), list): 122 | return any(_is_nullable(s) for s in schema["anyOf"]) 123 | return False 124 | 125 | 126 | def _get_model_config(model: Type[Any]) -> "Union[ConfigDict, Type[BaseConfig]]": 127 | """ 128 | Return the 'config' for a pydantic model. 129 | In version 1 of pydantic, this is a class. In version 2, it's a dictionary. 130 | """ 131 | if hasattr(model, "Config") and inspect.isclass(model.Config): 132 | return model.Config 133 | return model.model_config 134 | 135 | 136 | def _get_model_json_schema(model: Type[Any]) -> Dict[str, Any]: 137 | """ 138 | Generate the JSON schema for a pydantic model. 139 | """ 140 | if _is_v1_model(model): 141 | return json.loads(model.schema_json()) 142 | return model.model_json_schema(mode="serialization") 143 | 144 | 145 | def _extract_pydantic_models(module: ModuleType) -> List[type]: 146 | """ 147 | Given a module, return a list of the pydantic models contained within it. 148 | """ 149 | models: List[type] = [] 150 | module_name = module.__name__ 151 | 152 | for _, model in inspect.getmembers(module, _is_pydantic_model): 153 | models.append(model) 154 | 155 | for _, submodule in inspect.getmembers(module, lambda obj: _is_submodule(obj, module_name)): 156 | models.extend(_extract_pydantic_models(submodule)) 157 | 158 | return models 159 | 160 | 161 | def _clean_json_schema(schema: Dict[str, Any], model: Any = None) -> None: 162 | """ 163 | Clean up the resulting JSON schemas via the following steps: 164 | 165 | 1) Get rid of descriptions that are auto-generated and just add noise: 166 | - "An enumeration." for Enums 167 | - `inspect.getdoc(str)` for Literal types 168 | 2) Remove titles from JSON schema properties. 169 | If we don't do this, each property will have its own interface in the 170 | resulting typescript file (which is a LOT of unnecessary noise). 171 | 3) If it's a V1 model, ensure that nullability is properly represented. 172 | https://github.com/pydantic/pydantic/issues/1270 173 | """ 174 | description = schema.get("description") 175 | 176 | if "enum" in schema and description == _USELESS_ENUM_DESCRIPTION: 177 | del schema["description"] 178 | elif description == _USELESS_STR_DESCRIPTION: 179 | del schema["description"] 180 | 181 | properties: Dict[str, Dict[str, Any]] = schema.get("properties", {}) 182 | 183 | for prop in properties.values(): 184 | prop.pop("title", None) 185 | 186 | if _is_v1_model(model): 187 | fields: List["ModelField"] = list(model.__fields__.values()) 188 | fields_that_should_be_nullable = [f for f in fields if f.allow_none] 189 | for field in fields_that_should_be_nullable: 190 | try: 191 | name = field.alias 192 | prop = properties.get(field.alias) 193 | if prop and not _is_nullable(prop): 194 | properties[name] = {"anyOf": [prop, {"type": "null"}]} 195 | except Exception: 196 | LOG.error( 197 | f"Failed to ensure nullability for field {field.alias}.", 198 | exc_info=True, 199 | ) 200 | 201 | 202 | def _clean_output_file(output_filename: str) -> None: 203 | """ 204 | Clean up the resulting typescript definitions via the following steps: 205 | 206 | 1. Remove the "_Master_" model. 207 | It exists solely to serve as a namespace for the target models. 208 | By rolling them all up into a single model, we can generate a single output file. 209 | 2. Add a banner comment with clear instructions for regenerating the typescript definitions. 210 | """ 211 | with open(output_filename, "r") as f: 212 | lines = f.readlines() 213 | 214 | start, end = None, None 215 | for i, line in enumerate(lines): 216 | if line.rstrip("\r\n") == "export interface _Master_ {": 217 | start = i 218 | elif (start is not None) and line.rstrip("\r\n") == "}": 219 | end = i 220 | break 221 | 222 | assert start is not None, "Could not find the start of the _Master_ interface." 223 | assert end is not None, "Could not find the end of the _Master_ interface." 224 | 225 | banner_comment_lines = [ 226 | "/* tslint:disable */\n", 227 | "/* eslint-disable */\n", 228 | "/**\n", 229 | "/* This file was automatically generated from pydantic models by running pydantic2ts.\n", 230 | "/* Do not modify it by hand - just update the pydantic models and then re-run the script\n", 231 | "*/\n\n", 232 | ] 233 | 234 | new_lines = banner_comment_lines + lines[:start] + lines[(end + 1) :] 235 | 236 | with open(output_filename, "w") as f: 237 | f.writelines(new_lines) 238 | 239 | 240 | @contextmanager 241 | def _schema_generation_overrides( 242 | model: Type[Any], 243 | ) -> Generator[None, None, None]: 244 | """ 245 | Temporarily override the 'extra' setting for a model, 246 | changing it to 'forbid' unless it was EXPLICITLY set to 'allow'. 247 | This prevents '[k: string]: any' from automatically being added to every interface. 248 | """ 249 | revert: Dict[str, Any] = {} 250 | config = _get_model_config(model) 251 | try: 252 | if isinstance(config, dict): 253 | if config.get("extra") != "allow": 254 | revert["extra"] = config.get("extra") 255 | config["extra"] = "forbid" 256 | else: 257 | if config.extra != "allow": 258 | revert["extra"] = config.extra 259 | config.extra = "forbid" # type: ignore 260 | yield 261 | finally: 262 | for key, value in revert.items(): 263 | if isinstance(config, dict): 264 | config[key] = value 265 | else: 266 | setattr(config, key, value) 267 | 268 | 269 | def _generate_json_schema(models: List[type]) -> str: 270 | """ 271 | Create a top-level '_Master_' model with references to each of the actual models. 272 | Generate the schema for this model, which will include the schemas for all the 273 | nested models. Then clean up the schema. 274 | """ 275 | with ExitStack() as stack: 276 | models_by_name: Dict[str, type] = {} 277 | models_as_fields: Dict[str, Tuple[type, Any]] = {} 278 | 279 | for model in models: 280 | stack.enter_context(_schema_generation_overrides(model)) 281 | name = model.__name__ 282 | models_by_name[name] = model 283 | models_as_fields[name] = (model, ...) 284 | 285 | use_v1_tools = any(issubclass(m, v1.BaseModel) for m in models) 286 | create_model = v1.create_model if use_v1_tools else v2.create_model # type: ignore 287 | master_model = create_model("_Master_", **models_as_fields) # type: ignore 288 | master_schema = _get_model_json_schema(master_model) # type: ignore 289 | 290 | defs_key = "$defs" if "$defs" in master_schema else "definitions" 291 | defs: Dict[str, Any] = master_schema.get(defs_key, {}) 292 | 293 | for name, schema in defs.items(): 294 | _clean_json_schema(schema, models_by_name.get(name)) 295 | 296 | return json.dumps(master_schema, indent=2) 297 | 298 | 299 | def generate_typescript_defs( 300 | module: str, 301 | output: str, 302 | exclude: Tuple[str, ...] = (), 303 | json2ts_cmd: str = "json2ts", 304 | ) -> None: 305 | """ 306 | Convert the pydantic models in a python module into typescript interfaces. 307 | 308 | :param module: python module containing pydantic model definitions. 309 | example: my_project.api.schemas 310 | :param output: file that the typescript definitions will be written to 311 | :param exclude: optional, a tuple of names for pydantic models which 312 | should be omitted from the typescript output. 313 | :param json2ts_cmd: optional, the command that will execute json2ts. 314 | Provide this if the executable is not discoverable 315 | or if it's locally installed (ex: 'yarn json2ts'). 316 | """ 317 | if " " not in json2ts_cmd and not shutil.which(json2ts_cmd): 318 | raise Exception( 319 | "json2ts must be installed. Instructions can be found here: " 320 | "https://www.npmjs.com/package/json-schema-to-typescript" 321 | ) 322 | 323 | LOG.info("Finding pydantic models...") 324 | 325 | models = _extract_pydantic_models(_import_module(module)) 326 | 327 | if exclude: 328 | models = [ 329 | m for m in models if (m.__name__ not in exclude and m.__qualname__ not in exclude) 330 | ] 331 | 332 | if not models: 333 | LOG.info("No pydantic models found, exiting.") 334 | return 335 | 336 | LOG.info("Generating JSON schema from pydantic models...") 337 | 338 | schema = _generate_json_schema(models) 339 | schema_dir = mkdtemp() 340 | schema_file_path = os.path.join(schema_dir, "schema.json") 341 | 342 | with open(schema_file_path, "w") as f: 343 | f.write(schema) 344 | 345 | LOG.info("Converting JSON schema to typescript definitions...") 346 | 347 | json2ts_exit_code = os.system( 348 | f'{json2ts_cmd} -i {schema_file_path} -o {output} --bannerComment ""' 349 | ) 350 | 351 | shutil.rmtree(schema_dir) 352 | 353 | if json2ts_exit_code == 0: 354 | _clean_output_file(output) 355 | LOG.info(f"Saved typescript definitions to {output}.") 356 | else: 357 | raise RuntimeError(f'"{json2ts_cmd}" failed with exit code {json2ts_exit_code}.') 358 | 359 | 360 | def parse_cli_args(args: Optional[List[str]] = None) -> argparse.Namespace: 361 | """ 362 | Parses the command-line arguments passed to pydantic2ts. 363 | """ 364 | parser = argparse.ArgumentParser( 365 | prog="pydantic2ts", 366 | description=main.__doc__, 367 | formatter_class=argparse.RawTextHelpFormatter, 368 | ) 369 | parser.add_argument( 370 | "--module", 371 | help="name or filepath of the python module.\n" 372 | "Discoverable submodules will also be checked.", 373 | required=True, 374 | ) 375 | parser.add_argument( 376 | "--output", 377 | help="name of the file the typescript definitions should be written to.", 378 | required=True, 379 | ) 380 | parser.add_argument( 381 | "--exclude", 382 | action="append", 383 | default=[], 384 | help="name of a pydantic model which should be omitted from the results.\n" 385 | "This option can be defined multiple times.", 386 | ) 387 | parser.add_argument( 388 | "--json2ts-cmd", 389 | dest="json2ts_cmd", 390 | default="json2ts", 391 | help="path to the json-schema-to-typescript executable.\n" 392 | "Provide this if it's not discoverable or if it's only installed locally (example: 'yarn json2ts').\n" 393 | "(default: json2ts)", 394 | ) 395 | return parser.parse_args(args) 396 | 397 | 398 | def main() -> None: 399 | """ 400 | CLI entrypoint to run :func:`generate_typescript_defs` 401 | """ 402 | logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s") 403 | args = parse_cli_args() 404 | return generate_typescript_defs( 405 | args.module, 406 | args.output, 407 | tuple(args.exclude), 408 | args.json2ts_cmd, 409 | ) 410 | 411 | 412 | if __name__ == "__main__": 413 | main() 414 | -------------------------------------------------------------------------------- /pydantic2ts/pydantic_v1.py: -------------------------------------------------------------------------------- 1 | try: 2 | from pydantic.v1 import BaseModel, create_model # type: ignore 3 | from pydantic.v1.generics import GenericModel 4 | 5 | enabled = True 6 | except ImportError: 7 | from pydantic import BaseModel, create_model 8 | 9 | enabled = True 10 | 11 | try: 12 | from pydantic.generics import GenericModel 13 | except ImportError: # pragma: no cover 14 | GenericModel = None 15 | 16 | __all__ = ("BaseModel", "GenericModel", "create_model", "enabled") 17 | -------------------------------------------------------------------------------- /pydantic2ts/pydantic_v2.py: -------------------------------------------------------------------------------- 1 | try: 2 | from pydantic.version import VERSION 3 | 4 | assert VERSION.startswith("2") 5 | 6 | from pydantic import BaseModel, create_model 7 | 8 | enabled = True 9 | except (ImportError, AssertionError, AttributeError): 10 | BaseModel = None 11 | create_model = None 12 | enabled = False 13 | 14 | __all__ = ("BaseModel", "create_model", "enabled") 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pydantic-to-typescript" 3 | version = "2.0.0" 4 | description = "Convert pydantic models to typescript interfaces" 5 | authors = [ 6 | {name = "Phillip Dupuis", email = "phillip_dupuis@alumni.brown.edu"}, 7 | ] 8 | license = {text = "MIT"} 9 | readme = "README.md" 10 | requires-python = ">=3.8" 11 | keywords = ["pydantic", "typescript", "annotations", "validation", "interface"] 12 | classifiers = [ 13 | "Development Status :: 5 - Production/Stable", 14 | "License :: OSI Approved :: MIT License", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 3 :: Only", 18 | "Programming Language :: Python :: 3.8", 19 | "Programming Language :: Python :: 3.9", 20 | "Programming Language :: Python :: 3.10", 21 | "Programming Language :: Python :: 3.11", 22 | "Programming Language :: Python :: 3.12", 23 | "Programming Language :: Python :: 3.13", 24 | ] 25 | dependencies = [ 26 | "pydantic", 27 | ] 28 | 29 | [project.urls] 30 | Homepage = "https://github.com/phillipdupuis/pydantic-to-typescript" 31 | Repository = "https://github.com/phillipdupuis/pydantic-to-typescript" 32 | 33 | [project.scripts] 34 | pydantic2ts = "pydantic2ts.cli.script:main" 35 | 36 | [project.optional-dependencies] 37 | dev = [ 38 | "pytest", 39 | "pytest-cov", 40 | "coverage", 41 | "ruff", 42 | ] 43 | 44 | [build-system] 45 | requires = ["hatchling"] 46 | build-backend = "hatchling.build" 47 | 48 | [tool.hatch.build.targets.wheel] 49 | packages = ["pydantic2ts"] 50 | 51 | [tool.ruff] 52 | line-length = 100 53 | indent-width = 4 54 | 55 | [tool.ruff.format] 56 | quote-style = "double" 57 | 58 | [tool.ruff.lint] 59 | select = ["E", "F", "I", "B", "W"] 60 | fixable = ["ALL"] 61 | ignore = ["E501"] -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phillipdupuis/pydantic-to-typescript/e5b6cdde813ad4dc339b57c8b6e58ff2a182d17e/tests/__init__.py -------------------------------------------------------------------------------- /tests/expected_results/computed_fields/output.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | /* This file was automatically generated from pydantic models by running pydantic2ts. 5 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 6 | */ 7 | 8 | export interface Rectangle { 9 | width: number; 10 | length: number; 11 | area: number; 12 | } 13 | -------------------------------------------------------------------------------- /tests/expected_results/computed_fields/v2_input.py: -------------------------------------------------------------------------------- 1 | # https://docs.pydantic.dev/latest/usage/computed_fields/ 2 | 3 | from pydantic import BaseModel, computed_field 4 | 5 | 6 | class Rectangle(BaseModel): 7 | width: int 8 | length: int 9 | 10 | @computed_field 11 | @property 12 | def area(self) -> int: 13 | return self.width * self.length 14 | -------------------------------------------------------------------------------- /tests/expected_results/excluding_models/output.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | /* This file was automatically generated from pydantic models by running pydantic2ts. 5 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 6 | */ 7 | 8 | export interface Profile { 9 | username: string; 10 | age?: number | null; 11 | hobbies: string[]; 12 | } 13 | -------------------------------------------------------------------------------- /tests/expected_results/excluding_models/v1_input.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | try: 4 | from pydantic.v1 import BaseModel 5 | except ImportError: 6 | from pydantic import BaseModel 7 | 8 | 9 | class LoginCredentials(BaseModel): 10 | username: str 11 | password: str 12 | 13 | 14 | class Profile(BaseModel): 15 | username: str 16 | age: Optional[int] = None 17 | hobbies: List[str] 18 | 19 | 20 | class LoginResponseData(BaseModel): 21 | token: str 22 | profile: Profile 23 | -------------------------------------------------------------------------------- /tests/expected_results/excluding_models/v2_input.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class LoginCredentials(BaseModel): 7 | username: str 8 | password: str 9 | 10 | 11 | class Profile(BaseModel): 12 | username: str 13 | age: Optional[int] = None 14 | hobbies: List[str] 15 | 16 | 17 | class LoginResponseData(BaseModel): 18 | token: str 19 | profile: Profile 20 | -------------------------------------------------------------------------------- /tests/expected_results/extra_fields/output.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | /* This file was automatically generated from pydantic models by running pydantic2ts. 5 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 6 | */ 7 | 8 | export interface ModelExtraAllow { 9 | a: string; 10 | [k: string]: unknown; 11 | } 12 | export interface ModelExtraForbid { 13 | a: string; 14 | } 15 | export interface ModelExtraIgnore { 16 | a: string; 17 | } 18 | export interface ModelExtraNone { 19 | a: string; 20 | } 21 | -------------------------------------------------------------------------------- /tests/expected_results/extra_fields/v1_input.py: -------------------------------------------------------------------------------- 1 | try: 2 | from pydantic.v1 import BaseConfig, BaseModel, Extra 3 | except ImportError: 4 | from pydantic import BaseConfig, BaseModel, Extra 5 | 6 | 7 | class ModelExtraAllow(BaseModel): 8 | a: str 9 | 10 | class Config(BaseConfig): 11 | extra = Extra.allow 12 | 13 | 14 | class ModelExtraForbid(BaseModel): 15 | a: str 16 | 17 | class Config(BaseConfig): 18 | extra = Extra.forbid 19 | 20 | 21 | class ModelExtraIgnore(BaseModel): 22 | a: str 23 | 24 | class Config(BaseConfig): 25 | extra = Extra.ignore 26 | 27 | 28 | class ModelExtraNone(BaseModel): 29 | a: str 30 | -------------------------------------------------------------------------------- /tests/expected_results/extra_fields/v2_input.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, ConfigDict 2 | 3 | 4 | class ModelExtraAllow(BaseModel): 5 | model_config = ConfigDict(extra="allow") 6 | a: str 7 | 8 | 9 | class ModelExtraForbid(BaseModel): 10 | model_config = ConfigDict(extra="forbid") 11 | a: str 12 | 13 | 14 | class ModelExtraIgnore(BaseModel): 15 | model_config = ConfigDict(extra="ignore") 16 | a: str 17 | 18 | 19 | class ModelExtraNone(BaseModel): 20 | a: str 21 | -------------------------------------------------------------------------------- /tests/expected_results/generics/output.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | /* This file was automatically generated from pydantic models by running pydantic2ts. 5 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 6 | */ 7 | 8 | export interface Article { 9 | author: User; 10 | content: string; 11 | published: string; 12 | } 13 | export interface User { 14 | name: string; 15 | email: string; 16 | } 17 | export interface Error { 18 | code: number; 19 | message: string; 20 | } 21 | export interface ListArticlesResponse { 22 | data?: Article[] | null; 23 | error?: Error | null; 24 | } 25 | export interface ListUsersResponse { 26 | data?: User[] | null; 27 | error?: Error | null; 28 | } 29 | export interface UserProfile { 30 | name: string; 31 | email: string; 32 | joined: string; 33 | last_active: string; 34 | age: number; 35 | } 36 | export interface UserProfileResponse { 37 | data?: UserProfile | null; 38 | error?: Error | null; 39 | } 40 | -------------------------------------------------------------------------------- /tests/expected_results/generics/v1_input.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Generic, List, Optional, Type, TypeVar 3 | 4 | try: 5 | from pydantic.v1 import BaseModel 6 | from pydantic.v1.generics import GenericModel 7 | except ImportError: 8 | from pydantic import BaseModel 9 | from pydantic.generics import GenericModel 10 | 11 | T = TypeVar("T") 12 | 13 | 14 | class Error(BaseModel): 15 | code: int 16 | message: str 17 | 18 | 19 | class ApiResponse(GenericModel, Generic[T]): 20 | data: Optional[T] = None 21 | error: Optional[Error] = None 22 | 23 | 24 | def create_response_type(data_type: T, name: str) -> "Type[ApiResponse[T]]": 25 | """ 26 | Create a concrete implementation of ApiResponse and then applies the specified name. 27 | This is necessary because the name automatically generated by __concrete_name__ is 28 | really ugly, it just doesn't look good. 29 | """ 30 | t = ApiResponse[data_type] 31 | t.__name__ = name 32 | t.__qualname__ = name 33 | return t 34 | 35 | 36 | class User(BaseModel): 37 | name: str 38 | email: str 39 | 40 | 41 | class UserProfile(User): 42 | joined: datetime 43 | last_active: datetime 44 | age: int 45 | 46 | 47 | class Article(BaseModel): 48 | author: User 49 | content: str 50 | published: datetime 51 | 52 | 53 | ListUsersResponse = create_response_type(List[User], "ListUsersResponse") 54 | 55 | ListArticlesResponse = create_response_type(List[Article], "ListArticlesResponse") 56 | 57 | UserProfileResponse = create_response_type(UserProfile, "UserProfileResponse") 58 | -------------------------------------------------------------------------------- /tests/expected_results/generics/v2_input.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Generic, List, Optional, Type, TypeVar 3 | 4 | from pydantic import BaseModel 5 | 6 | T = TypeVar("T") 7 | 8 | 9 | class Error(BaseModel): 10 | code: int 11 | message: str 12 | 13 | 14 | class ApiResponse(BaseModel, Generic[T]): 15 | data: Optional[T] = None 16 | error: Optional[Error] = None 17 | 18 | 19 | def create_response_type(data_type: T, name: str) -> "Type[ApiResponse[T]]": 20 | """ 21 | Create a concrete implementation of ApiResponse and then applies the specified name. 22 | This is necessary because the name automatically generated by __concrete_name__ is 23 | really ugly, it just doesn't look good. 24 | """ 25 | t = ApiResponse[data_type] 26 | t.__name__ = name 27 | t.__qualname__ = name 28 | return t 29 | 30 | 31 | class User(BaseModel): 32 | name: str 33 | email: str 34 | 35 | 36 | class UserProfile(User): 37 | joined: datetime 38 | last_active: datetime 39 | age: int 40 | 41 | 42 | class Article(BaseModel): 43 | author: User 44 | content: str 45 | published: datetime 46 | 47 | 48 | ListUsersResponse = create_response_type(List[User], "ListUsersResponse") 49 | 50 | ListArticlesResponse = create_response_type(List[Article], "ListArticlesResponse") 51 | 52 | UserProfileResponse = create_response_type(UserProfile, "UserProfileResponse") 53 | -------------------------------------------------------------------------------- /tests/expected_results/single_module/output.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | /* This file was automatically generated from pydantic models by running pydantic2ts. 5 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 6 | */ 7 | 8 | export interface LoginCredentials { 9 | username: string; 10 | password: string; 11 | } 12 | export interface LoginResponseData { 13 | token: string; 14 | profile: Profile; 15 | } 16 | export interface Profile { 17 | username: string; 18 | age?: number | null; 19 | hobbies: string[]; 20 | } 21 | -------------------------------------------------------------------------------- /tests/expected_results/single_module/v1_input.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | try: 4 | from pydantic.v1 import BaseModel 5 | except ImportError: 6 | from pydantic import BaseModel 7 | 8 | 9 | class LoginCredentials(BaseModel): 10 | username: str 11 | password: str 12 | 13 | 14 | class Profile(BaseModel): 15 | username: str 16 | age: Optional[int] = None 17 | hobbies: List[str] 18 | 19 | 20 | class LoginResponseData(BaseModel): 21 | token: str 22 | profile: Profile 23 | -------------------------------------------------------------------------------- /tests/expected_results/single_module/v2_input.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class LoginCredentials(BaseModel): 7 | username: str 8 | password: str 9 | 10 | 11 | class Profile(BaseModel): 12 | username: str 13 | age: Optional[int] = None 14 | hobbies: List[str] 15 | 16 | 17 | class LoginResponseData(BaseModel): 18 | token: str 19 | profile: Profile 20 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/output.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | /* This file was automatically generated from pydantic models by running pydantic2ts. 5 | /* Do not modify it by hand - just update the pydantic models and then re-run the script 6 | */ 7 | 8 | export type CatBreed = "domestic shorthair" | "bengal" | "persian" | "siamese"; 9 | export type DogBreed = "mutt" | "labrador" | "golden retriever"; 10 | 11 | export interface AnimalShelter { 12 | address: string; 13 | cats: Cat[]; 14 | dogs: Dog[]; 15 | } 16 | export interface Cat { 17 | name: string; 18 | age: number; 19 | declawed: boolean; 20 | breed: CatBreed; 21 | } 22 | export interface Dog { 23 | name: string; 24 | age: number; 25 | breed: DogBreed; 26 | } 27 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/v1_animals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phillipdupuis/pydantic-to-typescript/e5b6cdde813ad4dc339b57c8b6e58ff2a182d17e/tests/expected_results/submodules/v1_animals/__init__.py -------------------------------------------------------------------------------- /tests/expected_results/submodules/v1_animals/cats.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | try: 4 | from pydantic.v1 import BaseModel 5 | except ImportError: 6 | from pydantic import BaseModel 7 | 8 | 9 | class CatBreed(str, Enum): 10 | domestic_shorthair = "domestic shorthair" 11 | bengal = "bengal" 12 | persian = "persian" 13 | siamese = "siamese" 14 | 15 | 16 | class Cat(BaseModel): 17 | name: str 18 | age: int 19 | declawed: bool 20 | breed: CatBreed 21 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/v1_animals/dogs.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | try: 4 | from pydantic.v1 import BaseModel 5 | except ImportError: 6 | from pydantic import BaseModel 7 | 8 | 9 | class DogBreed(str, Enum): 10 | mutt = "mutt" 11 | labrador = "labrador" 12 | golden_retriever = "golden retriever" 13 | 14 | 15 | class Dog(BaseModel): 16 | name: str 17 | age: int 18 | breed: DogBreed 19 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/v1_input.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | try: 4 | from pydantic.v1 import BaseModel 5 | except ImportError: 6 | from pydantic import BaseModel 7 | 8 | from .v1_animals.cats import Cat 9 | from .v1_animals.dogs import Dog 10 | 11 | 12 | class AnimalShelter(BaseModel): 13 | address: str 14 | cats: List[Cat] 15 | dogs: List[Dog] 16 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/v2_animals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phillipdupuis/pydantic-to-typescript/e5b6cdde813ad4dc339b57c8b6e58ff2a182d17e/tests/expected_results/submodules/v2_animals/__init__.py -------------------------------------------------------------------------------- /tests/expected_results/submodules/v2_animals/cats.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class CatBreed(str, Enum): 7 | domestic_shorthair = "domestic shorthair" 8 | bengal = "bengal" 9 | persian = "persian" 10 | siamese = "siamese" 11 | 12 | 13 | class Cat(BaseModel): 14 | name: str 15 | age: int 16 | declawed: bool 17 | breed: CatBreed 18 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/v2_animals/dogs.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class DogBreed(str, Enum): 7 | mutt = "mutt" 8 | labrador = "labrador" 9 | golden_retriever = "golden retriever" 10 | 11 | 12 | class Dog(BaseModel): 13 | name: str 14 | age: int 15 | breed: DogBreed 16 | -------------------------------------------------------------------------------- /tests/expected_results/submodules/v2_input.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pydantic import BaseModel 4 | 5 | from .v2_animals.cats import Cat 6 | from .v2_animals.dogs import Dog 7 | 8 | 9 | class AnimalShelter(BaseModel): 10 | address: str 11 | cats: List[Cat] 12 | dogs: List[Dog] 13 | -------------------------------------------------------------------------------- /tests/test_script.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | from itertools import product 4 | from pathlib import Path 5 | from typing import Optional, Tuple 6 | 7 | import pytest 8 | 9 | from pydantic2ts import generate_typescript_defs 10 | from pydantic2ts.cli.script import parse_cli_args 11 | from pydantic2ts.pydantic_v2 import enabled as v2_enabled 12 | 13 | _PYDANTIC_VERSIONS = (1, 2) if v2_enabled else (1,) 14 | _RESULTS_DIRECTORY = Path( 15 | os.path.join(os.path.dirname(os.path.realpath(__file__)), "expected_results") 16 | ) 17 | 18 | 19 | def _python_module_path(test_name: str, pydantic_version: int) -> str: 20 | return str(_RESULTS_DIRECTORY / test_name / f"v{pydantic_version}_input.py") 21 | 22 | 23 | def _expected_typescript_code(test_name: str) -> str: 24 | return (_RESULTS_DIRECTORY / test_name / "output.ts").read_text() 25 | 26 | 27 | def _run_test( 28 | tmp_path: Path, 29 | test_name: str, 30 | pydantic_version: int, 31 | *, 32 | module_path: Optional[str] = None, 33 | call_from_python: bool = False, 34 | exclude: Tuple[str, ...] = (), 35 | ): 36 | """ 37 | Execute pydantic2ts logic for converting pydantic models into tyepscript definitions. 38 | Compare the output with the expected output, verifying it is identical. 39 | """ 40 | module_path = module_path or _python_module_path(test_name, pydantic_version) 41 | output_path = str(tmp_path / f"{test_name}_v{pydantic_version}.ts") 42 | 43 | if call_from_python: 44 | generate_typescript_defs(module_path, output_path, exclude) 45 | else: 46 | cmd = f"pydantic2ts --module {module_path} --output {output_path}" 47 | for model_to_exclude in exclude: 48 | cmd += f" --exclude {model_to_exclude}" 49 | subprocess.run(cmd, shell=True, check=True) 50 | 51 | assert Path(output_path).read_text() == _expected_typescript_code(test_name) 52 | 53 | 54 | @pytest.mark.parametrize( 55 | "pydantic_version, call_from_python", 56 | product(_PYDANTIC_VERSIONS, [False, True]), 57 | ) 58 | def test_single_module(tmp_path: Path, pydantic_version: int, call_from_python: bool): 59 | _run_test(tmp_path, "single_module", pydantic_version, call_from_python=call_from_python) 60 | 61 | 62 | @pytest.mark.parametrize( 63 | "pydantic_version, call_from_python", 64 | product(_PYDANTIC_VERSIONS, [False, True]), 65 | ) 66 | def test_submodules(tmp_path: Path, pydantic_version: int, call_from_python: bool): 67 | _run_test(tmp_path, "submodules", pydantic_version, call_from_python=call_from_python) 68 | 69 | 70 | @pytest.mark.parametrize( 71 | "pydantic_version, call_from_python", 72 | product(_PYDANTIC_VERSIONS, [False, True]), 73 | ) 74 | def test_generics(tmp_path: Path, pydantic_version: int, call_from_python: bool): 75 | _run_test(tmp_path, "generics", pydantic_version, call_from_python=call_from_python) 76 | 77 | 78 | @pytest.mark.parametrize( 79 | "pydantic_version, call_from_python", 80 | product(_PYDANTIC_VERSIONS, [False, True]), 81 | ) 82 | def test_excluding_models(tmp_path: Path, pydantic_version: int, call_from_python: bool): 83 | _run_test( 84 | tmp_path, 85 | "excluding_models", 86 | pydantic_version, 87 | call_from_python=call_from_python, 88 | exclude=("LoginCredentials", "LoginResponseData"), 89 | ) 90 | 91 | 92 | @pytest.mark.parametrize( 93 | "pydantic_version, call_from_python", 94 | product([v for v in _PYDANTIC_VERSIONS if v > 1], [False, True]), 95 | ) 96 | def test_computed_fields(tmp_path: Path, pydantic_version: int, call_from_python: bool): 97 | _run_test(tmp_path, "computed_fields", pydantic_version, call_from_python=call_from_python) 98 | 99 | 100 | @pytest.mark.parametrize( 101 | "pydantic_version, call_from_python", 102 | product(_PYDANTIC_VERSIONS, [False, True]), 103 | ) 104 | def test_extra_fields(tmp_path: Path, pydantic_version: int, call_from_python: bool): 105 | _run_test(tmp_path, "extra_fields", pydantic_version, call_from_python=call_from_python) 106 | 107 | 108 | def test_relative_filepath(tmp_path: Path): 109 | test_name = "single_module" 110 | pydantic_version = _PYDANTIC_VERSIONS[0] 111 | absolute_path = _python_module_path(test_name, pydantic_version) 112 | relative_path = Path(absolute_path).relative_to(Path.cwd()) 113 | _run_test( 114 | tmp_path, 115 | test_name, 116 | pydantic_version, 117 | module_path=str(relative_path), 118 | ) 119 | 120 | 121 | def test_error_if_json2ts_not_installed(tmp_path: Path): 122 | module_path = _python_module_path("single_module", _PYDANTIC_VERSIONS[0]) 123 | output_path = str(tmp_path / "json2ts_test_output.ts") 124 | 125 | # If the json2ts command has no spaces and the executable cannot be found, 126 | # that means the user either hasn't installed json-schema-to-typescript or they made a typo. 127 | # We should raise a descriptive error with installation instructions. 128 | invalid_global_cmd = "someCommandWhichDefinitelyDoesNotExist" 129 | with pytest.raises(Exception) as exc1: 130 | generate_typescript_defs( 131 | module_path, 132 | output_path, 133 | json2ts_cmd=invalid_global_cmd, 134 | ) 135 | assert ( 136 | str(exc1.value) 137 | == "json2ts must be installed. Instructions can be found here: https://www.npmjs.com/package/json-schema-to-typescript" 138 | ) 139 | 140 | # But if the command DOES contain spaces (ex: "yarn json2ts") they're likely using a locally installed CLI. 141 | # We should not be validating the command in this case. 142 | # Instead we should just be *trying* to run it and checking the exit code. 143 | invalid_local_cmd = "yaaaarn json2tsbutwithatypo" 144 | with pytest.raises(RuntimeError) as exc2: 145 | generate_typescript_defs( 146 | module_path, 147 | output_path, 148 | json2ts_cmd=invalid_local_cmd, 149 | ) 150 | assert str(exc2.value).startswith(f'"{invalid_local_cmd}" failed with exit code ') 151 | 152 | 153 | def test_error_if_invalid_module_path(tmp_path: Path): 154 | with pytest.raises(ModuleNotFoundError): 155 | generate_typescript_defs("fake_module", str(tmp_path / "fake_module_output.ts")) 156 | 157 | 158 | def test_parse_cli_args(): 159 | args_basic = parse_cli_args(["--module", "my_module.py", "--output", "myOutput.ts"]) 160 | assert args_basic.module == "my_module.py" 161 | assert args_basic.output == "myOutput.ts" 162 | assert args_basic.exclude == [] 163 | assert args_basic.json2ts_cmd == "json2ts" 164 | args_with_excludes = parse_cli_args( 165 | [ 166 | "--module", 167 | "my_module.py", 168 | "--output", 169 | "myOutput.ts", 170 | "--exclude", 171 | "Foo", 172 | "--exclude", 173 | "Bar", 174 | ] 175 | ) 176 | assert args_with_excludes.exclude == ["Foo", "Bar"] 177 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.8" 3 | 4 | [[package]] 5 | name = "annotated-types" 6 | version = "0.7.0" 7 | source = { registry = "https://pypi.org/simple" } 8 | dependencies = [ 9 | { name = "typing-extensions", marker = "python_full_version < '3.9'" }, 10 | ] 11 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 12 | wheels = [ 13 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 14 | ] 15 | 16 | [[package]] 17 | name = "colorama" 18 | version = "0.4.6" 19 | source = { registry = "https://pypi.org/simple" } 20 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 21 | wheels = [ 22 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 23 | ] 24 | 25 | [[package]] 26 | name = "coverage" 27 | version = "7.6.1" 28 | source = { registry = "https://pypi.org/simple" } 29 | sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 } 30 | wheels = [ 31 | { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690 }, 32 | { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127 }, 33 | { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654 }, 34 | { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598 }, 35 | { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732 }, 36 | { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816 }, 37 | { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325 }, 38 | { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418 }, 39 | { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343 }, 40 | { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136 }, 41 | { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796 }, 42 | { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244 }, 43 | { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279 }, 44 | { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859 }, 45 | { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549 }, 46 | { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477 }, 47 | { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134 }, 48 | { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910 }, 49 | { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348 }, 50 | { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230 }, 51 | { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 }, 52 | { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 }, 53 | { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 }, 54 | { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 }, 55 | { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455 }, 56 | { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 }, 57 | { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 }, 58 | { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 }, 59 | { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 }, 60 | { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 }, 61 | { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 }, 62 | { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 }, 63 | { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 }, 64 | { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 }, 65 | { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 }, 66 | { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 }, 67 | { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 }, 68 | { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 }, 69 | { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 }, 70 | { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 }, 71 | { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 }, 72 | { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 }, 73 | { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 }, 74 | { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 }, 75 | { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 }, 76 | { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 }, 77 | { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 }, 78 | { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 }, 79 | { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 }, 80 | { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 }, 81 | { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674 }, 82 | { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101 }, 83 | { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554 }, 84 | { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440 }, 85 | { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889 }, 86 | { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142 }, 87 | { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805 }, 88 | { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655 }, 89 | { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296 }, 90 | { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137 }, 91 | { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688 }, 92 | { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120 }, 93 | { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249 }, 94 | { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237 }, 95 | { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311 }, 96 | { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453 }, 97 | { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958 }, 98 | { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938 }, 99 | { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352 }, 100 | { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153 }, 101 | { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926 }, 102 | ] 103 | 104 | [package.optional-dependencies] 105 | toml = [ 106 | { name = "tomli", marker = "python_full_version <= '3.11'" }, 107 | ] 108 | 109 | [[package]] 110 | name = "exceptiongroup" 111 | version = "1.2.2" 112 | source = { registry = "https://pypi.org/simple" } 113 | sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } 114 | wheels = [ 115 | { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, 116 | ] 117 | 118 | [[package]] 119 | name = "iniconfig" 120 | version = "2.0.0" 121 | source = { registry = "https://pypi.org/simple" } 122 | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 123 | wheels = [ 124 | { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, 125 | ] 126 | 127 | [[package]] 128 | name = "packaging" 129 | version = "24.2" 130 | source = { registry = "https://pypi.org/simple" } 131 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 132 | wheels = [ 133 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 134 | ] 135 | 136 | [[package]] 137 | name = "pluggy" 138 | version = "1.5.0" 139 | source = { registry = "https://pypi.org/simple" } 140 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 141 | wheels = [ 142 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 143 | ] 144 | 145 | [[package]] 146 | name = "pydantic" 147 | version = "2.10.0" 148 | source = { registry = "https://pypi.org/simple" } 149 | dependencies = [ 150 | { name = "annotated-types" }, 151 | { name = "pydantic-core" }, 152 | { name = "typing-extensions" }, 153 | ] 154 | sdist = { url = "https://files.pythonhosted.org/packages/e9/78/58c36d0cf331b659d0ccd99175e3523c457b4f8e67cb92a8fdc22ec1667c/pydantic-2.10.0.tar.gz", hash = "sha256:0aca0f045ff6e2f097f1fe89521115335f15049eeb8a7bef3dafe4b19a74e289", size = 781980 } 155 | wheels = [ 156 | { url = "https://files.pythonhosted.org/packages/9e/ee/255cbfdbf5c47650de70ac8a5425107511f505ed0366c29d537f7f1842e1/pydantic-2.10.0-py3-none-any.whl", hash = "sha256:5e7807ba9201bdf61b1b58aa6eb690916c40a47acfb114b1b4fef3e7fd5b30fc", size = 454346 }, 157 | ] 158 | 159 | [[package]] 160 | name = "pydantic-core" 161 | version = "2.27.0" 162 | source = { registry = "https://pypi.org/simple" } 163 | dependencies = [ 164 | { name = "typing-extensions" }, 165 | ] 166 | sdist = { url = "https://files.pythonhosted.org/packages/d1/cd/8331ae216bcc5a3f2d4c6b941c9f63de647e2700d38133f4f7e0132a00c4/pydantic_core-2.27.0.tar.gz", hash = "sha256:f57783fbaf648205ac50ae7d646f27582fc706be3977e87c3c124e7a92407b10", size = 412675 } 167 | wheels = [ 168 | { url = "https://files.pythonhosted.org/packages/ff/97/8a42e9c17c305516c0d956a2887d616d3a1b0531b0053ac95a917e4a1ab7/pydantic_core-2.27.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2ac6b919f7fed71b17fe0b4603c092a4c9b5bae414817c9c81d3c22d1e1bcc", size = 1893954 }, 169 | { url = "https://files.pythonhosted.org/packages/5b/09/ff3ce866f769ebbae2abdcd742247dc2bd6967d646daf54a562ceee6abdb/pydantic_core-2.27.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e015833384ca3e1a0565a79f5d953b0629d9138021c27ad37c92a9fa1af7623c", size = 1807944 }, 170 | { url = "https://files.pythonhosted.org/packages/88/d7/e04d06ca71a0bd7f4cac24e6aa562129969c91117e5fad2520ede865c8cb/pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db72e40628967f6dc572020d04b5f800d71264e0531c6da35097e73bdf38b003", size = 1829151 }, 171 | { url = "https://files.pythonhosted.org/packages/14/24/90b0babb61b68ecc471ce5becad8f7fc5f7835c601774e5de577b051b7ad/pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df45c4073bed486ea2f18757057953afed8dd77add7276ff01bccb79982cf46c", size = 1849502 }, 172 | { url = "https://files.pythonhosted.org/packages/fc/34/62612e655b4d693a6ec515fd0ddab4bfc0cc6759076e09c23fc6966bd07b/pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:836a4bfe0cc6d36dc9a9cc1a7b391265bf6ce9d1eb1eac62ac5139f5d8d9a6fa", size = 2035489 }, 173 | { url = "https://files.pythonhosted.org/packages/12/7d/0ff62235adda41b87c495c1b95c84d4debfecb91cfd62e3100abad9754fa/pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bf1340ae507f6da6360b24179c2083857c8ca7644aab65807023cf35404ea8d", size = 2774949 }, 174 | { url = "https://files.pythonhosted.org/packages/7f/ac/e1867e2b808a668f32ad9012eaeac0b0ee377eee8157ab93720f48ee609b/pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ab325fc86fbc077284c8d7f996d904d30e97904a87d6fb303dce6b3de7ebba9", size = 2130123 }, 175 | { url = "https://files.pythonhosted.org/packages/2f/04/5006f2dbf655052826ac8d03d51b9a122de709fed76eb1040aa21772f530/pydantic_core-2.27.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1da0c98a85a6c6ed702d5556db3b09c91f9b0b78de37b7593e2de8d03238807a", size = 1981988 }, 176 | { url = "https://files.pythonhosted.org/packages/80/8b/bdbe875c4758282402e3cc75fa6bf2f0c8ffac1874f384190034786d3cbc/pydantic_core-2.27.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b0202ebf2268954090209a84f9897345719e46a57c5f2c9b7b250ca0a9d3e63", size = 1992043 }, 177 | { url = "https://files.pythonhosted.org/packages/2f/2d/4e46981cfcf4ca4c2ff7734dec08162e398dc598c6c0687454b05a82dc2f/pydantic_core-2.27.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:35380671c3c921fe8adf31ad349dc6f7588b7e928dbe44e1093789734f607399", size = 2087309 }, 178 | { url = "https://files.pythonhosted.org/packages/d2/43/56ef2e72360d909629a54198d2bc7ef60f19fde8ceb5c90d7749120d0b61/pydantic_core-2.27.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b4c19525c3538fbc0bbda6229f9682fb8199ce9ac37395880e6952798e00373", size = 2140517 }, 179 | { url = "https://files.pythonhosted.org/packages/61/40/81e5d8f84ab070cf091d072bb61b6021ff79d7110b2d0145fe3171b6107b/pydantic_core-2.27.0-cp310-none-win32.whl", hash = "sha256:333c840a1303d1474f491e7be0b718226c730a39ead0f7dab2c7e6a2f3855555", size = 1814120 }, 180 | { url = "https://files.pythonhosted.org/packages/05/64/e543d342b991d38426bcb841bc0b4b95b9bd2191367ba0cc75f258e3d583/pydantic_core-2.27.0-cp310-none-win_amd64.whl", hash = "sha256:99b2863c1365f43f74199c980a3d40f18a218fbe683dd64e470199db426c4d6a", size = 1972268 }, 181 | { url = "https://files.pythonhosted.org/packages/85/ba/5ed9583a44d9fbd6fbc028df8e3eae574a3ef4761d7f56bb4e0eb428d5ce/pydantic_core-2.27.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4523c4009c3f39d948e01962223c9f5538602e7087a628479b723c939fab262d", size = 1891468 }, 182 | { url = "https://files.pythonhosted.org/packages/50/1e/58baa0fde14aafccfcc09a8b45bdc11eb941b58a69536729d832e383bdbd/pydantic_core-2.27.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84af1cf7bfdcbc6fcf5a5f70cc9896205e0350306e4dd73d54b6a18894f79386", size = 1807103 }, 183 | { url = "https://files.pythonhosted.org/packages/7d/87/0422a653ddfcf68763eb56d6e4e2ad19df6d5e006f3f4b854fda06ce2ba3/pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e65466b31be1070b4a5b7dbfbd14b247884cb8e8b79c64fb0f36b472912dbaea", size = 1827446 }, 184 | { url = "https://files.pythonhosted.org/packages/a4/48/8e431b7732695c93ded79214299a83ac04249d748243b8ba6644ab076574/pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a5c022bb0d453192426221605efc865373dde43b17822a264671c53b068ac20c", size = 1847798 }, 185 | { url = "https://files.pythonhosted.org/packages/98/7d/e1f28e12a26035d7c8b7678830400e5b94129c9ccb74636235a2eeeee40f/pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bb69bf3b6500f195c3deb69c1205ba8fc3cb21d1915f1f158a10d6b1ef29b6a", size = 2033797 }, 186 | { url = "https://files.pythonhosted.org/packages/89/b4/ad5bc2b43b7ca8fd5f5068eca7f195565f53911d9ae69925f7f21859a929/pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aa4d1b2eba9a325897308b3124014a142cdccb9f3e016f31d3ebee6b5ea5e75", size = 2767592 }, 187 | { url = "https://files.pythonhosted.org/packages/3e/a6/7fb0725eaf1122518c018bfe38aaf4ad3d512e8598e2c08419b9a270f4bf/pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e96ca781e0c01e32115912ebdf7b3fb0780ce748b80d7d28a0802fa9fbaf44e", size = 2130244 }, 188 | { url = "https://files.pythonhosted.org/packages/a1/2c/453e52a866947a153bb575bbbb6b14db344f07a73b2ad820ff8f40e9807b/pydantic_core-2.27.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b872c86d8d71827235c7077461c502feb2db3f87d9d6d5a9daa64287d75e4fa0", size = 1979626 }, 189 | { url = "https://files.pythonhosted.org/packages/7a/43/1faa8601085dab2a37dfaca8d48605b76e38aeefcde58bf95534ab96b135/pydantic_core-2.27.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:82e1ad4ca170e8af4c928b67cff731b6296e6a0a0981b97b2eb7c275cc4e15bd", size = 1990741 }, 190 | { url = "https://files.pythonhosted.org/packages/dd/ef/21f25f5964979b7e6f9102074083b5448c22c871da438d91db09601e6634/pydantic_core-2.27.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:eb40f828bc2f73f777d1eb8fee2e86cd9692a4518b63b6b5aa8af915dfd3207b", size = 2086325 }, 191 | { url = "https://files.pythonhosted.org/packages/8a/f9/81e5f910571a20655dd7bf10e6d6db8c279e250bfbdb5ab1a09ce3e0eb82/pydantic_core-2.27.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9a8fbf506fde1529a1e3698198fe64bfbe2e0c09557bc6a7dcf872e7c01fec40", size = 2138839 }, 192 | { url = "https://files.pythonhosted.org/packages/59/c4/27917b73d0631098b91f2ec303e1becb823fead0628ee9055fca78ec1e2e/pydantic_core-2.27.0-cp311-none-win32.whl", hash = "sha256:24f984fc7762ed5f806d9e8c4c77ea69fdb2afd987b4fd319ef06c87595a8c55", size = 1809514 }, 193 | { url = "https://files.pythonhosted.org/packages/ea/48/a30c67d62b8f39095edc3dab6abe69225e8c57186f31cc59a1ab984ea8e6/pydantic_core-2.27.0-cp311-none-win_amd64.whl", hash = "sha256:68950bc08f9735306322bfc16a18391fcaac99ded2509e1cc41d03ccb6013cfe", size = 1971838 }, 194 | { url = "https://files.pythonhosted.org/packages/4e/9e/3798b901cf331058bae0ba4712a52fb0106c39f913830aaf71f01fd10d45/pydantic_core-2.27.0-cp311-none-win_arm64.whl", hash = "sha256:3eb8849445c26b41c5a474061032c53e14fe92a11a5db969f722a2716cd12206", size = 1862174 }, 195 | { url = "https://files.pythonhosted.org/packages/82/99/43149b127559f3152cd28cb7146592c6547cfe47d528761954e2e8fcabaf/pydantic_core-2.27.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8117839a9bdbba86e7f9df57018fe3b96cec934c3940b591b0fd3fbfb485864a", size = 1887064 }, 196 | { url = "https://files.pythonhosted.org/packages/7e/dd/989570c76334aa55ccb4ee8b5e0e6881a513620c6172d93b2f3b77e10f81/pydantic_core-2.27.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a291d0b4243a259c8ea7e2b84eb9ccb76370e569298875a7c5e3e71baf49057a", size = 1804405 }, 197 | { url = "https://files.pythonhosted.org/packages/3e/b5/bce1d6d6fb71d916c74bf988b7d0cd7fc0c23da5e08bc0d6d6e08c12bf36/pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e35afd9e10b2698e6f2f32256678cb23ca6c1568d02628033a837638b3ed12", size = 1822595 }, 198 | { url = "https://files.pythonhosted.org/packages/35/93/a6e5e04625ac8fcbed523d7b741e91cc3a37ed1e04e16f8f2f34269bbe53/pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58ab0d979c969983cdb97374698d847a4acffb217d543e172838864636ef10d9", size = 1848701 }, 199 | { url = "https://files.pythonhosted.org/packages/3a/74/56ead1436e3f6513b59b3a442272578a6ec09a39ab95abd5ee321bcc8c95/pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d06b667e53320332be2bf6f9461f4a9b78092a079b8ce8634c9afaa7e10cd9f", size = 2031878 }, 200 | { url = "https://files.pythonhosted.org/packages/e1/4d/8905b2710ef653c0da27224bfb6a084b5873ad6fdb975dda837943e5639d/pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78f841523729e43e3928a364ec46e2e3f80e6625a4f62aca5c345f3f626c6e8a", size = 2673386 }, 201 | { url = "https://files.pythonhosted.org/packages/1d/f0/abe1511f11756d12ce18d016f3555cb47211590e4849ee02e7adfdd1684e/pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:400bf470e4327e920883b51e255617dfe4496d4e80c3fea0b5a5d0bf2c404dd4", size = 2152867 }, 202 | { url = "https://files.pythonhosted.org/packages/c7/90/1c588d4d93ce53e1f5ab0cea2d76151fcd36613446bf99b670d7da9ddf89/pydantic_core-2.27.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:951e71da6c89d354572098bada5ba5b5dc3a9390c933af8a614e37755d3d1840", size = 1986595 }, 203 | { url = "https://files.pythonhosted.org/packages/a3/9c/27d06369f39375966836cde5c8aec0a66dc2f532c13d9aa1a6c370131fbd/pydantic_core-2.27.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a51ce96224eadd1845150b204389623c8e129fde5a67a84b972bd83a85c6c40", size = 1995731 }, 204 | { url = "https://files.pythonhosted.org/packages/26/4e/b039e52b7f4c51d9fae6715d5d2e47a57c369b8e0cb75838974a193aae40/pydantic_core-2.27.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:483c2213a609e7db2c592bbc015da58b6c75af7360ca3c981f178110d9787bcf", size = 2085771 }, 205 | { url = "https://files.pythonhosted.org/packages/01/93/2796bd116a93e7e4e10baca4c55266c4d214b3b4e5ee7f0e9add69c184af/pydantic_core-2.27.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:359e7951f04ad35111b5ddce184db3391442345d0ab073aa63a95eb8af25a5ef", size = 2150452 }, 206 | { url = "https://files.pythonhosted.org/packages/0f/93/e57562d6ea961557174c3afa481a73ce0e2d8b823e0eb2b320bfb00debbe/pydantic_core-2.27.0-cp312-none-win32.whl", hash = "sha256:ee7d9d5537daf6d5c74a83b38a638cc001b648096c1cae8ef695b0c919d9d379", size = 1830767 }, 207 | { url = "https://files.pythonhosted.org/packages/44/00/4f121ca5dd06420813e7858395b5832603ed0074a5b74ef3104c8dbc2fd5/pydantic_core-2.27.0-cp312-none-win_amd64.whl", hash = "sha256:2be0ad541bb9f059954ccf8877a49ed73877f862529575ff3d54bf4223e4dd61", size = 1973909 }, 208 | { url = "https://files.pythonhosted.org/packages/c3/c7/36f87c0dabbde9c0dd59b9024e4bf117a5122515c864ddbe685ed8301670/pydantic_core-2.27.0-cp312-none-win_arm64.whl", hash = "sha256:6e19401742ed7b69e51d8e4df3c03ad5ec65a83b36244479fd70edde2828a5d9", size = 1877037 }, 209 | { url = "https://files.pythonhosted.org/packages/9d/b2/740159bdfe532d856e340510246aa1fd723b97cadf1a38153bdfb52efa28/pydantic_core-2.27.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5f2b19b8d6fca432cb3acf48cf5243a7bf512988029b6e6fd27e9e8c0a204d85", size = 1886935 }, 210 | { url = "https://files.pythonhosted.org/packages/ca/2a/2f435d9fd591c912ca227f29c652a93775d35d54677b57c3157bbad823b5/pydantic_core-2.27.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c86679f443e7085ea55a7376462553996c688395d18ef3f0d3dbad7838f857a2", size = 1805318 }, 211 | { url = "https://files.pythonhosted.org/packages/ba/f2/755b628009530b19464bb95c60f829b47a6ef7930f8ca1d87dac90fd2848/pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:510b11e9c3b1a852876d1ccd8d5903684336d635214148637ceb27366c75a467", size = 1822284 }, 212 | { url = "https://files.pythonhosted.org/packages/3d/c2/a12744628b1b55c5384bd77657afa0780868484a92c37a189fb460d1cfe7/pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb704155e73b833801c247f39d562229c0303f54770ca14fb1c053acb376cf10", size = 1848522 }, 213 | { url = "https://files.pythonhosted.org/packages/60/1d/dfcb8ab94a4637d4cf682550a2bf94695863988e7bcbd6f4d83c04178e17/pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ce048deb1e033e7a865ca384770bccc11d44179cf09e5193a535c4c2f497bdc", size = 2031678 }, 214 | { url = "https://files.pythonhosted.org/packages/ee/c8/f9cbcab0275e031c4312223c75d999b61fba60995003cd89dc4866300059/pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58560828ee0951bb125c6f2862fbc37f039996d19ceb6d8ff1905abf7da0bf3d", size = 2672948 }, 215 | { url = "https://files.pythonhosted.org/packages/41/f9/c613546237cf58ed7a7fa9158410c14d0e7e0cbbf95f83a905c9424bb074/pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb4785894936d7682635726613c44578c420a096729f1978cd061a7e72d5275", size = 2152419 }, 216 | { url = "https://files.pythonhosted.org/packages/49/71/b951b03a271678b1d1b79481dac38cf8bce8a4e178f36ada0e9aff65a679/pydantic_core-2.27.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2883b260f7a93235488699d39cbbd94fa7b175d3a8063fbfddd3e81ad9988cb2", size = 1986408 }, 217 | { url = "https://files.pythonhosted.org/packages/9a/2c/07b0d5b5e1cdaa07b7c23e758354377d294ff0395116d39c9fa734e5d89e/pydantic_core-2.27.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c6fcb3fa3855d583aa57b94cf146f7781d5d5bc06cb95cb3afece33d31aac39b", size = 1995895 }, 218 | { url = "https://files.pythonhosted.org/packages/63/09/c21e0d7438c7e742209cc8603607c8d389df96018396c8a2577f6e24c5c5/pydantic_core-2.27.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e851a051f7260e6d688267eb039c81f05f23a19431bd7dfa4bf5e3cb34c108cd", size = 2085914 }, 219 | { url = "https://files.pythonhosted.org/packages/68/e4/5ed8f09d92655dcd0a86ee547e509adb3e396cef0a48f5c31e3b060bb9d0/pydantic_core-2.27.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edb1bfd45227dec8d50bc7c7d86463cd8728bcc574f9b07de7369880de4626a3", size = 2150217 }, 220 | { url = "https://files.pythonhosted.org/packages/cd/e6/a202f0e1b81c729130404e82d9de90dc4418ec01df35000d48d027c38501/pydantic_core-2.27.0-cp313-none-win32.whl", hash = "sha256:678f66462058dd978702db17eb6a3633d634f7aa0deaea61e0a674152766d3fc", size = 1830973 }, 221 | { url = "https://files.pythonhosted.org/packages/06/3d/21ed0f308e6618ce6c5c6bfb9e71734a9a3256d5474a53c8e5aaaba498ca/pydantic_core-2.27.0-cp313-none-win_amd64.whl", hash = "sha256:d28ca7066d6cdd347a50d8b725dc10d9a1d6a1cce09836cf071ea6a2d4908be0", size = 1974853 }, 222 | { url = "https://files.pythonhosted.org/packages/d7/18/e5744a132b81f98b9f92e15f33f03229a1d254ce7af942b1422ec2ac656f/pydantic_core-2.27.0-cp313-none-win_arm64.whl", hash = "sha256:6f4a53af9e81d757756508b57cae1cf28293f0f31b9fa2bfcb416cc7fb230f9d", size = 1877469 }, 223 | { url = "https://files.pythonhosted.org/packages/e1/79/9ff7da9e775aa9bf42c9df93fc940d421216b22d255a6edbc11aa291d3f0/pydantic_core-2.27.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:e9f9feee7f334b72ceae46313333d002b56f325b5f04271b4ae2aadd9e993ae4", size = 1897587 }, 224 | { url = "https://files.pythonhosted.org/packages/5d/62/fecc64300ea766b6b45de87663ff2adba63c6624a71ba8bc5a323e17ef5e/pydantic_core-2.27.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:225bfff5d425c34e1fd562cef52d673579d59b967d9de06178850c4802af9039", size = 1777716 }, 225 | { url = "https://files.pythonhosted.org/packages/89/96/85e7daa1151104c24f4b007d32374c899c5e66ebbbf4da4debd1794e084f/pydantic_core-2.27.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921ad596ff1a82f9c692b0758c944355abc9f0de97a4c13ca60ffc6d8dc15d4", size = 1831004 }, 226 | { url = "https://files.pythonhosted.org/packages/80/31/a9c66908c95dd2a04d84baa98b46d8ea35abb13354d0a27ac47ffab6decf/pydantic_core-2.27.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6354e18a9be37bfa124d6b288a87fb30c673745806c92956f1a25e3ae6e76b96", size = 1850721 }, 227 | { url = "https://files.pythonhosted.org/packages/48/a4/7bc31d7bc5dcbc6d7c8ab2ada38a99d2bd22e93b73e9a9a2a84626016740/pydantic_core-2.27.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ee4c2a75af9fe21269a4a0898c5425afb01af1f5d276063f57e2ae1bc64e191", size = 2037703 }, 228 | { url = "https://files.pythonhosted.org/packages/5c/d8/8f68ab9d67c129dc046ad1aa105dc3a86c9ffb6c2243d44d7140381007ea/pydantic_core-2.27.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c91e3c04f5191fd3fb68764bddeaf02025492d5d9f23343b283870f6ace69708", size = 2771401 }, 229 | { url = "https://files.pythonhosted.org/packages/8e/e1/bb637cf80583bf9058b8e5a7645cdc99a8adf3941a58329ced63f4c63843/pydantic_core-2.27.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a6ebfac28fd51890a61df36ef202adbd77d00ee5aca4a3dadb3d9ed49cfb929", size = 2133159 }, 230 | { url = "https://files.pythonhosted.org/packages/50/82/c9b7dc0b081a3f26ee321f56b67e5725ec94128d92f1e08525080ba2f2df/pydantic_core-2.27.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36aa167f69d8807ba7e341d67ea93e50fcaaf6bc433bb04939430fa3dab06f31", size = 1983746 }, 231 | { url = "https://files.pythonhosted.org/packages/65/02/6b308344a5968a1b99959fb965e72525837f609adf2412d47769902b2db5/pydantic_core-2.27.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e8d89c276234579cd3d095d5fa2a44eb10db9a218664a17b56363cddf226ff3", size = 1992306 }, 232 | { url = "https://files.pythonhosted.org/packages/f2/d6/4f9c7059020863535810a027f993bb384da1f9af60b4d6364493661befb6/pydantic_core-2.27.0-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:5cc822ab90a70ea3a91e6aed3afac570b276b1278c6909b1d384f745bd09c714", size = 2088195 }, 233 | { url = "https://files.pythonhosted.org/packages/80/1e/896a1472a6d7863144e0738181cfdad872c90b57d5c1a5ee073838d751c5/pydantic_core-2.27.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e15315691fe2253eb447503153acef4d7223dfe7e7702f9ed66539fcd0c43801", size = 2142683 }, 234 | { url = "https://files.pythonhosted.org/packages/8b/fe/773312dae0be37017e91e2684834bc971aca8f8b6f44e5395c7e4814ae52/pydantic_core-2.27.0-cp38-none-win32.whl", hash = "sha256:dfa5f5c0a4c8fced1422dc2ca7eefd872d5d13eb33cf324361dbf1dbfba0a9fe", size = 1817110 }, 235 | { url = "https://files.pythonhosted.org/packages/90/c1/219e5b3c4dd33d88dee17479b5a3aace3c9c66f26cb7317acc33d74ef02a/pydantic_core-2.27.0-cp38-none-win_amd64.whl", hash = "sha256:513cb14c0cc31a4dfd849a4674b20c46d87b364f997bbcb02282306f5e187abf", size = 1970874 }, 236 | { url = "https://files.pythonhosted.org/packages/00/e4/4d6d9193a33c964920bf56fcbe11fa30511d3d900a81c740b0157579b122/pydantic_core-2.27.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:4148dc9184ab79e356dc00a4199dc0ee8647973332cb385fc29a7cced49b9f9c", size = 1894360 }, 237 | { url = "https://files.pythonhosted.org/packages/f4/46/9d27771309609126678dee81e8e93188dbd0515a543b27e0a01a806c1893/pydantic_core-2.27.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5fc72fbfebbf42c0856a824b8b0dc2b5cd2e4a896050281a21cfa6fed8879cb1", size = 1773921 }, 238 | { url = "https://files.pythonhosted.org/packages/a0/3a/3a6a4cee7bc11bcb3f8859a63c6b4d88b8df66ad7c9c9e6d667dd894b439/pydantic_core-2.27.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:185ef205256cd8b38431205698531026979db89a79587725c1e55c59101d64e9", size = 1829480 }, 239 | { url = "https://files.pythonhosted.org/packages/2b/aa/ecf0fcee9031eef516cef2e336d403a61bd8df75ab17a856bc29f3eb07d4/pydantic_core-2.27.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:395e3e1148fa7809016231f8065f30bb0dc285a97b4dc4360cd86e17bab58af7", size = 1849759 }, 240 | { url = "https://files.pythonhosted.org/packages/b6/17/8953bbbe7d3c015bdfa34171ba1738a43682d770e68c87171dd8887035c3/pydantic_core-2.27.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33d14369739c5d07e2e7102cdb0081a1fa46ed03215e07f097b34e020b83b1ae", size = 2035679 }, 241 | { url = "https://files.pythonhosted.org/packages/ec/19/514fdf2f684003961b6f34543f0bdf3be2e0f17b8b25cd8d44c343521148/pydantic_core-2.27.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7820bb0d65e3ce1e3e70b6708c2f66143f55912fa02f4b618d0f08b61575f12", size = 2773208 }, 242 | { url = "https://files.pythonhosted.org/packages/9a/37/2cdd48b7367fbf0576d16402837212d2b1798aa4ea887f1795f8ddbace07/pydantic_core-2.27.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43b61989068de9ce62296cde02beffabcadb65672207fc51e7af76dca75e6636", size = 2130616 }, 243 | { url = "https://files.pythonhosted.org/packages/3a/6c/fa100356e1c8f749797d88401a1d5ed8d458705d43e259931681b5b96ab4/pydantic_core-2.27.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15e350efb67b855cd014c218716feea4986a149ed1f42a539edd271ee074a196", size = 1981857 }, 244 | { url = "https://files.pythonhosted.org/packages/0f/3d/36c0c832c1fd1351c495bf1495b61b2e40248c54f7874e6df439e6ffb9a5/pydantic_core-2.27.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:433689845288f9a1ee5714444e65957be26d30915f7745091ede4a83cfb2d7bb", size = 1992515 }, 245 | { url = "https://files.pythonhosted.org/packages/99/12/ee67e29369b368c404c6aead492e1528ec887609d388a7a30b675b969b82/pydantic_core-2.27.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:3fd8bc2690e7c39eecdf9071b6a889ce7b22b72073863940edc2a0a23750ca90", size = 2087604 }, 246 | { url = "https://files.pythonhosted.org/packages/0e/6c/72ca869aabe190e4cd36b03226286e430a1076c367097c77cb0704b1cbb3/pydantic_core-2.27.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:884f1806609c2c66564082540cffc96868c5571c7c3cf3a783f63f2fb49bd3cd", size = 2141000 }, 247 | { url = "https://files.pythonhosted.org/packages/5c/b8/e7499cfa6f1e46e92a645e74198b7bb9ce3d49e82f626a02726dc917fc74/pydantic_core-2.27.0-cp39-none-win32.whl", hash = "sha256:bf37b72834e7239cf84d4a0b2c050e7f9e48bced97bad9bdf98d26b8eb72e846", size = 1813857 }, 248 | { url = "https://files.pythonhosted.org/packages/2e/27/81203aa6cbf68772afd9c3877ce2e35878f434e824aad4047e7cfd3bc14d/pydantic_core-2.27.0-cp39-none-win_amd64.whl", hash = "sha256:31a2cae5f059329f9cfe3d8d266d3da1543b60b60130d186d9b6a3c20a346361", size = 1974744 }, 249 | { url = "https://files.pythonhosted.org/packages/d3/ad/c1dc814ab524cb247ceb6cb25236895a5cae996c438baf504db610fd6c92/pydantic_core-2.27.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4fb49cfdb53af5041aba909be00cccfb2c0d0a2e09281bf542371c5fd36ad04c", size = 1889233 }, 250 | { url = "https://files.pythonhosted.org/packages/24/bb/069a9dd910e6c09aab90a118c08d3cb30dc5738550e9f2d21f3b086352c2/pydantic_core-2.27.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:49633583eb7dc5cba61aaf7cdb2e9e662323ad394e543ee77af265736bcd3eaa", size = 1768419 }, 251 | { url = "https://files.pythonhosted.org/packages/cb/a1/f9b4e625ee8c7f683c8295c85d11f79a538eb53719f326646112a7800bda/pydantic_core-2.27.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:153017e3d6cd3ce979de06d84343ca424bb6092727375eba1968c8b4693c6ecb", size = 1822870 }, 252 | { url = "https://files.pythonhosted.org/packages/12/07/04abaeeabf212650de3edc300b2ab89fb17da9bc4408ef4e01a62efc87dc/pydantic_core-2.27.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff63a92f6e249514ef35bc795de10745be0226eaea06eb48b4bbeaa0c8850a4a", size = 1977039 }, 253 | { url = "https://files.pythonhosted.org/packages/0f/9d/99bbeb21d5be1d5affdc171e0e84603a757056f9f4293ef236e41af0a5bc/pydantic_core-2.27.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5982048129f40b082c2654de10c0f37c67a14f5ff9d37cf35be028ae982f26df", size = 1974317 }, 254 | { url = "https://files.pythonhosted.org/packages/5f/78/815aa74db1591a9ad4086bc1bf98e2126686245a956d76cd4e72bf9841ad/pydantic_core-2.27.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:91bc66f878557313c2a6bcf396e7befcffe5ab4354cfe4427318968af31143c3", size = 1985101 }, 255 | { url = "https://files.pythonhosted.org/packages/d9/a8/9c1557d5282108916448415e85f829b70ba99d97f03cee0e40a296e58a65/pydantic_core-2.27.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:68ef5377eb582fa4343c9d0b57a5b094046d447b4c73dd9fbd9ffb216f829e7d", size = 2073399 }, 256 | { url = "https://files.pythonhosted.org/packages/ca/b0/5296273d652fa9aa140771b3f4bb574edd3cbf397090625b988f6a57b02b/pydantic_core-2.27.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c5726eec789ee38f2c53b10b1821457b82274f81f4f746bb1e666d8741fcfadb", size = 2129499 }, 257 | { url = "https://files.pythonhosted.org/packages/e9/fd/7f39ff702fdca954f26c84b40d9bf744733bb1a50ca6b7569822b9cbb7f4/pydantic_core-2.27.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0c431e4be5c1a0c6654e0c31c661cd89e0ca956ef65305c3c3fd96f4e72ca39", size = 1997246 }, 258 | { url = "https://files.pythonhosted.org/packages/bb/4f/76f1ac16a0c277a3a8be2b5b52b0a09929630e794fb1938c4cd85396c34f/pydantic_core-2.27.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8e21d927469d04b39386255bf00d0feedead16f6253dcc85e9e10ddebc334084", size = 1889486 }, 259 | { url = "https://files.pythonhosted.org/packages/f3/96/4ff5a8ec0c457afcd87334d4e2f6fd25df6642b4ff8bf587316dd6eccd59/pydantic_core-2.27.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b51f964fcbb02949fc546022e56cdb16cda457af485e9a3e8b78ac2ecf5d77e", size = 1768718 }, 260 | { url = "https://files.pythonhosted.org/packages/52/21/e7bab7b9674d5b1a8cf06939929991753e4b814b01bae29321a8739990b3/pydantic_core-2.27.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a7fd4de38f7ff99a37e18fa0098c3140286451bc823d1746ba80cec5b433a1", size = 1823291 }, 261 | { url = "https://files.pythonhosted.org/packages/1d/68/d1868a78ce0d776c3e04179fbfa6272e72d4363c49f9bdecfe4b2007dd75/pydantic_core-2.27.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fda87808429c520a002a85d6e7cdadbf58231d60e96260976c5b8f9a12a8e13", size = 1977040 }, 262 | { url = "https://files.pythonhosted.org/packages/68/7b/2e361ff81f60c4c28f65b53670436849ec716366d4f1635ea243a31903a2/pydantic_core-2.27.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a150392102c402c538190730fda06f3bce654fc498865579a9f2c1d2b425833", size = 1973909 }, 263 | { url = "https://files.pythonhosted.org/packages/a8/44/a4a3718f3b148526baccdb9a0bc8e6b7aa840c796e637805c04aaf1a74c3/pydantic_core-2.27.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c9ed88b398ba7e3bad7bd64d66cc01dcde9cfcb7ec629a6fd78a82fa0b559d78", size = 1985091 }, 264 | { url = "https://files.pythonhosted.org/packages/3a/79/2cdf503e8aac926a99d64b2a02642ab1377146999f9a68536c54bd8b2c46/pydantic_core-2.27.0-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:9fe94d9d2a2b4edd7a4b22adcd45814b1b59b03feb00e56deb2e89747aec7bfe", size = 2073484 }, 265 | { url = "https://files.pythonhosted.org/packages/e8/15/74c61b7ea348b252fe97a32e5b531fdde331710db80e9b0fae1302023414/pydantic_core-2.27.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d8b5ee4ae9170e2775d495b81f414cc20268041c42571530513496ba61e94ba3", size = 2129473 }, 266 | { url = "https://files.pythonhosted.org/packages/57/81/0e9ebcc80b107e1dfacc677ad7c2ab0202cc0e10ba76b23afbb147ac32fb/pydantic_core-2.27.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d29e235ce13c91902ef3efc3d883a677655b3908b1cbc73dee816e5e1f8f7739", size = 1997389 }, 267 | ] 268 | 269 | [[package]] 270 | name = "pydantic-to-typescript" 271 | version = "2.0.0" 272 | source = { editable = "." } 273 | dependencies = [ 274 | { name = "pydantic" }, 275 | ] 276 | 277 | [package.optional-dependencies] 278 | dev = [ 279 | { name = "coverage" }, 280 | { name = "pytest" }, 281 | { name = "pytest-cov" }, 282 | { name = "ruff" }, 283 | ] 284 | 285 | [package.metadata] 286 | requires-dist = [ 287 | { name = "coverage", marker = "extra == 'dev'" }, 288 | { name = "pydantic" }, 289 | { name = "pytest", marker = "extra == 'dev'" }, 290 | { name = "pytest-cov", marker = "extra == 'dev'" }, 291 | { name = "ruff", marker = "extra == 'dev'" }, 292 | ] 293 | 294 | [[package]] 295 | name = "pytest" 296 | version = "8.3.3" 297 | source = { registry = "https://pypi.org/simple" } 298 | dependencies = [ 299 | { name = "colorama", marker = "sys_platform == 'win32'" }, 300 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 301 | { name = "iniconfig" }, 302 | { name = "packaging" }, 303 | { name = "pluggy" }, 304 | { name = "tomli", marker = "python_full_version < '3.11'" }, 305 | ] 306 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } 307 | wheels = [ 308 | { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, 309 | ] 310 | 311 | [[package]] 312 | name = "pytest-cov" 313 | version = "5.0.0" 314 | source = { registry = "https://pypi.org/simple" } 315 | dependencies = [ 316 | { name = "coverage", extra = ["toml"] }, 317 | { name = "pytest" }, 318 | ] 319 | sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042 } 320 | wheels = [ 321 | { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990 }, 322 | ] 323 | 324 | [[package]] 325 | name = "ruff" 326 | version = "0.7.4" 327 | source = { registry = "https://pypi.org/simple" } 328 | sdist = { url = "https://files.pythonhosted.org/packages/0b/8b/bc4e0dfa1245b07cf14300e10319b98e958a53ff074c1dd86b35253a8c2a/ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2", size = 3275547 } 329 | wheels = [ 330 | { url = "https://files.pythonhosted.org/packages/e6/4b/f5094719e254829766b807dadb766841124daba75a37da83e292ae5ad12f/ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478", size = 10447512 }, 331 | { url = "https://files.pythonhosted.org/packages/9e/1d/3d2d2c9f601cf6044799c5349ff5267467224cefed9b35edf5f1f36486e9/ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63", size = 10235436 }, 332 | { url = "https://files.pythonhosted.org/packages/62/83/42a6ec6216ded30b354b13e0e9327ef75a3c147751aaf10443756cb690e9/ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20", size = 9888936 }, 333 | { url = "https://files.pythonhosted.org/packages/4d/26/e1e54893b13046a6ad05ee9b89ee6f71542ba250f72b4c7a7d17c3dbf73d/ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109", size = 10697353 }, 334 | { url = "https://files.pythonhosted.org/packages/21/24/98d2e109c4efc02bfef144ec6ea2c3e1217e7ce0cfddda8361d268dfd499/ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452", size = 10228078 }, 335 | { url = "https://files.pythonhosted.org/packages/ad/b7/964c75be9bc2945fc3172241b371197bb6d948cc69e28bc4518448c368f3/ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea", size = 11264823 }, 336 | { url = "https://files.pythonhosted.org/packages/12/8d/20abdbf705969914ce40988fe71a554a918deaab62c38ec07483e77866f6/ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7", size = 11951855 }, 337 | { url = "https://files.pythonhosted.org/packages/b8/fc/6519ce58c57b4edafcdf40920b7273dfbba64fc6ebcaae7b88e4dc1bf0a8/ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05", size = 11516580 }, 338 | { url = "https://files.pythonhosted.org/packages/37/1a/5ec1844e993e376a86eb2456496831ed91b4398c434d8244f89094758940/ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06", size = 12692057 }, 339 | { url = "https://files.pythonhosted.org/packages/50/90/76867152b0d3c05df29a74bb028413e90f704f0f6701c4801174ba47f959/ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc", size = 11085137 }, 340 | { url = "https://files.pythonhosted.org/packages/c8/eb/0a7cb6059ac3555243bd026bb21785bbc812f7bbfa95a36c101bd72b47ae/ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172", size = 10681243 }, 341 | { url = "https://files.pythonhosted.org/packages/5e/76/2270719dbee0fd35780b05c08a07b7a726c3da9f67d9ae89ef21fc18e2e5/ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a", size = 10319187 }, 342 | { url = "https://files.pythonhosted.org/packages/9f/e5/39100f72f8ba70bec1bd329efc880dea8b6c1765ea1cb9d0c1c5f18b8d7f/ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd", size = 10803715 }, 343 | { url = "https://files.pythonhosted.org/packages/a5/89/40e904784f305fb56850063f70a998a64ebba68796d823dde67e89a24691/ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a", size = 11162912 }, 344 | { url = "https://files.pythonhosted.org/packages/8d/1b/dd77503b3875c51e3dbc053fd8367b845ab8b01c9ca6d0c237082732856c/ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac", size = 8702767 }, 345 | { url = "https://files.pythonhosted.org/packages/63/76/253ddc3e89e70165bba952ecca424b980b8d3c2598ceb4fc47904f424953/ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6", size = 9497534 }, 346 | { url = "https://files.pythonhosted.org/packages/aa/70/f8724f31abc0b329ca98b33d73c14020168babcf71b0cba3cded5d9d0e66/ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f", size = 8851590 }, 347 | ] 348 | 349 | [[package]] 350 | name = "tomli" 351 | version = "2.1.0" 352 | source = { registry = "https://pypi.org/simple" } 353 | sdist = { url = "https://files.pythonhosted.org/packages/1e/e4/1b6cbcc82d8832dd0ce34767d5c560df8a3547ad8cbc427f34601415930a/tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8", size = 16622 } 354 | wheels = [ 355 | { url = "https://files.pythonhosted.org/packages/de/f7/4da0ffe1892122c9ea096c57f64c2753ae5dd3ce85488802d11b0992cc6d/tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391", size = 13750 }, 356 | ] 357 | 358 | [[package]] 359 | name = "typing-extensions" 360 | version = "4.12.2" 361 | source = { registry = "https://pypi.org/simple" } 362 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 363 | wheels = [ 364 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 365 | ] 366 | --------------------------------------------------------------------------------