diff --git a/.github/workflows/pdoc.yml b/.github/workflows/pdoc.yml new file mode 100644 index 0000000..eec063d --- /dev/null +++ b/.github/workflows/pdoc.yml @@ -0,0 +1,47 @@ +name: website + +# build the documentation whenever there are new commits on main +on: + push: + branches: + - main + # Alternative: only build for tags. + # tags: + # - '*' + +# security: restrict permissions for CI jobs. +permissions: + contents: read + +jobs: + # Build the documentation and upload the static HTML files as an artifact. + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Setup uv + uses: astral-sh/setup-uv@v6 + - uses: actions/setup-python@v6 + with: + python-version: '3.14' + - run: uv run pdoc ./src/obelisk -o docs/ --math + - uses: actions/upload-pages-artifact@v4 + with: + path: docs/ + + # Deploy the artifact to GitHub pages. + # This is a separate job so that only actions/deploy-pages has the necessary permissions. + deploy: + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/sphinx-documentation.yml b/.github/workflows/sphinx-documentation.yml deleted file mode 100644 index 8650358..0000000 --- a/.github/workflows/sphinx-documentation.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Sphinx docs to gh-pages - -on: - push: - branches: - - main - -# workflow_dispatch: # Un comment line if you also want to trigger action manually - -jobs: - deploy_docs: - runs-on: ubuntu-latest - name: Sphinx documentation to GitHub pages - steps: - - uses: actions/checkout@v4 - - name: Building HTML - uses: astral-sh/setup-uv@v6 - - shell: bash -l {0} - run: | - uv run sphinx-build -M html docs/source/ docs/build/ - - name: Deploy - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/build/html diff --git a/.gitignore b/.gitignore index ac51e21..fe34974 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ share/python-wheels/ *.egg MANIFEST +.python-version + # Installer logs pip-log.txt pip-delete-this-directory.txt @@ -46,9 +48,7 @@ coverage.xml cover/ # Sphinx documentation -docs/_build/ -docs/build/ -docs/source/_autosummary/ +docs/ # PyBuilder .pybuilder/ diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d0c3cbf..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 747ffb7..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/source/_templates/autosummary/class.rst b/docs/source/_templates/autosummary/class.rst deleted file mode 100644 index d57822d..0000000 --- a/docs/source/_templates/autosummary/class.rst +++ /dev/null @@ -1,37 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. autoclass:: {{ objname }} - :show-inheritance: - - {% block methods %} - .. automethod:: __init__ - :no-index: - - {% if methods %} - .. rubric:: {{ _('Methods') }} - - .. autosummary:: - {% for item in methods %} - {% if item not in inherited_members %} - .. automethod:: {{ item }} - :no-index-entry: - {%- endif %} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Attributes') }} - - .. autosummary:: - {% for item in attributes %} - {% if item not in inherited_members %} - .. autoattribute:: {{ item }} - :no-index-entry: - {%- endif %} - {%- endfor %} - {% endif %} - {% endblock %} diff --git a/docs/source/_templates/autosummary/module.rst b/docs/source/_templates/autosummary/module.rst deleted file mode 100644 index 2b788aa..0000000 --- a/docs/source/_templates/autosummary/module.rst +++ /dev/null @@ -1,65 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} -.. automodule:: {{ fullname }} - - {% block attributes %} - {%- if attributes %} - .. rubric:: {{ _('Module Attributes') }} - - .. autosummary:: - :toctree: - {% for item in attributes %} - {{ item }} - {%- endfor %} - {% endif %} - {%- endblock %} - - {%- block functions %} - {%- if functions %} - .. rubric:: {{ _('Functions') }} - - .. autosummary:: - :toctree: - {% for item in functions %} - {{ item }} - {%- endfor %} - {% endif %} - {%- endblock %} - - {%- block classes %} - {%- if classes %} - .. rubric:: {{ _('Classes') }} - - .. autosummary:: - :toctree: - {% for item in classes %} - {{ item }} - {%- endfor %} - {% endif %} - {%- endblock %} - - {%- block exceptions %} - {%- if exceptions %} - .. rubric:: {{ _('Exceptions') }} - - .. autosummary:: - :toctree: - {% for item in exceptions %} - {{ item }} - {%- endfor %} - {% endif %} - {%- endblock %} - -{%- block modules %} -{%- if modules %} -.. rubric:: Modules - -.. autosummary:: - :toctree: - :recursive: -{% for item in modules %} - {{ item }} -{%- endfor %} -{% endif %} -{%- endblock %} diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 935ecd9..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,57 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -from datetime import datetime - -project = 'Obelisk-Py' -copyright = '2025-{}, PreDiCT - IDLab'.format( - datetime.now().year -) -author = 'Stef Pletinck' - -import re -import os -release = re.sub('^v', '', os.popen('git describe').read().strip()) - -import sys -sys.path.insert(0, os.path.abspath('../..')) - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.viewcode', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', -] -autosummary_generate = True -autosummary_ignore_module_all = False - -templates_path = ['_templates'] -exclude_patterns = [] - - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = 'alabaster' -html_static_path = ['_static'] - -autodoc_default_options = { - 'inherited-members': False, - 'undoc-members': True, -} -autoclass_content = 'class' -autodoc_member_order = 'groupwise' - -intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'pydantic': ('https://docs.pydantic.dev/latest', None), -} diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index eb599a3..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Obelisk-py documentation -=========================================== - -Obelisk-py is a fresh client for the Obelisk platform. -It brings retry strategies and optional async support - -Please find the changelog on `GitHub `__. - - -.. autosummary:: - :toctree: _autosummary - :recursive: - - obelisk diff --git a/pyproject.toml b/pyproject.toml index 4c48a7e..f3b3c1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,10 +41,10 @@ source = "vcs" [dependency-groups] dev = [ - "sphinx>=7.4.7", "pytest>=8.3.5", "pytest-asyncio>=0.25.3", "mypy>=1.18.2", + "pdoc>=16.0.0", ] [tool.hatch.build.targets.wheel] diff --git a/src/obelisk/__init__.py b/src/obelisk/__init__.py index a4b72ea..661c8a2 100644 --- a/src/obelisk/__init__.py +++ b/src/obelisk/__init__.py @@ -3,18 +3,81 @@ We support both "classic" Obelisk and HFS, each with a synchronous and async API. We also support Obelisk CORE, in async only for now. -The PyPi package name is ``obelisk-py``, the Python module is called ``obelisk``. -Your starting point will be one of the Obelisk instances in :mod:`~.sync` or :mod:`~.asynchronous` depending on your preferred API. +The PyPi package name is `obelisk-py`, the Python module is called `obelisk`. +Your starting point will be one of the Obelisk instances in `.sync` or `.asynchronous` depending on your preferred API. The Obelisk classes in these modules both implement the same interface, but the asynchronous implementation returns Coroutines. -Error handling --------------- +For Obelisk CORE, look at `obelisk.asynchronous.core`, or the below example as your starting point. + +## Error handling Obelisk-py comes with robust retry logic to handle any errors that may come up. Issues like timeouts, temporary server errors or even DNS issues are fairly common, and handling them properly is important. -Each Client accepts a retry strategy of type :class:`~.strategies.retry.RetryStrategy`. -Several predefined strategies are available in :mod:`.strategies.retry`. +Each Client accepts a retry strategy of type `.strategies.retry.RetryStrategy`. +Several predefined strategies are available in `.strategies.retry`. + +## Quick Start + +Using CORE +```py +from obelisk.asynchronous.core import Client, QueryParams +from obelisk.types.core import Filter, Comparison +import os +import asyncio + +client_id = os.getenv('CLIENT_ID') +client_secret = os.getenv('CLIENT_SECRET') + +# You may want to specify a retry strategy +client = Client( + client=client_id, + secret=client_secret +) + +query = QueryParams( + dataset="some-dataset", + fields=["metric","labels","value","timestamp"], + dataType="number", + filter_=Filter().add_and( + Comparison.equal("metric", "heart_rate::number"), + Comparison.greater("timestamp", 42069180) + ) + +data = asyncio.get_event_loop().run_until_complete(client.query(query)) +``` + +Using Classic or HFS, synchronously (async is analogous) +```py +from obelisk.sync import Client +from obelisk.types import ObeliskKind +import os + +client_id = os.getenv('CLIENT_ID') +client_secret = os.getenv('CLIENT_SECRET') + +# You may want to specify a retry strategy +client = Client( + client=client_id, + secret=client_secret, + kind=ObeliskKind.CLASSIC # or HFS, as you wish +) + +data = client.query( + datasets=["some-dataset"], + metrics=["heart-rate::number"], + from_timestamp=42069180, + filter_={ + "source": { + "_startsWith": "user123" + } + } +) +``` + +## Changelog + +See [here](https://github.com/predict-idlab/obelisk-python/blob/main/CHANGELOG.rst) """ diff --git a/src/obelisk/asynchronous/client.py b/src/obelisk/asynchronous/client.py index 99a5b7c..d8d9910 100644 --- a/src/obelisk/asynchronous/client.py +++ b/src/obelisk/asynchronous/client.py @@ -18,6 +18,9 @@ class Obelisk(BaseClient): Component that contains all the logic to consume data from the Obelisk API (e.g. historical data, sse). + For most usecases, `query` will be the method you need. + Have a look at `query_time_chunked` too, because it might just be very useful. + Obelisk API Documentation: https://obelisk.docs.apiary.io/ """ @@ -42,34 +45,34 @@ async def fetch_single_chunk( Parameters ---------- - datasets : List[str] + - datasets: List of Dataset IDs. - metrics : Optional[List[str]] = None + - metrics: List of Metric IDs or wildcards (e.g. `*::number`), defaults to all metrics. - fields : Optional[List[str]] = None + - fields: List of fields to return in the result set. Defaults to `[metric, source, value]` - from_timestamp : Optional[int] = None + - from_timestamp: Limit output to events after (and including) this UTC millisecond timestamp, if present. - to_timestamp : Optional[int] = None + - to_timestamp: Limit output to events before (and excluding) this UTC millisecond timestamp, if present. - order_by : Optional[dict] = None + - order_by: Specifies the ordering of the output, defaults to ascending by timestamp. See Obelisk docs for format. Caller is responsible for validity. - filter_ : Optional[dict] = None + - filter_: Limit output to events matching the specified Filter expression. See Obelisk docs, caller is responsible for validity. - limit : Optional[int] = None + - limit: Limit output to a maximum number of events. Also determines the page size. Default is server-determined, usually 2500. - limit_by : Optional[dict] = None + - limit_by: Limit the combination of a specific set of Index fields to a specified maximum number. - cursor : Optional[str] = None + - cursor: Specifies the next cursor, used when paging through large result sets. """ @@ -129,31 +132,31 @@ async def query( Parameters ---------- - datasets : List[str] + - datasets: List of Dataset IDs. - metrics : Optional[List[str]] = None + - metrics: List of Metric IDs or wildcards (e.g. `*::number`), defaults to all metrics. - fields : Optional[List[str]] = None + - fields: List of fields to return in the result set. Defaults to `[metric, source, value]` - from_timestamp : Optional[int] = None + - from_timestamp: Limit output to events after (and including) this UTC millisecond timestamp, if present. - to_timestamp : Optional[int] = None + - to_timestamp: Limit output to events before (and excluding) this UTC millisecond timestamp, if present. - order_by : Optional[dict] = None + - order_by: Specifies the ordering of the output, defaults to ascending by timestamp. See Obelisk docs for format. Caller is responsible for validity. - filter_ : Optional[dict] = None + - filter_: Limit output to events matching the specified Filter expression. See Obelisk docs, caller is responsible for validity. - limit : Optional[int] = None + - limit: Limit output to a maximum number of events. Also determines the page size. Default is server-determined, usually 2500. - limit_by : Optional[dict] = None + - limit_by: Limit the combination of a specific set of Index fields to a specified maximum number. """ @@ -209,19 +212,19 @@ async def query_time_chunked( Parameters ---------- - datasets : List[str] + - datasets: Dataset IDs to query from - metrics : List[str] + - metrics: IDs of metrics to query - from_time : `datetime.datetime` + - from_time: Start time to fetch from - to_time : `datetime.datetime` + - to_time: End time to fetch until. - jump : `datetime.timedelta` + - jump: Size of one yielded chunk - filter_ : Optional[dict] = None + - filter_: Obelisk filter, caller is responsible for correct format - direction : Literal['asc', 'desc'] = 'asc' + - direction: Yield older data or newer data first, defaults to older first. """ @@ -249,23 +252,23 @@ async def send( Parameters ---------- - dataset : str + - dataset: ID for the dataset to publish to - data : List[dict] + - data: List of Obelisk-acceptable datapoints. Exact format varies between Classic or HFS, caller is responsible for formatting. - precision : :class:`~obelisk.types.TimestampPrecision` = TimestampPrecision.MILLISECONDS + - precision: Precision used in the numeric timestamps contained in data. Ensure it matches to avoid weird errors. - mode : :class:`~obelisk.types.IngestMode` = IngestMode.DEFAULT - See docs for :class:`~obelisk.types.IngestMode`. + - mode: + See docs for `obelisk.types.IngestMode`. Raises ------ ObeliskError - When the resulting status code is not 204, an empty :exc:`~obelisk.exceptions.ObeliskError` is raised. + When the resulting status code is not 204, an empty `obelisk.exceptions.ObeliskError` is raised. """ params = { diff --git a/src/obelisk/asynchronous/core.py b/src/obelisk/asynchronous/core.py index 0ddd0b9..e4eff93 100644 --- a/src/obelisk/asynchronous/core.py +++ b/src/obelisk/asynchronous/core.py @@ -1,8 +1,8 @@ """ This module contains the asynchronous API to interface with Obelisk CORE. -These methods all return a :class:`Awaitable`. +These methods all return a `Awaitable`. -Relevant entrance points are :class:`Client`. +Relevant entrance points are `Client`. This API vaguely resembles that of clients to previous Obelisk versions, but also significantly diverts from it where the underlying Obelisk CORE API does so. @@ -87,7 +87,7 @@ class ObeliskPosition(BaseModel): class IncomingDatapoint(BaseModel): """A datapoint to be submitted to Obelisk. These are validated quite extensively, but not fully. - .. automethod:: check_metric_type(self) + We check roughly if the value type corresponds to the declared type if its one of `number`, `number[]`, `bool` or `string`. """ timestamp: AwareDatetime | None = None @@ -133,20 +133,29 @@ def serialize_comma_string(input: Any, handler: SerializerFunctionWrapHandler) - class QueryParams(BaseModel): + """ + To avoid having too many parameters on query functions, + and sharing the implementation between query and chunked query, + this model collects the information needed to execute a query. + + Contrary to the name, this does not correlate directly to URL query parameters sent to Obelisk. + """ dataset: str groupBy: Annotated[list[FieldName] | None, WrapSerializer(serialize_comma_string)] = None + """List of Field Names to aggregate by as defined in Obelisk docs, None selects the server-side defaults.""" aggregator: Aggregator | None = None fields: Annotated[list[FieldName] | None, WrapSerializer(serialize_comma_string)] = None - orderBy: Annotated[list[str] | None, WrapSerializer(serialize_comma_string)] = ( - None # More complex than just FieldName, can be prefixed with - to invert sort - ) + """List of Field Names as defined in Obelisk docs, None selects the server-side defaults.""" + orderBy: Annotated[list[str] | None, WrapSerializer(serialize_comma_string)] = None + """List of Field Names, with their potential prefixes and suffixes, to select ordering. None user server defaults.""" dataType: DataType | None = None + """Data type expected to be returned, is mandatory if the `value` field is requested in the `fields` parameter""" filter_: Annotated[str | Filter | None, Field(serialization_alias="filter")] = None """ - Obelisk CORE handles filtering in `RSQL format `__ , - to make it easier to also programatically write these filters, we provide the :class:`Filter` option as well. + Obelisk CORE handles filtering in [RSQL format](https://obelisk.pages.ilabt.imec.be/obelisk-core/query.html#rsql-format), + to make it easier to also programatically write these filters, we provide the `obelisk.types.core.Filter` option as well. - Suffix to avoid collisions. + Suffix to avoid collisions with builtin Python filter function. """ cursor: str | None = None limit: int = 1000 @@ -161,10 +170,16 @@ def check_datatype_needed(self) -> Self: return self def to_dict(self) -> dict[str, Any]: - return self.model_dump(exclude_none=True, by_alias=True, mode='json') + return self.model_dump(exclude_none=True, by_alias=True, mode='json', exclude={"dataset"}) class ChunkedParams(BaseModel): + """ + The parameters to be used with `Client.query_time_chunked`, + which allows fetching large spans of data in specified "chunks" specified in time units, + for example processing weeks of data one hour at a time. + This limits memory useage. + """ dataset: str groupBy: list[FieldName] | None = None aggregator: Aggregator | None = None @@ -178,6 +193,7 @@ class ChunkedParams(BaseModel): start: datetime end: datetime jump: timedelta = timedelta(hours=1) + """The size of one chunk. 1 hour is a common default. You will receive however many datapoints are included in this interval.""" model_config = ConfigDict(arbitrary_types_allowed=True) @@ -189,6 +205,7 @@ def check_datatype_needed(self) -> Self: return self def chunks(self) -> Iterator[QueryParams]: + """Splits this model into an Iterator of ordinary `QueryParams` objects, to query one timestep at a time.""" current_start = self.start while current_start < self.end: current_end = current_start + self.jump @@ -210,11 +227,28 @@ def chunks(self) -> Iterator[QueryParams]: class QueryResult(BaseModel): + """The data returned by a single chunk fetch""" cursor: str | None = None + """Cursors always point to the next page of data matched by filters. + They are none if there is no more data, they do not consider datapoint count limits.""" items: list[Datapoint] class Client(BaseClient): + """ + This class performs all communication with Obelisk. + + The intended methods to be used by consumers are `query` or `query_time_chunked`. + These will respectively return all data matching specified parameters, + or return all data, one timestep at a time respectively. + + `send` is considered an implementation detail, + but may be used by consumers for any endpoints not yet implemented by obelisk-py. + + `fetch_single_chunk` is the underlying layer to both query methods and requires the user to handle cursors themselves. + It may however still be useful in some circumstances. + """ + page_limit: int = 250 """How many datapoints to request per page in a cursored fetch""" @@ -222,7 +256,7 @@ def __init__( self, client: str, secret: str, - retry_strategy: RetryStrategy = NoRetryStrategy(), # noqa: B008 # This is fine to bew shared + retry_strategy: RetryStrategy = NoRetryStrategy(), # noqa: B008 # This is fine to be shared ) -> None: BaseClient.__init__( self, @@ -242,9 +276,9 @@ async def send( Parameters ---------- - dataset : str + - dataset ID for the dataset to publish to - data : List[IncomingDatapoint] + - data List of Obelisk-acceptable datapoints. Exact format varies between Classic or HFS, caller is responsible for formatting. @@ -252,8 +286,8 @@ async def send( Raises ------ - ObeliskError - When the resulting status code is not 204, an :exc:`~obelisk.exceptions.ObeliskError` is raised. + - ObeliskError + When the resulting status code is not 204, an `obelisk.exceptions.ObeliskError` is raised. """ response = await self.http_post( diff --git a/src/obelisk/strategies/__init__.py b/src/obelisk/strategies/__init__.py index e69de29..6b79f0c 100644 --- a/src/obelisk/strategies/__init__.py +++ b/src/obelisk/strategies/__init__.py @@ -0,0 +1,3 @@ +""" +You are probably looking for `.retry` +""" diff --git a/src/obelisk/strategies/retry.py b/src/obelisk/strategies/retry.py index b64dfd4..066c93d 100644 --- a/src/obelisk/strategies/retry.py +++ b/src/obelisk/strategies/retry.py @@ -1,5 +1,14 @@ """ Various retry strategies, from no retry to exponential backoff. + +We provide predefined strategies, as well as baseclasses for you to define your own. +Predefined are: +- `NoRetryStrategy` +- `ImmediateRetryStrategy` +- `ExponentialBackoffStrategy` + +To define your own strategy, implement your own subclass of `RetryStrategy`, +which returns fresh instances of your own `RetryEvaluator` subclass. """ from asyncio import sleep @@ -11,7 +20,7 @@ class RetryEvaluator(ABC): """ This class performs the actual retry handling. It can keep track of internal state as it so wishes, - to perform the function of :meth:`should_retry`. + to perform the function of `should_retry`. """ @abstractmethod @@ -26,7 +35,7 @@ async def should_retry(self) -> bool: class RetryStrategy(ABC): """ Base class for all retry strategies, whether predefined or user-made. - The strategy has as its sole purpose to create instances of :class:`RetryEvaluator` + The strategy has as its sole purpose to create instances of `RetryEvaluator` that can be used for a specific operation. Each individual failable operation will request a separate evaluator using the make method. @@ -81,7 +90,7 @@ class ExponentialBackoffStrategy(RetryStrategy): """ Retry strategy implementing the exponential backoff algorithm. Every failure up to `max_retries` will result in a sleep - of t ** n seconds, with t being the backoff and n amount of failures. + of $t^n$ seconds, with $t$ being the backoff and $n$ amount of failures. Note that backoff values less than one second will count as zero. """ diff --git a/src/obelisk/sync/__init__.py b/src/obelisk/sync/__init__.py index 141147c..9dd42a1 100644 --- a/src/obelisk/sync/__init__.py +++ b/src/obelisk/sync/__init__.py @@ -1,5 +1,5 @@ """ -This module provides wrappers for the classes in :mod:`obelisk.asynchronous` with a synchronous API. +This module provides wrappers for the classes in `obelisk.asynchronous` with a synchronous API. These hold on to a private event loop and block until a result is available. Note diff --git a/src/obelisk/sync/client.py b/src/obelisk/sync/client.py index f955f1c..3413efd 100644 --- a/src/obelisk/sync/client.py +++ b/src/obelisk/sync/client.py @@ -22,7 +22,7 @@ class Obelisk: Component that contains all the logic to consume data from the Obelisk API (e.g. historical data, sse). - Wraps :class:`~obelisk.asynchronous.consumer.Consumer`. + Wraps `obelisk.asynchronous.Obelisk`. Obelisk API Documentation: https://obelisk.docs.apiary.io/ @@ -63,34 +63,34 @@ def fetch_single_chunk( Parameters ---------- - datasets : List[str] + - datasets: List of Dataset IDs. - metrics : Optional[List[str]] = None + - metrics: List of Metric IDs or wildcards (e.g. ``*::number``), defaults to all metrics. - fields : Optional[List[str]] = None + - fields: List of fields to return in the result set. Defaults to `[metric, source, value]` - from_timestamp : Optional[int] = None + - from_timestamp: Limit output to events after (and including) this UTC millisecond timestamp, if present. - to_timestamp : Optional[int] = None + - to_timestamp: Limit output to events before (and excluding) this UTC millisecond timestamp, if present. - order_by : Optional[dict] = None + - order_by: Specifies the ordering of the output, defaults to ascending by timestamp. See Obelisk docs for format. Caller is responsible for validity. - filter_ : Optional[dict] = None + - filter_: Limit output to events matching the specified Filter expression. See Obelisk docs, caller is responsible for validity. - limit : Optional[int] = None + - limit: Limit output to a maximum number of events. Also determines the page size. Default is server-determined, usually 2500. - limit_by : Optional[dict] = None + - limit_by: Limit the combination of a specific set of Index fields to a specified maximum number. - cursor : Optional[str] = None + - cursor: Specifies the next cursor, used when paging through large result sets. """ @@ -132,31 +132,31 @@ def query( Parameters ---------- - datasets : List[str] + - datasets : List of Dataset IDs. - metrics : Optional[List[str]] = None + - metrics: List of Metric IDs or wildcards (e.g. `*::number`), defaults to all metrics. - fields : Optional[List[str]] = None + - fields: List of fields to return in the result set. Defaults to `[metric, source, value]` - from_timestamp : Optional[int] = None + - from_timestamp: Limit output to events after (and including) this UTC millisecond timestamp, if present. - to_timestamp : Optional[int] = None + - to_timestamp: Limit output to events before (and excluding) this UTC millisecond timestamp, if present. - order_by : Optional[dict] = None + - order_by: Specifies the ordering of the output, defaults to ascending by timestamp. See Obelisk docs for format. Caller is responsible for validity. - filter_ : Optional[dict] = None + - filter_: Limit output to events matching the specified Filter expression. See Obelisk docs, caller is responsible for validity. - limit : Optional[int] = None + - limit: Limit output to a maximum number of events. Also determines the page size. Default is server-determined, usually 2500. - limit_by : Optional[dict] = None + - limit_by: Limit the combination of a specific set of Index fields to a specified maximum number. """ @@ -195,19 +195,19 @@ def query_time_chunked( Parameters ---------- - datasets : List[str] + - datasets: Dataset IDs to query from - metrics : List[str] + - metrics: IDs of metrics to query - from_time : datetime.datetime + - from_time: Start time to fetch from - to_time : datetime.datetime + - to_time: End time to fetch until. - jump : datetime.timedelta + - jump: Size of one yielded chunk - filter_ : Optional[dict] = None + - filter_: Obelisk filter, caller is responsible for correct format - direction : Literal['asc', 'desc'] = 'asc' + - direction: Yield older data or newer data first, defaults to older first. """ @@ -235,23 +235,23 @@ def send( Parameters ---------- - dataset : str + - dataset: ID for the dataset to publish to - data : List[dict] + - data: List of Obelisk-acceptable datapoints. Exact format varies between Classic or HFS, caller is responsible for formatting. - precision : TimestampPrecision = TimestampPrecision.MILLISECONDS + - precision: Precision used in the numeric timestamps contained in data. Ensure it matches to avoid weird errors. - mode : IngestMode = IngestMode.DEFAULT - See docs for :class:`~obelisk.types.IngestMode`. + - mode: + See docs for `obelisk.types.IngestMode`. Raises ------ ObeliskError - When the resulting status code is not 204, an empty :exc:`~obelisk.exceptions.ObeliskError` is raised. + When the resulting status code is not 204, an empty `obelisk.exceptions.ObeliskError` is raised. """ task = self.loop.create_task( diff --git a/src/obelisk/types/__init__.py b/src/obelisk/types/__init__.py index 72893f4..2b54443 100644 --- a/src/obelisk/types/__init__.py +++ b/src/obelisk/types/__init__.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict class IngestMode(str, Enum): @@ -36,21 +36,29 @@ class TimestampPrecision(str, Enum): MICROSECONDS = "microseconds" -class Datapoint(BaseModel, extra="allow"): +class Datapoint(BaseModel): + """An Obelisk Classic / HFS datapoint. May contain more or less fields""" timestamp: int value: Any dataset: str | None = None metric: str | None = None source: str | None = None - userId: int | None = None # Only if HFS and no other name for field + userId: int | None = None + """This field is only used on HFS, and has a different name in some deployments.""" + + model_config = ConfigDict( + extra='allow' + ) class QueryResult(BaseModel): + """Result of a query""" items: list[Datapoint] cursor: str | None = None class ObeliskKind(str, Enum): + """Defines which variety of Obelisk a Client should use, and provides some URLs and config information.""" CLASSIC = "classic" HFS = "hfs" CORE = "core" diff --git a/src/obelisk/types/core.py b/src/obelisk/types/core.py index f5807b7..01ac2e9 100644 --- a/src/obelisk/types/core.py +++ b/src/obelisk/types/core.py @@ -1,7 +1,7 @@ """ Types specific to Obelisk CORE, including an RSQL filter implementation -To create a filter, look at :class:`Filter`. +To create a filter, look at `Filter`. Example: >>> from datetime import datetime @@ -30,11 +30,14 @@ class Constraint(ABC): # noqa: B024 # This is just a marker class """ - Constraints are simply groups of :class:`Comparison`, - such as :class:`And`, or :class:`Or`. + Constraints are simply groups of `Comparison`, + such as `And`, or `Or`. These constraints always enclose their contents in parentheses, to avoid confusing precendence situations in serialised format. + + Comparisons should not be created manually by library consumers, + instead use `Filter.add_and` or `Filter.add_or`. """ pass @@ -42,7 +45,7 @@ class Constraint(ABC): # noqa: B024 # This is just a marker class class Comparison: """ - Comparisons are the basic items of a :class:`Filter`. + Comparisons are the basic items of a `Filter`. They consist of a field name, operator, and possibly a value on the right. It is strongly suggested you create comparisons by using the staticmethods @@ -149,7 +152,7 @@ class Filter: """ Filter is an easier way to programatically create filters for the Obelisk CORE platform. - We still recommend you familiarise yourself with the CORE filter documentation, + We still recommend you familiarise yourself with the [CORE filter documentation](https://obelisk.pages.ilabt.imec.be/obelisk-core/query.html#rsql-format), as not everything is typechecked. Specifically, the left hand side of any comparison is left unchecked. Checking this would be borderline impossible with the optional arguments to some fields, diff --git a/uv.lock b/uv.lock index 8f85c60..a1e8776 100644 --- a/uv.lock +++ b/uv.lock @@ -5,15 +5,6 @@ resolution-markers = [ "python_full_version < '3.11'", ] -[[package]] -name = "alabaster" -version = "1.0.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, -] - [[package]] name = "annotated-types" version = "0.7.0" @@ -38,15 +29,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, ] -[[package]] -name = "babel" -version = "2.17.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, -] - [[package]] name = "certifi" version = "2025.1.31" @@ -56,67 +38,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, ] -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -126,15 +47,6 @@ 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 = "docutils" -version = "0.21.2" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, -] - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -190,15 +102,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] -[[package]] -name = "imagesize" -version = "1.4.1" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, -] - [[package]] name = "iniconfig" version = "2.0.0" @@ -220,6 +123,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, ] +[[package]] +name = "markdown2" +version = "2.5.4" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/42/f8/b2ae8bf5f28f9b510ae097415e6e4cb63226bb28d7ee01aec03a755ba03b/markdown2-2.5.4.tar.gz", hash = "sha256:a09873f0b3c23dbfae589b0080587df52ad75bb09a5fa6559147554736676889", size = 145652 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/06/2697b5043c3ecb720ce0d243fc7cf5024c0b5b1e450506e9b21939019963/markdown2-2.5.4-py3-none-any.whl", hash = "sha256:3c4b2934e677be7fec0e6f2de4410e116681f4ad50ec8e5ba7557be506d3f439", size = 49954 }, +] + [[package]] name = "markupsafe" version = "3.0.2" @@ -338,10 +250,9 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "mypy" }, + { name = "pdoc" }, { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" }, ] [package.metadata] @@ -354,9 +265,9 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "mypy", specifier = ">=1.18.2" }, + { name = "pdoc", specifier = ">=16.0.0" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-asyncio", specifier = ">=0.25.3" }, - { name = "sphinx", specifier = ">=7.4.7" }, ] [[package]] @@ -377,6 +288,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] +[[package]] +name = "pdoc" +version = "16.0.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown2" }, + { name = "markupsafe" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/fe/ab3f34a5fb08c6b698439a2c2643caf8fef0d61a86dd3fdcd5501c670ab8/pdoc-16.0.0.tar.gz", hash = "sha256:fdadc40cc717ec53919e3cd720390d4e3bcd40405cb51c4918c119447f913514", size = 111890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/a1/56a17b7f9e18c2bb8df73f3833345d97083b344708b97bab148fdd7e0b82/pdoc-16.0.0-py3-none-any.whl", hash = "sha256:070b51de2743b9b1a4e0ab193a06c9e6c12cf4151cf9137656eebb16e8556628", size = 100014 }, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -513,30 +439,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467 }, ] -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple/" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] -name = "roman-numerals-py" -version = "3.1.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, -] - [[package]] name = "ruff" version = "0.9.6" @@ -571,131 +473,6 @@ 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 = "snowballstemmer" -version = "2.2.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, -] - -[[package]] -name = "sphinx" -version = "8.1.3" -source = { registry = "https://pypi.org/simple/" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.11'" }, - { name = "babel", marker = "python_full_version < '3.11'" }, - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version < '3.11'" }, - { name = "imagesize", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -source = { registry = "https://pypi.org/simple/" } -resolution-markers = [ - "python_full_version >= '3.11'", -] -dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.11'" }, - { name = "babel", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.11'" }, - { name = "imagesize", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 }, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, -] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, -] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, -] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, -] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, -] - [[package]] name = "tomli" version = "2.2.1" @@ -743,12 +520,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec3 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 = "urllib3" -version = "2.3.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, -]