pax_global_header00006660000000000000000000000064150004735530014514gustar00rootroot0000000000000052 comment=3eae1c74e06a59a4d04147146be1bafbd53fd1c1 aioxmlrpc-0.10.0/000077500000000000000000000000001500047355300135705ustar00rootroot00000000000000aioxmlrpc-0.10.0/.editorconfig000066400000000000000000000002671500047355300162520ustar00rootroot00000000000000[*.py] indent_style = space indent_size = 4 [*.rst] indent_style = space indent_size = 3 [*.toml] indent_style = space indent_size = 2 [*.yml] indent_style = space indent_size = 2 aioxmlrpc-0.10.0/.github/000077500000000000000000000000001500047355300151305ustar00rootroot00000000000000aioxmlrpc-0.10.0/.github/workflows/000077500000000000000000000000001500047355300171655ustar00rootroot00000000000000aioxmlrpc-0.10.0/.github/workflows/build-artifacts.yml000066400000000000000000000023231500047355300227650ustar00rootroot00000000000000# Build envsub tarballs for supported python. name: "Build artifact" on: workflow_call: inputs: release-version: required: true type: string dry-run: required: true type: boolean python-version: required: true type: string pull_request: paths: # When we change pyproject.toml, we want to ensure that the maturin builds still work. - pyproject.toml # And when we change this workflow itself... - .github/workflows/build-artifacts.yml concurrency: group: sdist-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: sdist: name: Build artifact for ${{ inputs.release-version }} ${{ inputs.dry-run && '(dry-run)' || '' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} - name: Install uv uses: astral-sh/setup-uv@v3 - name: Install the project run: uv sync - name: Build tarball run: uv build - name: "Upload sdist" uses: actions/upload-artifact@v4 with: name: pypi_files path: dist/* aioxmlrpc-0.10.0/.github/workflows/publish-pypi.yml000066400000000000000000000016161500047355300223410ustar00rootroot00000000000000# Publish a release to PyPI. # name: "Publish to PyPI" on: workflow_call: inputs: release-version: required: true type: string dry-run: required: true type: boolean jobs: pypi-publish: name: Upload to PyPI ${{ inputs.release-version }} ${{ inputs.dry-run && '(dry-run)' || '' }} runs-on: ubuntu-latest if: ${{ !inputs.dry-run }} permissions: # This permission is needed for private repositories. contents: read # IMPORTANT: this permission is mandatory for trusted publishing id-token: write steps: - uses: actions/download-artifact@v4 with: pattern: pypi_files path: dist merge-multiple: true - uses: pdm-project/setup-pdm@v4 with: python-version: 3.12 - name: Publish package distributions to PyPI run: pdm publish --no-build aioxmlrpc-0.10.0/.github/workflows/release.yml000066400000000000000000000044031500047355300213310ustar00rootroot00000000000000name: Release on: push: tags: - 'v*' # Automatically trigger on version tags - 'dry-run' workflow_dispatch: inputs: tag: description: "Release Tag" required: true default: "dry-run" type: string env: PYTHON_VERSION: "3.12" jobs: plan: runs-on: ubuntu-latest outputs: release_version: ${{ steps.release-version.outputs.release_version }} dry-run: ${{ steps.release-version.outputs.dry_run }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set Release Version id: release-version run: | if [ "${{ github.event_name }}" == "push" ]; then echo "release_version=${{ github.ref_name }}" >> $GITHUB_OUTPUT if [ "${{ github.ref_name }}" == "dry-run" ]; then echo "dry_run=true" >> $GITHUB_OUTPUT else echo "dry_run=false" >> $GITHUB_OUTPUT fi else version="${{ github.event.inputs.tag || 'dry-run' }}" if [ "${version}" == "dry-run" ]; then echo "release_version=latest" >> $GITHUB_OUTPUT echo "dry_run=true" >> $GITHUB_OUTPUT else echo "release_version=${version}" >> $GITHUB_OUTPUT echo "dry_run=false" >> $GITHUB_OUTPUT fi fi - name: Display Release Version run: echo "The release version is ${{ steps.release-version.outputs.release_version }}" unit-tests: uses: ./.github/workflows/tests.yml build-artifacts: needs: - plan - unit-tests uses: ./.github/workflows/build-artifacts.yml with: release-version: ${{ needs.plan.outputs.release_version }} dry-run: ${{ needs.plan.outputs.dry-run == 'true' }} python-version: '3.12' tests-artifacts: needs: - plan - build-artifacts uses: ./.github/workflows/tests-artifacts.yml with: release-version: ${{ needs.plan.outputs.release_version }} dry-run: ${{ needs.plan.outputs.dry-run == 'true' }} publish-pypi: needs: - plan - tests-artifacts uses: ./.github/workflows/publish-pypi.yml with: release-version: ${{ needs.plan.outputs.release_version }} dry-run: ${{ needs.plan.outputs.dry-run == 'true' }} aioxmlrpc-0.10.0/.github/workflows/tests-artifacts.yml000066400000000000000000000021551500047355300230330ustar00rootroot00000000000000name: tests artifacts # Controls when the workflow will run on: # Allows you to run this workflow manually from the Actions tab workflow_call: inputs: release-version: required: true type: string description: "release number" dry-run: required: true type: boolean description: "blank run means that the release will not be pushed" jobs: test-sdist: name: test tarball archive of ${{ inputs.release-version }} ${{ inputs.dry-run && '(dry-run)' || '' }} runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - uses: actions/download-artifact@v4 with: pattern: pypi_files path: dist merge-multiple: true - name: "Install" run: | pip install dist/aioxmlrpc-*.whl --force-reinstall - name: "Test sdist" run: | python -c "from aioxmlrpc import __version__; print(__version__, end='')" aioxmlrpc-0.10.0/.github/workflows/tests.yml000066400000000000000000000030511500047355300210510ustar00rootroot00000000000000name: tests on: push: branches: [ main ] pull_request: branches: [ main ] workflow_dispatch: workflow_call: jobs: tests: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install uv uses: astral-sh/setup-uv@v3 - name: Install the project run: uv sync --group dev - name: Check types run: | uv run mypy src/aioxmlrpc/ - name: Run tests run: | uv run pytest tests --junitxml=junit/test-results-${{ matrix.python-version }}.xml --cov=aioxmlrpc --cov-report=xml --cov-report=html - name: Upload pytest test results uses: actions/upload-artifact@v4 with: name: pytest-results-${{ matrix.python-version }} path: junit/test-results-${{ matrix.python-version }}.xml - name: Upload test results to Codecov if: ${{ !cancelled() }} && matrix.python-version == '3.12' && github.event_name != 'workflow_dispatch' uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Codecov if: matrix.python-version == '3.12' && github.event_name != 'workflow_dispatch' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml aioxmlrpc-0.10.0/.gitignore000066400000000000000000000010151500047355300155550ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject venv* *.log # Sphinx documentation docs/_build/ # pycharm .idea/ aioxmlrpc-0.10.0/CHANGELOG.rst000066400000000000000000000037721500047355300156220ustar00rootroot000000000000000.10.0 - Released on 2025-04-18 -------------------------------- * Add support of aioxmlrpc.server * Add support of MultiCall * Fix httpx warning 0.9.1 - Released on 2024-12-06 ------------------------------- * Fix for httpx 0.28 (Ondra Geršl) 0.9.0 - Released on 2024-11-06 ------------------------------- * Add typing. * Breaking change: drop support of python 3.7 and python 3.8. * Switch packaging to uv / pdm. * CI update * Update LICENSE to MIT 0.8.1 - Released on 2024-04-25 ------------------------------- * Add PEP-517 metadata in python package 0.8.0 - Released on 2024-04-09 ------------------------------- * Update dependencies 0.7.0 - Released on 2023-05-08 ------------------------------ * New feature: Add missing context kwargs argument for ServerProxy compatibility. * Breaking change: Argument headers must be a kwargs like in the standard library. * Breaking change: Non standard arguments timeout and session must be kwargs too. * Update dependencies 0.6.5 - Released on 2023-04-27 ------------------------------ * Update dependencies 0.6.4 - Released on 2022-06-02 ------------------------------ * Update dependencies 0.6.3 released on 2021-12-15 ---------------------------- * Fix ProtocolError 0.6.2 released on 2021-12-13 ---------------------------- * Update httpx to ^0.21.1 * Switch CI to github action 0.6.1 released on 2021-10-06 ---------------------------- * Switch to httpx 0.5 released on 2017-09-10 -------------------------- * Remove compatibility with aiohttp < 1.0 (Ovv) 0.4 released on 2017-03-30 -------------------------- * Fix NXDOMAIN Exception handling (Vladimir Rutsky) * Fix cancel of futures handling (Gustavo Tavares Cabral) 0.3 released on 2016-06-16 -------------------------- * Fix socket closing issue 0.2 released on 2016-05-26 -------------------------- * Update compatibility for aiohttp >= 0.20 .. important:: This break the compatibility of python 3.3 0.1 released on 2014-05-17 -------------------------- * Initial version implementing ``aioxmlrpc.client`` aioxmlrpc-0.10.0/CONTRIBUTORS.rst000066400000000000000000000002241500047355300162550ustar00rootroot00000000000000Contributors ============ A. Jesse Jiryu Davis Gustavo Tavares Cabral Vladimir Rutsky nibrag sayoun Ondra Geršl Romuald Brunet Sylvain Peyrefitte aioxmlrpc-0.10.0/Justfile000066400000000000000000000026351500047355300153060ustar00rootroot00000000000000package := 'aioxmlrpc' default_unittest_suite := 'tests/unittests' default_functest_suite := 'tests/functionals' install: uv sync --group dev test: lint typecheck unittest lint: uv run ruff check . typecheck: uv run mypy src/ tests/ unittest test_suite=default_unittest_suite: uv run pytest -sxv {{test_suite}} lf: uv run pytest -sxvvv --lf cov test_suite=default_unittest_suite: rm -f .coverage rm -rf htmlcov uv run pytest --cov-report=html --cov={{package}} {{test_suite}} xdg-open htmlcov/index.html fmt: uv run ruff check --fix . uv run ruff format src tests release major_minor_patch: test && changelog #! /bin/bash # Try to bump the version first if ! uvx pdm bump {{major_minor_patch}}; then # If it fails, check if pdm-bump is installed if ! uvx pdm self list | grep -q pdm-bump; then # If not installed, add pdm-bump uvx pdm self add pdm-bump fi # Attempt to bump the version again uvx pdm bump {{major_minor_patch}} fi uv sync changelog: uv run python scripts/write_changelog.py cat CHANGELOG.rst >> CHANGELOG.rst.new rm CHANGELOG.rst mv CHANGELOG.rst.new CHANGELOG.rst $EDITOR CHANGELOG.rst publish: git commit -am "Release $(uv run scripts/get_version.py)" git tag "v$(uv run scripts/get_version.py)" git push origin "v$(uv run scripts/get_version.py)" aioxmlrpc-0.10.0/LICENSE000066400000000000000000000021101500047355300145670ustar00rootroot00000000000000MIT License Copyright (c) 2014-2024, Guillaume Gauvrit and Contibutors 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. aioxmlrpc-0.10.0/README.rst000066400000000000000000000043521500047355300152630ustar00rootroot00000000000000========= aioxmlrpc ========= .. image:: https://github.com/mardiros/aioxmlrpc/actions/workflows/tests.yml/badge.svg :target: https://github.com/mardiros/aioxmlrpc/actions/workflows/tests.yml .. image:: https://codecov.io/gh/mardiros/aioxmlrpc/branch/master/graph/badge.svg?token=BR3KttC9uJ :target: https://codecov.io/gh/mardiros/aioxmlrpc Asyncio version of the standard lib ``xmlrpc`` ``aioxmlrpc.client``, which works like ``xmlrpc.client`` but uses coroutines, has been implemented. ``aioxmlrpc.client`` is based on ``httpx`` for the transport, and just patch the necessary from the python standard library to get it working. ``aioxmlrpc.server``, which works much like ``xmlrpc.server``, but runs on the asyncio event loop and handles remote procedure calls (RPC) using both regular functions and coroutines. ``aioxmlrpc.server`` is based on ``starlette`` and ``uvicorn`` for handling HTTP. Installation ------------ aioxmlrpc is available on PyPI, it can simply be installed with your favorite tool, example with pip here. :: pip install aioxmlrpc The server dependencies is installed using the extra syntax. :: pip install "aioxmlrpc[server]" Getting Started --------------- Client ~~~~~~ This example show how to print the current version of the Gandi XML-RPC api. :: import asyncio from aioxmlrpc.client import ServerProxy async def print_gandi_api_version(): api = ServerProxy('https://rpc.gandi.net/xmlrpc/') result = await api.version.info() print(result) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(print_gandi_api_version()) loop.stop() Run the example :: uv run examples/gandi_api_version.py Server ~~~~~~ This example show an exemple of the server side. :: import asyncio from aioxmlrpc.server import SimpleXMLRPCServer class Api: def info(self): return "1.0.0" async def sleep(self): await asyncio.sleep(1) return "done" async def main(): server = SimpleXMLRPCServer(("0.0.0.0", 8080)) server.register_instance(Api(), allow_dotted_names=True) await server.serve_forever() if __name__ == "__main__": asyncio.run(main()) aioxmlrpc-0.10.0/examples/000077500000000000000000000000001500047355300154065ustar00rootroot00000000000000aioxmlrpc-0.10.0/examples/gandi_api_version.py000077500000000000000000000005161500047355300214450ustar00rootroot00000000000000#!/usr/bin/env python import asyncio from aioxmlrpc.client import ServerProxy ENDPOINT = "https://rpc.gandi.net/xmlrpc/" async def main(): client = ServerProxy(ENDPOINT, allow_none=True) for _ in range(5): data = await client.version.info() print(data) if __name__ == "__main__": asyncio.run(main()) aioxmlrpc-0.10.0/examples/server.py000066400000000000000000000006421500047355300172700ustar00rootroot00000000000000import asyncio from aioxmlrpc.server import SimpleXMLRPCServer class Api: def info(self): return "1.0.0" async def sleep(self): await asyncio.sleep(1) return "done" async def main(): server = SimpleXMLRPCServer(("0.0.0.0", 8080)) server.register_instance(Api(), allow_dotted_names=True) await server.serve_forever() if __name__ == "__main__": asyncio.run(main()) aioxmlrpc-0.10.0/pyproject.toml000066400000000000000000000042641500047355300165120ustar00rootroot00000000000000[project] name = "aioxmlrpc" description = "XML-RPC client for asyncio" classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: AsyncIO", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "License :: OSI Approved :: MIT License", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries", "Topic :: System :: Networking", "Typing :: Typed", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] version = "0.10.0" readme = "README.rst" license = { text = "MIT License" } authors = [{ name = "Guillaume Gauvrit", email = "guillaume@gauvr.it" }] requires-python = ">=3.9" dependencies = ["httpx >=0.24, <1"] [project.optional-dependencies] server = ["starlette>=0.46.2", "uvicorn>=0.34.1"] [dependency-groups] dev = [ "mypy>=1.13.0,<2", "pytest >=8.3.3,<9", "pytest-asyncio >=0.24.0", "pytest-cov >=6.0.0,<7", "starlette>=0.46.2", "uvicorn>=0.34.1", ] [tool.pdm.build] includes = ["src", "CHANGELOG.rst", "CONTRIBUTORS.rst"] excludes = ["tests"] [project.urls] Homepage = "https://github.com/mardiros/aioxmlrpc" Documentation = "https://github.com/mardiros/aioxmlrpc/blob/main/README.rst" Repository = "https://github.com/mardiros/aioxmlrpc.git" Issues = "https://github.com/mardiros/aioxmlrpc/issues" Changelog = "https://mardiros.github.io/aioxmlrpc/user/changelog.html" [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" [[tool.mypy.overrides]] disallow_any_generics = true disallow_untyped_defs = true module = "aioxmlrpc.*" [tool.pyright] ignore = ["examples"] include = ["src", "tests"] reportPrivateUsage = false reportUnknownMemberType = false reportUnknownParameterType = false reportUnknownVariableType = false reportShadowedImports = false typeCheckingMode = "strict" venvPath = ".venv" [tool.coverage.report] exclude_lines = [ "if TYPE_CHECKING:", "except ImportError:", "\\s+\\.\\.\\.$", "# coverage: ignore", ] [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" aioxmlrpc-0.10.0/scripts/000077500000000000000000000000001500047355300152575ustar00rootroot00000000000000aioxmlrpc-0.10.0/scripts/get_version.py000066400000000000000000000001611500047355300201530ustar00rootroot00000000000000import aioxmlrpc __version__ = aioxmlrpc.__version__ if __name__ == "__main__": print(__version__, end="") aioxmlrpc-0.10.0/scripts/write_changelog.py000066400000000000000000000006271500047355300207770ustar00rootroot00000000000000#!/usr/bin/env python3 import datetime from importlib.metadata import version header = ( f"{version('aioxmlrpc')} - " f"Released on {datetime.datetime.now().date().isoformat()}" ) with open("CHANGELOG.rst.new", "w") as changelog: changelog.write(header) changelog.write("\n") changelog.write("-" * len(header)) changelog.write("\n") changelog.write("* please write here \n\n") aioxmlrpc-0.10.0/src/000077500000000000000000000000001500047355300143575ustar00rootroot00000000000000aioxmlrpc-0.10.0/src/aioxmlrpc/000077500000000000000000000000001500047355300163555ustar00rootroot00000000000000aioxmlrpc-0.10.0/src/aioxmlrpc/__init__.py000066400000000000000000000001661500047355300204710ustar00rootroot00000000000000""" XML-RPC Protocol for ``asyncio`` """ from importlib import metadata __version__ = metadata.version("aioxmlrpc") aioxmlrpc-0.10.0/src/aioxmlrpc/client.py000066400000000000000000000143471500047355300202160ustar00rootroot00000000000000""" XML-RPC Client with asyncio. This module adapt the ``xmlrpc.client`` module of the standard library to work with asyncio. """ import asyncio import logging import ssl from typing import ( Any, Awaitable, Callable, Optional, Union, cast, ) from xmlrpc import client as xmlrpc import httpx __ALL__ = ["ServerProxy", "Fault", "ProtocolError", "MultiCall"] RPCResult = Any RPCParameters = Any # you don't have to import xmlrpc.client from your code Fault = xmlrpc.Fault ProtocolError = xmlrpc.ProtocolError log = logging.getLogger(__name__) class _Method: # some magic to bind an XML-RPC method to an RPC server. # supports "nested" methods (e.g. examples.getStateName) def __init__( self, send: Callable[[str, RPCParameters], Awaitable[RPCResult]], name: str ) -> None: self.__send = send self.__name = name def __getattr__(self, name: str) -> "_Method": return _Method(self.__send, "%s.%s" % (self.__name, name)) async def __call__(self, *args: RPCParameters) -> RPCResult: ret = await self.__send(self.__name, args) return ret class AioTransport(xmlrpc.Transport): """ ``xmlrpc.Transport`` subclass for asyncio support """ def __init__( self, session: httpx.AsyncClient, use_https: bool, *, use_datetime: bool = False, use_builtin_types: bool = False, auth: Optional[httpx._types.AuthTypes] = None, timeout: Optional[httpx._types.TimeoutTypes] = None, ): super().__init__(use_datetime, use_builtin_types) self.use_https = use_https self._session = session self.auth = auth or httpx.USE_CLIENT_DEFAULT self.timeout = timeout async def request( # type: ignore self, host: str, handler: str, request_body: bytes, verbose: bool = False, ) -> RPCResult: """ Send the XML-RPC request, return the response. This method is a coroutine. """ url = self._build_url(host, handler) response = None try: response = await self._session.post( url, content=request_body, auth=self.auth, timeout=self.timeout, ) body = response.text if response.status_code != 200: raise ProtocolError( url, response.status_code, body, # response.headers is a case insensitive dict from httpx, # the ProtocolError is typed as simple dict cast(dict[str, str], response.headers), ) except asyncio.CancelledError: raise except ProtocolError: raise except Exception as exc: log.error("Unexpected error", exc_info=True) if response is not None: errcode = response.status_code headers = cast(dict[str, str], response.headers) # coverage: ignore else: errcode = 0 headers = {} raise ProtocolError(url, errcode, str(exc), headers) return self.parse_response(body) def parse_response( # type: ignore self, body: str, ) -> RPCResult: """ Parse the xmlrpc response. """ p, u = self.getparser() p.feed(body) p.close() return u.close() def _build_url(self, host: str, handler: str) -> str: """ Build a url for our request based on the host, handler and use_http property """ scheme = "https" if self.use_https else "http" return f"{scheme}://{host}{handler}" class ServerProxy(xmlrpc.ServerProxy): """ ``xmlrpc.ServerProxy`` subclass for asyncio support """ def __init__( self, uri: str, encoding: Optional[str] = None, verbose: bool = False, allow_none: bool = False, use_datetime: bool = False, use_builtin_types: bool = False, auth: Optional[httpx._types.AuthTypes] = None, *, headers: Optional[dict[str, Any]] = None, context: Optional[Union[bool, ssl.SSLContext]] = None, timeout: httpx._types.TimeoutTypes = 5.0, session: Optional[httpx.AsyncClient] = None, ) -> None: if not headers: headers = { "User-Agent": "python/aioxmlrpc", "Accept": "text/xml", "Content-Type": "text/xml", } if context is None: context = True self._session = session or httpx.AsyncClient(headers=headers, verify=context) transport = AioTransport( use_https=uri.startswith("https://"), session=self._session, auth=auth, timeout=timeout, use_datetime=use_datetime, use_builtin_types=use_builtin_types, ) super().__init__( uri, transport, encoding, verbose, allow_none, use_datetime, use_builtin_types, ) async def __request( # type: ignore self, methodname: str, params: RPCParameters, ) -> RPCResult: # call a method on the remote server request = xmlrpc.dumps( params, methodname, encoding=self.__encoding, allow_none=self.__allow_none ).encode(self.__encoding) response = await self.__transport.request( # type: ignore self.__host, self.__handler, request, verbose=self.__verbose ) if len(response) == 1: # type: ignore response = response[0] return response def __getattr__(self, name: str) -> _Method: # type: ignore return _Method(self.__request, name) class MultiCall(xmlrpc.MultiCall): __server: ServerProxy async def __call__(self) -> xmlrpc.MultiCallIterator: # type: ignore marshalled_list = [] for name, args in self.__call_list: marshalled_list.append({"methodName": name, "params": args}) return xmlrpc.MultiCallIterator( await self.__server.system.multicall(marshalled_list) ) aioxmlrpc-0.10.0/src/aioxmlrpc/py.typed000066400000000000000000000000001500047355300200420ustar00rootroot00000000000000aioxmlrpc-0.10.0/src/aioxmlrpc/server.py000066400000000000000000000143231500047355300202400ustar00rootroot00000000000000""" XML-RPC Server with asyncio. This module adapt the ``xmlrpc.server`` module of the standard library to work with asyncio. Handle RPC of classic and coroutine functions """ import asyncio import inspect from types import TracebackType from typing import ( Any, Awaitable, Callable, Coroutine, Iterable, Optional, Tuple, overload, ) from xmlrpc import server from xmlrpc.client import loads, dumps, Fault import uvicorn from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import Response from starlette.routing import Route __all__ = ["SimpleXMLRPCDispatcher", "SimpleXMLRPCServer"] _Marshallable = Any class SimpleXMLRPCDispatcher(server.SimpleXMLRPCDispatcher): async def _marshaled_dispatch(self, data: str) -> bytes: # type: ignore """ Override function from SimpleXMLRPCDispatcher to handle coroutines RPC case """ try: params, method = loads(data, use_builtin_types=self.use_builtin_types) if method is None: raise ValueError("Invalid") response = await self._dispatch(method, params) # wrap response in a singleton tuple response = (response,) response = dumps( response, methodresponse=True, allow_none=self.allow_none, encoding=self.encoding, ) except Fault as fault: response = dumps(fault, allow_none=self.allow_none, encoding=self.encoding) except Exception as exc: # report exception back to server response = dumps( Fault(1, "%s:%s" % (type(exc), exc)), encoding=self.encoding, allow_none=self.allow_none, ) return response.encode(self.encoding, "xmlcharrefreplace") async def _dispatch( # type: ignore self, method: str, params: Iterable[_Marshallable] ) -> _Marshallable: # type: ignore """ Override function from SimpleXMLRPCDispatcher to handle coroutine RPC call """ func = None try: # check to see if a matching function has been registered func = self.funcs[method] except KeyError: if self.instance is not None: # check for a _dispatch method if hasattr(self.instance, "_dispatch"): resp = await self.instance._dispatch(method, params) if inspect.iscoroutine(self.instance._dispatch): return await resp else: return resp else: # call instance method directly try: func = server.resolve_dotted_attribute( self.instance, method, getattr(self, "allow_dotted_names", True), ) except AttributeError: pass if func is not None: result = func(*params) if inspect.iscoroutine(result): return await result else: return result else: raise Exception('method "%s" is not supported' % method) async def system_multicall(self, call_list: list[dict[str, _Marshallable]]): # type: ignore async def handle_call(call: dict[str, _Marshallable]) -> _Marshallable: method_name = call["methodName"] params = call["params"] try: result = await self._dispatch(method_name, params) return [result] except Fault as fault: return {"faultCode": fault.faultCode, "faultString": fault.faultString} except BaseException as exc: return {"faultCode": 1, "faultString": f"{type(exc).__name__}:{exc}"} return await asyncio.gather(*(handle_call(call) for call in call_list)) class SimpleXMLRPCServer(SimpleXMLRPCDispatcher): rpc_paths = ["/", "/RPC2", "/xmlrpc"] def __init__( self, addr: Tuple[str, int], logRequests: bool = True, allow_none: bool = False, encoding: Optional[str] = None, use_builtin_types: bool = False, ) -> None: super().__init__(allow_none, encoding, use_builtin_types) self.host, self.port = addr self.logRequests = logRequests self.app = Starlette( routes=[ Route(route, self.handle_xmlrpc, methods=["POST"]) for route in self.rpc_paths ], ) async def handle_xmlrpc(self, request: Request) -> Response: body = await request.body() response = await self._marshaled_dispatch(body.decode()) return Response(response, media_type="text/xml") def serve_forever(self) -> asyncio.Task[Any]: config = uvicorn.Config( self.app, host=self.host, port=self.port, log_level="error", loop="asyncio" ) self.server = uvicorn.Server(config) return asyncio.create_task(self.server.serve()) @overload # type: ignore def register_function( self, function: Callable[..., _Marshallable], name: Optional[str] = None ) -> Callable[..., _Marshallable]: ... @overload def register_function( self, function: Coroutine[Awaitable[_Marshallable], Any, Any], name: Optional[str] = None, ) -> Coroutine[Awaitable[_Marshallable], Any, Any]: ... def register_function( # type: ignore self, function: Any, name: Optional[str] = None, ) -> Any: super().register_function(function, name) async def __aenter__(self) -> "SimpleXMLRPCServer": return self def __enter__(self) -> "SimpleXMLRPCServer": return self def __exit__( self, exc_type: Optional[type[BaseException]], exc: Optional[BaseException], tb: Optional[TracebackType], ) -> None: ... async def __aexit__( self, exc_type: Optional[type[BaseException]], exc: Optional[BaseException], tb: Optional[TracebackType], ) -> None: ... aioxmlrpc-0.10.0/tests/000077500000000000000000000000001500047355300147325ustar00rootroot00000000000000aioxmlrpc-0.10.0/tests/functionals/000077500000000000000000000000001500047355300172575ustar00rootroot00000000000000aioxmlrpc-0.10.0/tests/functionals/conftest.py000066400000000000000000000033011500047355300214530ustar00rootroot00000000000000import asyncio from datetime import datetime import socket from math import pow import pytest from aioxmlrpc.client import ServerProxy from aioxmlrpc.server import SimpleXMLRPCServer async def wait_for_socket( host: str, port: int, timeout: int = 5, poll_time: float = 0.1 ): """Wait until the socket is open, or raise an error if the timeout is exceeded.""" for _ in range(timeout * int(1 / poll_time)): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: if sock.connect_ex((host, port)) == 0: break await asyncio.sleep(poll_time) else: raise RuntimeError(f"Server on {host}:{port} did not start in time.") async def multiply(a: int, b: int) -> int: await asyncio.sleep(0) return a * b class ExampleService: def get_data(self): return "42" class dt: @staticmethod def now(): return datetime.today() @pytest.fixture async def server(): addr = ("localhost", 8000) async with SimpleXMLRPCServer(addr) as server: server.register_function(pow) server.register_function(multiply) server.register_function(lambda x, y: x + y, "add") # type: ignore @server.register_function async def substract(x: int, y: int) -> int: return x - y server.register_instance(ExampleService(), allow_dotted_names=True) server.register_multicall_functions() task = server.serve_forever() await wait_for_socket(*addr) yield "http://localhost:8000/RPC2" server.server.should_exit = True await task @pytest.fixture async def client(server: str) -> ServerProxy: return ServerProxy(server) aioxmlrpc-0.10.0/tests/functionals/test_functionals.py000066400000000000000000000014311500047355300232140ustar00rootroot00000000000000from datetime import datetime from aioxmlrpc.client import ServerProxy, MultiCall async def test_method(client: ServerProxy): assert await client.pow(4, 2) == 16 async def test_lambda(client: ServerProxy): assert await client.add(4, 2) == 6 async def test_coroutine(client: ServerProxy): assert await client.multiply(4, 2) == 8 async def test_decorator(client: ServerProxy): assert await client.substract(16, 2) == 14 async def test_dotted(client: ServerProxy): assert await client.get_data() == "42" assert await client.dt.now() == datetime.today() async def test_multicall(client: ServerProxy): multicall = MultiCall(client) multicall.pow(4, 2) multicall.add(4, 2) resp = await multicall() assert resp[0] == 16 assert resp[1] == 6 aioxmlrpc-0.10.0/tests/unittests/000077500000000000000000000000001500047355300167745ustar00rootroot00000000000000aioxmlrpc-0.10.0/tests/unittests/__init__.py000066400000000000000000000000541500047355300211040ustar00rootroot00000000000000""" Test suite for the aioxmlrpc module """ aioxmlrpc-0.10.0/tests/unittests/test_client.py000066400000000000000000000074051500047355300216710ustar00rootroot00000000000000import ssl import pytest from httpx import Request, Response from aioxmlrpc.client import Fault, MultiCall, ProtocolError, ServerProxy RESPONSES = { "http://localhost/test_xmlrpc_ok": { "status": 200, "body": """ 1 """, }, "http://localhost/test_xmlrpc_multi_ok": { "status": 200, "body": """ 1 2 """, }, "http://localhost/test_xmlrpc_fault": { "status": 200, "body": """ faultCode 4 faultString You are not lucky """, }, "http://localhost/test_http_500": { "status": 500, "body": """ I am really broken """, }, } class DummyAsyncClient: async def post(self, url, *args, **kwargs): response = RESPONSES[url] return Response( status_code=response["status"], headers={}, text=response["body"], request=Request("POST", url), ) async def test_xmlrpc_ok(): client = ServerProxy("http://localhost/test_xmlrpc_ok", session=DummyAsyncClient()) response = await client.name.space.proxfyiedcall() assert response == 1 async def test_xmlrpc_fault(): client = ServerProxy( "http://localhost/test_xmlrpc_fault", session=DummyAsyncClient() ) with pytest.raises(Fault): await client.name.space.proxfyiedcall() async def test_http_500(): client = ServerProxy("http://localhost/test_http_500", session=DummyAsyncClient()) with pytest.raises(ProtocolError): await client.name.space.proxfyiedcall() async def test_network_error(): client = ServerProxy("http://nonexistent/nonexistent") with pytest.raises(ProtocolError): await client.name.space.proxfyiedcall() def test_context_default(): client = ServerProxy("http://nonexistent/nonexistent") ctx = client._session._transport._pool._ssl_context # type: ignore assert ctx.verify_mode is ssl.VerifyMode.CERT_REQUIRED def test_context_disable(): client = ServerProxy("http://nonexistent/nonexistent", context=False) ctx = client._session._transport._pool._ssl_context # type: ignore assert ctx.verify_mode is ssl.VerifyMode.CERT_NONE def test_context_custom(): ctx = ssl.create_default_context() client = ServerProxy("http://nonexistent/nonexistent", context=ctx) assert client._session._transport._pool._ssl_context is ctx # type: ignore async def test_multicall(): client = ServerProxy( "http://localhost/test_xmlrpc_multi_ok", session=DummyAsyncClient() ) mc = MultiCall(client) mc.name.space.proxfyiedcall() mc.name.space.proxfyiedcall() response = await mc() assert response[0] == 1 assert response[1] == 2 aioxmlrpc-0.10.0/tests/unittests/test_server.py000066400000000000000000000037511500047355300217210ustar00rootroot00000000000000import pytest from aioxmlrpc.server import SimpleXMLRPCDispatcher RPC_CALL = """ division {0} {1} """ RPC_RESPONSE = """ {0} """ RPC_FAULT = """ faultCode 1 faultString {0} """ async def test_marshall_unregister(): d = SimpleXMLRPCDispatcher() resp = await d._marshaled_dispatch(RPC_CALL.format(8, 2)) assert resp.decode() == RPC_FAULT.format( "<class 'Exception'>:method \"division\" is not supported" ) @pytest.mark.parametrize( "params,expected", [ pytest.param(RPC_CALL.format(8, 2), RPC_RESPONSE.format("4.0"), id="ok"), pytest.param( RPC_CALL.format(8, 0), RPC_FAULT.format("<class 'ZeroDivisionError'>:division by zero"), id="fault", ), ], ) async def test_marshall(params: str, expected: str): d = SimpleXMLRPCDispatcher() d.register_function(lambda x, y: x / y, "division") resp = await d._marshaled_dispatch(params) assert resp.decode() == expected async def test_multicall(): d = SimpleXMLRPCDispatcher() d.register_function(lambda x, y: x / y, "division") resp = await d.system_multicall( [ {"methodName": "division", "params": [8, 2]}, {"methodName": "division", "params": [8, 0]}, ], ) assert resp == [ [ 4.0, ], { "faultCode": 1, "faultString": "ZeroDivisionError:division by zero", }, ] aioxmlrpc-0.10.0/uv.lock000066400000000000000000001172341500047355300151040ustar00rootroot00000000000000version = 1 revision = 1 requires-python = ">=3.9" [[package]] name = "aioxmlrpc" version = "0.10.0" source = { editable = "." } dependencies = [ { name = "httpx" }, ] [package.optional-dependencies] server = [ { name = "starlette" }, { name = "uvicorn" }, ] [package.dev-dependencies] dev = [ { name = "mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "starlette" }, { name = "uvicorn" }, ] [package.metadata] requires-dist = [ { name = "httpx", specifier = ">=0.24,<1" }, { name = "starlette", marker = "extra == 'server'", specifier = ">=0.46.2" }, { name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.34.1" }, ] provides-extras = ["server"] [package.metadata.requires-dev] dev = [ { name = "mypy", specifier = ">=1.13.0,<2" }, { name = "pytest", specifier = ">=8.3.3,<9" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-cov", specifier = ">=6.0.0,<7" }, { name = "starlette", specifier = ">=0.46.2" }, { name = "uvicorn", specifier = ">=0.34.1" }, ] [[package]] name = "anyio" version = "4.6.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] name = "certifi" version = "2024.8.30" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } wheels = [ { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] [[package]] name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "coverage" version = "7.6.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 } wheels = [ { url = "https://files.pythonhosted.org/packages/a5/93/4ad92f71e28ece5c0326e5f4a6630aa4928a8846654a65cfff69b49b95b9/coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", size = 206713 }, { url = "https://files.pythonhosted.org/packages/01/ae/747a580b1eda3f2e431d87de48f0604bd7bc92e52a1a95185a4aa585bc47/coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", size = 207149 }, { url = "https://files.pythonhosted.org/packages/07/1a/1f573f8a6145f6d4c9130bbc120e0024daf1b24cf2a78d7393fa6eb6aba7/coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", size = 235584 }, { url = "https://files.pythonhosted.org/packages/40/42/c8523f2e4db34aa9389caee0d3688b6ada7a84fcc782e943a868a7f302bd/coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", size = 233486 }, { url = "https://files.pythonhosted.org/packages/8d/95/565c310fffa16ede1a042e9ea1ca3962af0d8eb5543bc72df6b91dc0c3d5/coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", size = 234649 }, { url = "https://files.pythonhosted.org/packages/d5/81/3b550674d98968ec29c92e3e8650682be6c8b1fa7581a059e7e12e74c431/coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", size = 233744 }, { url = "https://files.pythonhosted.org/packages/0d/70/d66c7f51b3e33aabc5ea9f9624c1c9d9655472962270eb5e7b0d32707224/coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", size = 232204 }, { url = "https://files.pythonhosted.org/packages/23/2d/2b3a2dbed7a5f40693404c8a09e779d7c1a5fbed089d3e7224c002129ec8/coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", size = 233335 }, { url = "https://files.pythonhosted.org/packages/5a/4f/92d1d2ad720d698a4e71c176eacf531bfb8e0721d5ad560556f2c484a513/coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", size = 209435 }, { url = "https://files.pythonhosted.org/packages/c7/b9/cdf158e7991e2287bcf9082670928badb73d310047facac203ff8dcd5ff3/coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", size = 210243 }, { url = "https://files.pythonhosted.org/packages/87/31/9c0cf84f0dfcbe4215b7eb95c31777cdc0483c13390e69584c8150c85175/coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", size = 206819 }, { url = "https://files.pythonhosted.org/packages/53/ed/a38401079ad320ad6e054a01ec2b61d270511aeb3c201c80e99c841229d5/coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", size = 207263 }, { url = "https://files.pythonhosted.org/packages/20/e7/c3ad33b179ab4213f0d70da25a9c214d52464efa11caeab438592eb1d837/coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", size = 239205 }, { url = "https://files.pythonhosted.org/packages/36/91/fc02e8d8e694f557752120487fd982f654ba1421bbaa5560debf96ddceda/coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", size = 236612 }, { url = "https://files.pythonhosted.org/packages/cc/57/cb08f0eda0389a9a8aaa4fc1f9fec7ac361c3e2d68efd5890d7042c18aa3/coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", size = 238479 }, { url = "https://files.pythonhosted.org/packages/d5/c9/2c7681a9b3ca6e6f43d489c2e6653a53278ed857fd6e7010490c307b0a47/coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", size = 237405 }, { url = "https://files.pythonhosted.org/packages/b5/4e/ebfc6944b96317df8b537ae875d2e57c27b84eb98820bc0a1055f358f056/coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", size = 236038 }, { url = "https://files.pythonhosted.org/packages/13/f2/3a0bf1841a97c0654905e2ef531170f02c89fad2555879db8fe41a097871/coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", size = 236812 }, { url = "https://files.pythonhosted.org/packages/b9/9c/66bf59226b52ce6ed9541b02d33e80a6e816a832558fbdc1111a7bd3abd4/coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", size = 209400 }, { url = "https://files.pythonhosted.org/packages/2a/a0/b0790934c04dfc8d658d4a62acb8f7ca0efdf3818456fcad757b11c6479d/coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", size = 210243 }, { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 }, { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 }, { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 }, { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 }, { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 }, { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 }, { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 }, { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 }, { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 }, { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 }, { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 }, { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 }, { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 }, { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 }, { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 }, { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 }, { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 }, { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 }, { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 }, { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 }, { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 }, { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 }, { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 }, { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 }, { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 }, { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 }, { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 }, { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 }, { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 }, { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 }, { url = "https://files.pythonhosted.org/packages/fb/27/7efede2355bd1417137246246ab0980751b3ba6065102518a2d1eba6a278/coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", size = 206714 }, { url = "https://files.pythonhosted.org/packages/f3/94/594af55226676d078af72b329372e2d036f9ba1eb6bcf1f81debea2453c7/coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", size = 207146 }, { url = "https://files.pythonhosted.org/packages/d5/13/19de1c5315b22795dd67dbd9168281632424a344b648d23d146572e42c2b/coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", size = 235180 }, { url = "https://files.pythonhosted.org/packages/db/26/8fba01ce9f376708c7efed2761cea740f50a1b4138551886213797a4cecd/coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", size = 233100 }, { url = "https://files.pythonhosted.org/packages/74/66/4db60266551b89e820b457bc3811a3c5eaad3c1324cef7730c468633387a/coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", size = 234231 }, { url = "https://files.pythonhosted.org/packages/2a/9b/7b33f0892fccce50fc82ad8da76c7af1731aea48ec71279eef63a9522db7/coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858", size = 233383 }, { url = "https://files.pythonhosted.org/packages/91/49/6ff9c4e8a67d9014e1c434566e9169965f970350f4792a0246cd0d839442/coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", size = 231863 }, { url = "https://files.pythonhosted.org/packages/81/f9/c9d330dec440676b91504fcceebca0814718fa71c8498cf29d4e21e9dbfc/coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", size = 232854 }, { url = "https://files.pythonhosted.org/packages/ee/d9/605517a023a0ba8eb1f30d958f0a7ff3a21867b07dcb42618f862695ca0e/coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", size = 209437 }, { url = "https://files.pythonhosted.org/packages/aa/79/2626903efa84e9f5b9c8ee6972de8338673fdb5bb8d8d2797740bf911027/coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", size = 210209 }, { url = "https://files.pythonhosted.org/packages/cc/56/e1d75e8981a2a92c2a777e67c26efa96c66da59d645423146eb9ff3a851b/coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", size = 198954 }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] [[package]] name = "h11" version = "0.14.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] [[package]] name = "httpcore" version = "1.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 } wheels = [ { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 }, ] [[package]] name = "httpx" version = "0.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, { name = "sniffio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } wheels = [ { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] [[package]] name = "mypy" version = "1.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } wheels = [ { url = "https://files.pythonhosted.org/packages/5e/8c/206de95a27722b5b5a8c85ba3100467bd86299d92a4f71c6b9aa448bfa2f/mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", size = 11020731 }, { url = "https://files.pythonhosted.org/packages/ab/bb/b31695a29eea76b1569fd28b4ab141a1adc9842edde080d1e8e1776862c7/mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", size = 10184276 }, { url = "https://files.pythonhosted.org/packages/a5/2d/4a23849729bb27934a0e079c9c1aad912167d875c7b070382a408d459651/mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", size = 12587706 }, { url = "https://files.pythonhosted.org/packages/5c/c3/d318e38ada50255e22e23353a469c791379825240e71b0ad03e76ca07ae6/mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", size = 13105586 }, { url = "https://files.pythonhosted.org/packages/4a/25/3918bc64952370c3dbdbd8c82c363804678127815febd2925b7273d9482c/mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", size = 9632318 }, { url = "https://files.pythonhosted.org/packages/d0/19/de0822609e5b93d02579075248c7aa6ceaddcea92f00bf4ea8e4c22e3598/mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", size = 10939027 }, { url = "https://files.pythonhosted.org/packages/c8/71/6950fcc6ca84179137e4cbf7cf41e6b68b4a339a1f5d3e954f8c34e02d66/mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", size = 10108699 }, { url = "https://files.pythonhosted.org/packages/26/50/29d3e7dd166e74dc13d46050b23f7d6d7533acf48f5217663a3719db024e/mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", size = 12506263 }, { url = "https://files.pythonhosted.org/packages/3f/1d/676e76f07f7d5ddcd4227af3938a9c9640f293b7d8a44dd4ff41d4db25c1/mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", size = 12984688 }, { url = "https://files.pythonhosted.org/packages/9c/03/5a85a30ae5407b1d28fab51bd3e2103e52ad0918d1e68f02a7778669a307/mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", size = 9626811 }, { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 }, { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 }, { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 }, { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 }, { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 }, { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 }, { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 }, { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 }, { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 }, { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 }, { url = "https://files.pythonhosted.org/packages/5f/d4/b33ddd40dad230efb317898a2d1c267c04edba73bc5086bf77edeb410fb2/mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc", size = 11013906 }, { url = "https://files.pythonhosted.org/packages/f4/e6/f414bca465b44d01cd5f4a82761e15044bedd1bf8025c5af3cc64518fac5/mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732", size = 10180657 }, { url = "https://files.pythonhosted.org/packages/38/e9/fc3865e417722f98d58409770be01afb961e2c1f99930659ff4ae7ca8b7e/mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc", size = 12586394 }, { url = "https://files.pythonhosted.org/packages/2e/35/f4d8b6d2cb0b3dad63e96caf159419dda023f45a358c6c9ac582ccaee354/mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d", size = 13103591 }, { url = "https://files.pythonhosted.org/packages/22/1d/80594aef135f921dd52e142fa0acd19df197690bd0cde42cea7b88cf5aa2/mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24", size = 9634690 }, { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, ] [[package]] name = "mypy-extensions" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] [[package]] name = "packaging" version = "24.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } wheels = [ { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] [[package]] name = "pytest" version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] [[package]] name = "pytest-asyncio" version = "0.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 } wheels = [ { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, ] [[package]] name = "pytest-cov" version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } wheels = [ { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] name = "starlette" version = "0.46.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } wheels = [ { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, ] [[package]] name = "tomli" version = "2.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/35/b9/de2a5c0144d7d75a57ff355c0c24054f965b2dc3036456ae03a51ea6264b/tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed", size = 16096 } wheels = [ { url = "https://files.pythonhosted.org/packages/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237 }, ] [[package]] name = "typing-extensions" version = "4.12.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] name = "uvicorn" version = "0.34.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/37/dd92f1f9cedb5eaf74d9999044306e06abe65344ff197864175dbbd91871/uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc", size = 76755 } wheels = [ { url = "https://files.pythonhosted.org/packages/5f/38/a5801450940a858c102a7ad9e6150146a25406a119851c993148d56ab041/uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065", size = 62404 }, ]