diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 635f9bf8..5a7f67cb 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -3,7 +3,9 @@ description: "Create a release branch, bump version, and update CHANGELOG. Usage allowed-tools: Bash, Read, Edit, Write --- -Read the current version from `pyproject.toml` (the `version` property). +Read the current version from `core/pyproject.toml` (the `version` property). Both +distributions built from this repo are released in lockstep, so there is one +version for all of the sites below and they must never diverge. The bump type is: $ARGUMENTS @@ -15,12 +17,18 @@ Compute the new version by incrementing the appropriate component of the current Then perform these steps in order: 1. Run `git checkout -b release/NEW_VERSION` -2. Replace `OLD_VERSION` with `NEW_VERSION` in all of the following files: - - `pyproject.toml` — the `version` property - - `ably/__init__.py` — lib_version value - 3.Run `uv sync` to update `uv.lock` file -4. Commit all files together with message: `chore: bump version to NEW_VERSION` -3. Fetch merged PRs since the last release tag using: +2. Replace `OLD_VERSION` with `NEW_VERSION` in all of the following places: + - `core/pyproject.toml` — the `version` property + - `server/pyproject.toml` — the `version` property, **and** every + `ably-pubsub-core==OLD_VERSION` pin in `[project.dependencies]` and + `[project.optional-dependencies]` + - `core/src/ably_pubsub/core/__init__.py` — `lib_version` value + - `server/src/ably_pubsub/server/__init__.py` — `__version__` value +3. Run `uv sync` to update the `uv.lock` file +4. Run `uv run pytest test/unit/pubsub_packaging_test.py` — it asserts that + every one of those sites agrees +5. Commit all files together with message: `chore: bump version to NEW_VERSION` +6. Fetch merged PRs since the last release tag using: ``` gh pr list --state merged --base main --json number,title,mergedAt --limit 200 ``` @@ -30,23 +38,23 @@ Then perform these steps in order: ``` Filter the PRs to only those merged after that tag date. Format each as: ``` - - Short, one sentence summary from PR title and description [#NUMBER](https://github.com/ably/ably-java/pull/NUMBER) + - Short, one sentence summary from PR title and description [#NUMBER](https://github.com/ably/ably-python/pull/NUMBER) ``` If the tag doesn't exist or there are no merged PRs, use a single `-` placeholder bullet instead. -4. In `CHANGELOG.md`, insert the following block immediately after the `# Change Log` heading (and its trailing blank line), before the first existing `## [` version entry: +7. In `CHANGELOG.md`, insert the following block immediately after the `# Change Log` heading (and its trailing blank line), before the first existing `## [` version entry: ``` -## [NEW_VERSION](https://github.com/ably/ably-java/tree/vNEW_VERSION) +## [NEW_VERSION](https://github.com/ably/ably-python/tree/vNEW_VERSION) -[Full Changelog](https://github.com/ably/ably-java/compare/vOLD_VERSION...vNEW_VERSION) +[Full Changelog](https://github.com/ably/ably-python/compare/vOLD_VERSION...vNEW_VERSION) ### What's Changed -BULLETS_FROM_STEP_3 +BULLETS_FROM_STEP_6 ``` -5. Commit `CHANGELOG.md` with message: `docs: update CHANGELOG for NEW_VERSION release` +8. Commit `CHANGELOG.md` with message: `docs: update CHANGELOG for NEW_VERSION release` After completing all steps, show the user a summary of what was done. If PRs were found, list them. If the placeholder `-` was used instead, remind them to fill in the `### What's Changed` bullet points in `CHANGELOG.md` before merging. diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 42f6972d..0d5bc7e4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -10,6 +10,7 @@ on: push: branches: - main + - integration/v4 permissions: {} @@ -46,8 +47,37 @@ jobs: key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }} - name: Install dependencies - run: uv sync --extra crypto --extra dev + # The dev dependency group installs both workspace members editable, + # with the core's crypto and vcdiff extras. + run: uv sync - name: Generate rest sync code and tests run: uv run unasync - name: Test with pytest run: uv run pytest --verbose --tb=short --capture=no + + # A namespace mistake would otherwise surface only after publish: an + # ably_pubsub/__init__.py in either wheel makes the two distributions + # fight over the same directory, and a core wheel without the generated + # sync flavour is a build that skipped unasync. + - name: Check the built distributions + run: | + uv build --package ably-pubsub-core --out-dir dist-check + uv build --package ably-pubsub-server --out-dir dist-check + CORE_WHEEL=$(ls dist-check/ably_pubsub_core-*.whl) + if ! unzip -l "$CORE_WHEEL" | grep -q "ably_pubsub/core/sync/"; then + unzip -l "$CORE_WHEEL" + echo "::error::ably_pubsub/core/sync/ not found in the core wheel" + exit 1 + fi + for WHEEL in dist-check/*.whl; do + if unzip -l "$WHEEL" | grep -q "ably_pubsub/__init__.py"; then + echo "::error::$WHEEL ships ably_pubsub/__init__.py; the namespace must stay PEP 420" + exit 1 + fi + done + CORE_TARBALL=$(ls dist-check/ably_pubsub_core-*.tar.gz) + if ! tar -tzf "$CORE_TARBALL" | grep -q "ably_pubsub/core/sync/"; then + tar -tzf "$CORE_TARBALL" + echo "::error::ably_pubsub/core/sync/ not found in the core sdist" + exit 1 + fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d1027713..568a3199 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - integration/v4 permissions: {} @@ -37,6 +38,6 @@ jobs: key: venv-${{ runner.os }}-3.9-${{ hashFiles('uv.lock') }} - name: Install dependencies - run: uv sync --extra dev + run: uv sync - name: Lint with ruff run: uv run ruff check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f47e6b0..fa387922 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,8 @@ +# TODO: this workflow still builds and publishes the single `ably` distribution +# and does not work against the two-distribution workspace. Reworking it into a +# lockstep release of ably-pubsub-core and ably-pubsub-server is the next PR on +# the integration branch (plan step 15). The artifact checks it used to carry +# have moved to check.yml, where they run on every PR. name: Publish Python distribution to PyPI on: @@ -42,29 +47,6 @@ jobs: with: name: python-package-distributions path: dist/ - - name: Check that wheel and tarball contains ably/sync/ - run: | - # Check wheel - WHEEL=$(ls dist/*.whl | head -n 1) - echo "Checking wheel: $WHEEL" - if unzip -l "$WHEEL" | grep -q "ably/sync/"; then - echo "✅ Found ably/sync/ in wheel" - else - unzip -l "$WHEEL" - echo "❌ ably/sync/ not found in wheel" - exit 1 - fi - - # Check tarball - TARBALL=$(ls dist/*.tar.gz | head -n 1) - echo "Checking tarball: $TARBALL" - if tar -tzf "$TARBALL" | grep -q "ably/sync/"; then - echo "✅ Found ably/sync/ in tarball" - else - tar -tzf "$TARBALL" - echo "❌ ably/sync/ not found in tarball" - exit 1 - fi publish-to-pypi: name: Publish Python distribution to PyPI diff --git a/.gitignore b/.gitignore index 75ec0f34..9eedd00c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.egg *.egg-info dist +dist-check build eggs parts @@ -51,9 +52,9 @@ test_vars_out pytest app_spec app_spec.pkl -ably/types/options.py.orig +core/src/ably_pubsub/core/types/options.py.orig test/ably/restsetup.py.orig .idea/**/* -ably/sync/** +core/src/ably_pubsub/core/sync/** test/ably/sync/** diff --git a/LONG_DESCRIPTION.rst b/LONG_DESCRIPTION.rst deleted file mode 100644 index 3e4a6aed..00000000 --- a/LONG_DESCRIPTION.rst +++ /dev/null @@ -1,20 +0,0 @@ -Official Ably Bindings for Python -================================== - -A Python client library for Ably Realtime messaging. - - -Setup ------ - -You can install this package by using the pip tool and installing: - - pip install ably - - -Using Ably for Python ---------------------- - -- Sign up for Ably at https://ably.com/sign-up -- Get usage examples at https://github.com/ably/ably-python -- Visit https://ably.com/docs for a complete API reference and more examples diff --git a/ably/__init__.py b/ably/__init__.py deleted file mode 100644 index e050b7c5..00000000 --- a/ably/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -import logging - -from ably.realtime.realtime import AblyRealtime -from ably.rest.auth import Auth -from ably.rest.push import Push -from ably.rest.rest import AblyRest -from ably.types.annotation import Annotation, AnnotationAction -from ably.types.capability import Capability -from ably.types.channelmode import ChannelMode -from ably.types.channeloptions import ChannelOptions -from ably.types.channelsubscription import PushChannelSubscription -from ably.types.device import DeviceDetails -from ably.types.message import MessageAction, MessageVersion -from ably.types.operations import MessageOperation, PublishResult, UpdateDeleteResult -from ably.types.options import Options, VCDiffDecoder -from ably.util.crypto import CipherParams -from ably.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException -from ably.vcdiff.defaultvcdiffdecoder import AblyVCDiffDecoder - -logger = logging.getLogger(__name__) -logger.addHandler(logging.NullHandler()) - -api_version = '5' -lib_version = '3.1.2' diff --git a/ably/http/httputils.py b/ably/http/httputils.py deleted file mode 100644 index aca46b0f..00000000 --- a/ably/http/httputils.py +++ /dev/null @@ -1,55 +0,0 @@ -import base64 -import os -import platform - -import ably - - -class HttpUtils: - default_format = "json" - - mime_types = { - "json": "application/json", - "xml": "application/xml", - "html": "text/html", - "binary": "application/x-msgpack", - } - - @staticmethod - def default_get_headers(binary=False, version=None): - headers = HttpUtils.default_headers(version=version) - if binary: - headers["Accept"] = HttpUtils.mime_types['binary'] - else: - headers["Accept"] = HttpUtils.mime_types['json'] - return headers - - @staticmethod - def default_post_headers(binary=False, version=None): - headers = HttpUtils.default_get_headers(binary=binary, version=version) - headers["Content-Type"] = headers["Accept"] - return headers - - @staticmethod - def get_host_header(host): - return { - 'Host': host, - } - - @staticmethod - def default_headers(version=None): - if version is None: - version = ably.api_version - return { - "X-Ably-Version": version, - "Ably-Agent": f'ably-python/{ably.lib_version} python/{platform.python_version()}' - } - - @staticmethod - def get_query_params(options): - params = {} - - if options.add_request_ids: - params['request_id'] = base64.urlsafe_b64encode(os.urandom(12)).decode('ascii') - - return params diff --git a/core/README.md b/core/README.md new file mode 100644 index 00000000..6c3a8697 --- /dev/null +++ b/core/README.md @@ -0,0 +1,33 @@ +# ably-pubsub-core + +**This is an internal implementation package. Do not depend on it directly.** + +`ably-pubsub-core` holds the shared implementation of Ably's Pub/Sub SDK for +Python: the HTTP and realtime clients, channels, presence, authentication, +encryption and the message types. It is published so that the packages +applications *do* install can depend on one shared implementation, pinned to an +exact version. + +Install the package that names the side your code runs on instead: + +| Where your code runs | Install | Import | +| --- | --- | --- | +| A server or other trusted environment | `ably-pubsub-server` | `ably_pubsub.server` | + +Nothing under `ably_pubsub.core` is public API. Its module layout, and the +names within it, may change in any release — including patch releases — without +a deprecation cycle. The supported surface is what `ably_pubsub.server` +re-exports. + +`ably_pubsub` is a [PEP 420](https://peps.python.org/pep-0420/) namespace +package: this distribution ships `ably_pubsub/core/**` and never +`ably_pubsub/__init__.py`, so other distributions can contribute their own +subpackages to the same namespace. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/ably/ably-python/blob/main/CONTRIBUTING.md). + +## License + +Apache License 2.0 — see [LICENSE](https://github.com/ably/ably-python/blob/main/LICENSE). diff --git a/core/pyproject.toml b/core/pyproject.toml new file mode 100644 index 00000000..b103fdb5 --- /dev/null +++ b/core/pyproject.toml @@ -0,0 +1,68 @@ +[project] +name = "ably-pubsub-core" +version = "4.0.0" +description = "INTERNAL implementation package for Ably's Pub/Sub SDKs. Not for direct use — install ably-pubsub-server instead." +readme = "README.md" +requires-python = ">=3.8" +license = { text = "Apache-2.0" } +authors = [ + { name = "Ably", email = "support@ably.com" } +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "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", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "msgpack>=1.0.0,<2.0.0", + "httpx>=0.25.0,<1.0", + "h2>=4.1.0,<5.0.0", + "websockets>=12.0,<15.0; python_version=='3.8'", + "websockets>=15.0,<16.0; python_version>='3.9'", + "pyee>=11.1.0,<14.0.0", +] + +[project.optional-dependencies] +oldcrypto = ["pycrypto>=2.6.1,<3.0.0"] +crypto = ["pycryptodome"] +vcdiff = ["vcdiff-decoder>=0.1.0,<0.2.0"] + +[project.scripts] +unasync = "ably_pubsub.core.scripts.unasync:run" + +[project.urls] +Homepage = "https://ably.com" +Repository = "https://github.com/ably/ably-python" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.sdist] +ignore-vcs = true +include = [ + "/src/ably_pubsub", + "/README.md", + "/pyproject.toml" +] +exclude = [ + "**/*.pyc", + "**/__pycache__" +] + +[tool.hatch.build.targets.wheel] +ignore-vcs = true +# PEP 420: `src/ably_pubsub` has no __init__.py, so this ships +# `ably_pubsub/core/**` into the shared `ably_pubsub` namespace. +packages = ["src/ably_pubsub"] diff --git a/core/src/ably_pubsub/core/__init__.py b/core/src/ably_pubsub/core/__init__.py new file mode 100644 index 00000000..73d161f9 --- /dev/null +++ b/core/src/ably_pubsub/core/__init__.py @@ -0,0 +1,24 @@ +import logging + +from ably_pubsub.core.realtime.realtime import AblyRealtime +from ably_pubsub.core.rest.auth import Auth +from ably_pubsub.core.rest.push import Push +from ably_pubsub.core.rest.rest import AblyRest +from ably_pubsub.core.types.annotation import Annotation, AnnotationAction +from ably_pubsub.core.types.capability import Capability +from ably_pubsub.core.types.channelmode import ChannelMode +from ably_pubsub.core.types.channeloptions import ChannelOptions +from ably_pubsub.core.types.channelsubscription import PushChannelSubscription +from ably_pubsub.core.types.device import DeviceDetails +from ably_pubsub.core.types.message import MessageAction, MessageVersion +from ably_pubsub.core.types.operations import MessageOperation, PublishResult, UpdateDeleteResult +from ably_pubsub.core.types.options import Options, VCDiffDecoder +from ably_pubsub.core.util.crypto import CipherParams +from ably_pubsub.core.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException +from ably_pubsub.core.vcdiff.defaultvcdiffdecoder import AblyVCDiffDecoder + +logger = logging.getLogger(__name__) +logger.addHandler(logging.NullHandler()) + +api_version = '5' +lib_version = '4.0.0' diff --git a/ably/http/__init__.py b/core/src/ably_pubsub/core/http/__init__.py similarity index 100% rename from ably/http/__init__.py rename to core/src/ably_pubsub/core/http/__init__.py diff --git a/ably/http/http.py b/core/src/ably_pubsub/core/http/http.py similarity index 94% rename from ably/http/http.py rename to core/src/ably_pubsub/core/http/http.py index d21a9386..6b3a8fe9 100644 --- a/ably/http/http.py +++ b/core/src/ably_pubsub/core/http/http.py @@ -7,11 +7,11 @@ import httpx import msgpack -from ably.http.httputils import HttpUtils -from ably.rest.auth import Auth -from ably.transport.defaults import Defaults -from ably.util.exceptions import AblyException -from ably.util.helper import extract_url_params, is_token_error +from ably_pubsub.core.http.httputils import HttpUtils +from ably_pubsub.core.rest.auth import Auth +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.util.exceptions import AblyException +from ably_pubsub.core.util.helper import extract_url_params, is_token_error log = logging.getLogger(__name__) @@ -165,9 +165,11 @@ async def make_request(self, method, path, version=None, headers=None, body=None body = self.dump_body(body) if body: - all_headers = HttpUtils.default_post_headers(self.options.use_binary_protocol, version=version) + all_headers = HttpUtils.default_post_headers(self.options.use_binary_protocol, version=version, + agents=self.options.agents) else: - all_headers = HttpUtils.default_get_headers(self.options.use_binary_protocol, version=version) + all_headers = HttpUtils.default_get_headers(self.options.use_binary_protocol, version=version, + agents=self.options.agents) params = HttpUtils.get_query_params(self.options) diff --git a/core/src/ably_pubsub/core/http/httputils.py b/core/src/ably_pubsub/core/http/httputils.py new file mode 100644 index 00000000..6dff862b --- /dev/null +++ b/core/src/ably_pubsub/core/http/httputils.py @@ -0,0 +1,78 @@ +import base64 +import os +import platform + +import ably_pubsub.core + +# The agent identifier for this library, sent with its version on every HTTP +# request and websocket handshake. Registered in the ably-common agents +# registry; renaming it partitions this SDK's traffic from its own history, so +# it changes only alongside a registry entry. +LIBRARY_AGENT_IDENTIFIER = 'ably-pubsub-python' + +RUNTIME_AGENT_IDENTIFIER = 'python' + + +def _render_agents(agents): + """Render an ``{identifier: version}`` mapping as Ably-Agent tokens. + + A ``None`` version renders as a bare flag, matching how the agents registry + records entries that carry no version of their own (`browser`, and the + side-declaring entries such as `ably-pubsub-server`). + """ + return [name if version is None else f'{name}/{version}' for name, version in agents.items()] + + +class HttpUtils: + default_format = "json" + + mime_types = { + "json": "application/json", + "xml": "application/xml", + "html": "text/html", + "binary": "application/x-msgpack", + } + + @staticmethod + def default_get_headers(binary=False, version=None, agents=None): + headers = HttpUtils.default_headers(version=version, agents=agents) + if binary: + headers["Accept"] = HttpUtils.mime_types['binary'] + else: + headers["Accept"] = HttpUtils.mime_types['json'] + return headers + + @staticmethod + def default_post_headers(binary=False, version=None, agents=None): + headers = HttpUtils.default_get_headers(binary=binary, version=version, agents=agents) + headers["Content-Type"] = headers["Accept"] + return headers + + @staticmethod + def get_host_header(host): + return { + 'Host': host, + } + + @staticmethod + def default_headers(version=None, agents=None): + if version is None: + version = ably_pubsub.core.api_version + tokens = [ + f'{LIBRARY_AGENT_IDENTIFIER}/{ably_pubsub.core.lib_version}', + f'{RUNTIME_AGENT_IDENTIFIER}/{platform.python_version()}', + ] + tokens.extend(_render_agents(agents or {})) + return { + "X-Ably-Version": version, + "Ably-Agent": ' '.join(tokens), + } + + @staticmethod + def get_query_params(options): + params = {} + + if options.add_request_ids: + params['request_id'] = base64.urlsafe_b64encode(os.urandom(12)).decode('ascii') + + return params diff --git a/ably/http/paginatedresult.py b/core/src/ably_pubsub/core/http/paginatedresult.py similarity index 97% rename from ably/http/paginatedresult.py rename to core/src/ably_pubsub/core/http/paginatedresult.py index a034d9d1..9b4c629f 100644 --- a/ably/http/paginatedresult.py +++ b/core/src/ably_pubsub/core/http/paginatedresult.py @@ -2,8 +2,8 @@ import logging from urllib.parse import urlencode -from ably.http.http import Request -from ably.util import case +from ably_pubsub.core.http.http import Request +from ably_pubsub.core.util import case log = logging.getLogger(__name__) diff --git a/ably/realtime/__init__.py b/core/src/ably_pubsub/core/realtime/__init__.py similarity index 100% rename from ably/realtime/__init__.py rename to core/src/ably_pubsub/core/realtime/__init__.py diff --git a/ably/realtime/annotations.py b/core/src/ably_pubsub/core/realtime/annotations.py similarity index 93% rename from ably/realtime/annotations.py rename to core/src/ably_pubsub/core/realtime/annotations.py index 57400d45..d743c56b 100644 --- a/ably/realtime/annotations.py +++ b/core/src/ably_pubsub/core/realtime/annotations.py @@ -3,17 +3,17 @@ import logging from typing import TYPE_CHECKING -from ably.rest.annotations import RestAnnotations, construct_validate_annotation -from ably.transport.websockettransport import ProtocolMessageAction -from ably.types.annotation import Annotation, AnnotationAction -from ably.types.channelmode import ChannelMode -from ably.types.channelstate import ChannelState -from ably.util.eventemitter import EventEmitter -from ably.util.helper import is_callable_or_coroutine +from ably_pubsub.core.rest.annotations import RestAnnotations, construct_validate_annotation +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.types.annotation import Annotation, AnnotationAction +from ably_pubsub.core.types.channelmode import ChannelMode +from ably_pubsub.core.types.channelstate import ChannelState +from ably_pubsub.core.util.eventemitter import EventEmitter +from ably_pubsub.core.util.helper import is_callable_or_coroutine if TYPE_CHECKING: - from ably.realtime.channel import RealtimeChannel - from ably.realtime.connectionmanager import ConnectionManager + from ably_pubsub.core.realtime.channel import RealtimeChannel + from ably_pubsub.core.realtime.connectionmanager import ConnectionManager log = logging.getLogger(__name__) diff --git a/ably/realtime/channel.py b/core/src/ably_pubsub/core/realtime/channel.py similarity index 96% rename from ably/realtime/channel.py rename to core/src/ably_pubsub/core/realtime/channel.py index d5aa1a12..39014d72 100644 --- a/ably/realtime/channel.py +++ b/core/src/ably_pubsub/core/realtime/channel.py @@ -4,27 +4,27 @@ import logging from typing import TYPE_CHECKING -from ably.realtime.annotations import RealtimeAnnotations -from ably.realtime.connection import ConnectionState -from ably.realtime.presence import RealtimePresence -from ably.rest.channel import Channel -from ably.rest.channel import Channels as RestChannels -from ably.transport.websockettransport import ProtocolMessageAction -from ably.types.annotation import Annotation -from ably.types.channelmode import ChannelMode, decode_channel_mode, encode_channel_mode -from ably.types.channeloptions import ChannelOptions -from ably.types.channelstate import ChannelState, ChannelStateChange -from ably.types.flags import Flag, has_flag -from ably.types.message import Message, MessageAction, MessageVersion -from ably.types.mixins import DecodingContext -from ably.types.operations import MessageOperation, PublishResult, UpdateDeleteResult -from ably.types.presence import PresenceMessage -from ably.util.eventemitter import EventEmitter -from ably.util.exceptions import AblyException, IncompatibleClientIdException -from ably.util.helper import Timer, is_callable_or_coroutine, validate_message_size +from ably_pubsub.core.realtime.annotations import RealtimeAnnotations +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.realtime.presence import RealtimePresence +from ably_pubsub.core.rest.channel import Channel +from ably_pubsub.core.rest.channel import Channels as RestChannels +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.types.annotation import Annotation +from ably_pubsub.core.types.channelmode import ChannelMode, decode_channel_mode, encode_channel_mode +from ably_pubsub.core.types.channeloptions import ChannelOptions +from ably_pubsub.core.types.channelstate import ChannelState, ChannelStateChange +from ably_pubsub.core.types.flags import Flag, has_flag +from ably_pubsub.core.types.message import Message, MessageAction, MessageVersion +from ably_pubsub.core.types.mixins import DecodingContext +from ably_pubsub.core.types.operations import MessageOperation, PublishResult, UpdateDeleteResult +from ably_pubsub.core.types.presence import PresenceMessage +from ably_pubsub.core.util.eventemitter import EventEmitter +from ably_pubsub.core.util.exceptions import AblyException, IncompatibleClientIdException +from ably_pubsub.core.util.helper import Timer, is_callable_or_coroutine, validate_message_size if TYPE_CHECKING: - from ably.realtime.realtime import AblyRealtime + from ably_pubsub.core.realtime.realtime import AblyRealtime log = logging.getLogger(__name__) diff --git a/ably/realtime/connection.py b/core/src/ably_pubsub/core/realtime/connection.py similarity index 90% rename from ably/realtime/connection.py rename to core/src/ably_pubsub/core/realtime/connection.py index 907f56a5..c24d8837 100644 --- a/ably/realtime/connection.py +++ b/core/src/ably_pubsub/core/realtime/connection.py @@ -5,14 +5,14 @@ import logging from typing import TYPE_CHECKING -from ably.realtime.connectionmanager import ConnectionManager -from ably.types.connectiondetails import ConnectionDetails -from ably.types.connectionstate import ConnectionEvent, ConnectionState, ConnectionStateChange -from ably.util.eventemitter import EventEmitter -from ably.util.exceptions import AblyException +from ably_pubsub.core.realtime.connectionmanager import ConnectionManager +from ably_pubsub.core.types.connectiondetails import ConnectionDetails +from ably_pubsub.core.types.connectionstate import ConnectionEvent, ConnectionState, ConnectionStateChange +from ably_pubsub.core.util.eventemitter import EventEmitter +from ably_pubsub.core.util.exceptions import AblyException if TYPE_CHECKING: - from ably.realtime.realtime import AblyRealtime + from ably_pubsub.core.realtime.realtime import AblyRealtime log = logging.getLogger(__name__) diff --git a/ably/realtime/connectionmanager.py b/core/src/ably_pubsub/core/realtime/connectionmanager.py similarity index 97% rename from ably/realtime/connectionmanager.py rename to core/src/ably_pubsub/core/realtime/connectionmanager.py index 8b51fb0f..0f049a81 100644 --- a/ably/realtime/connectionmanager.py +++ b/core/src/ably_pubsub/core/realtime/connectionmanager.py @@ -9,19 +9,19 @@ import httpx -from ably.transport.defaults import Defaults -from ably.transport.websockettransport import ProtocolMessageAction, WebSocketTransport -from ably.types.connectiondetails import ConnectionDetails -from ably.types.connectionerrors import ConnectionErrors -from ably.types.connectionstate import ConnectionEvent, ConnectionState, ConnectionStateChange -from ably.types.operations import PublishResult -from ably.types.tokendetails import TokenDetails -from ably.util.eventemitter import EventEmitter -from ably.util.exceptions import AblyException, IncompatibleClientIdException -from ably.util.helper import Timer, get_random_id, is_token_error +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction, WebSocketTransport +from ably_pubsub.core.types.connectiondetails import ConnectionDetails +from ably_pubsub.core.types.connectionerrors import ConnectionErrors +from ably_pubsub.core.types.connectionstate import ConnectionEvent, ConnectionState, ConnectionStateChange +from ably_pubsub.core.types.operations import PublishResult +from ably_pubsub.core.types.tokendetails import TokenDetails +from ably_pubsub.core.util.eventemitter import EventEmitter +from ably_pubsub.core.util.exceptions import AblyException, IncompatibleClientIdException +from ably_pubsub.core.util.helper import Timer, get_random_id, is_token_error if TYPE_CHECKING: - from ably.realtime.realtime import AblyRealtime + from ably_pubsub.core.realtime.realtime import AblyRealtime log = logging.getLogger(__name__) diff --git a/ably/realtime/presence.py b/core/src/ably_pubsub/core/realtime/presence.py similarity index 98% rename from ably/realtime/presence.py rename to core/src/ably_pubsub/core/realtime/presence.py index 79d73070..dc578e91 100644 --- a/ably/realtime/presence.py +++ b/core/src/ably_pubsub/core/realtime/presence.py @@ -12,15 +12,15 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING, Any -from ably.realtime.connection import ConnectionState -from ably.realtime.presencemap import PresenceMap -from ably.types.channelstate import ChannelState, ChannelStateChange -from ably.types.presence import PresenceAction, PresenceMessage -from ably.util.eventemitter import EventEmitter -from ably.util.exceptions import AblyException +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.realtime.presencemap import PresenceMap +from ably_pubsub.core.types.channelstate import ChannelState, ChannelStateChange +from ably_pubsub.core.types.presence import PresenceAction, PresenceMessage +from ably_pubsub.core.util.eventemitter import EventEmitter +from ably_pubsub.core.util.exceptions import AblyException if TYPE_CHECKING: - from ably.realtime.channel import RealtimeChannel + from ably_pubsub.core.realtime.channel import RealtimeChannel log = logging.getLogger(__name__) @@ -354,7 +354,7 @@ async def _send_presence(self, presence_messages: list[dict]) -> None: Args: presence_messages: List of encoded presence messages to send """ - from ably.transport.websockettransport import ProtocolMessageAction + from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction protocol_msg = { 'action': ProtocolMessageAction.PRESENCE, diff --git a/ably/realtime/presencemap.py b/core/src/ably_pubsub/core/realtime/presencemap.py similarity index 99% rename from ably/realtime/presencemap.py rename to core/src/ably_pubsub/core/realtime/presencemap.py index 9c5adace..44e02336 100644 --- a/ably/realtime/presencemap.py +++ b/core/src/ably_pubsub/core/realtime/presencemap.py @@ -7,7 +7,7 @@ import logging from typing import Callable, Dict, List, Optional, Tuple -from ably.types.presence import PresenceAction, PresenceMessage +from ably_pubsub.core.types.presence import PresenceAction, PresenceMessage logger = logging.getLogger(__name__) diff --git a/ably/realtime/realtime.py b/core/src/ably_pubsub/core/realtime/realtime.py similarity index 97% rename from ably/realtime/realtime.py rename to core/src/ably_pubsub/core/realtime/realtime.py index ab435304..270193e1 100644 --- a/ably/realtime/realtime.py +++ b/core/src/ably_pubsub/core/realtime/realtime.py @@ -2,9 +2,9 @@ import logging from typing import Optional -from ably.realtime.channel import Channels -from ably.realtime.connection import Connection, ConnectionState -from ably.rest.rest import AblyRest +from ably_pubsub.core.realtime.channel import Channels +from ably_pubsub.core.realtime.connection import Connection, ConnectionState +from ably_pubsub.core.rest.rest import AblyRest log = logging.getLogger(__name__) diff --git a/ably/rest/__init__.py b/core/src/ably_pubsub/core/rest/__init__.py similarity index 100% rename from ably/rest/__init__.py rename to core/src/ably_pubsub/core/rest/__init__.py diff --git a/ably/rest/annotations.py b/core/src/ably_pubsub/core/rest/annotations.py similarity index 96% rename from ably/rest/annotations.py rename to core/src/ably_pubsub/core/rest/annotations.py index 588ffcf3..a049009f 100644 --- a/ably/rest/annotations.py +++ b/core/src/ably_pubsub/core/rest/annotations.py @@ -8,15 +8,15 @@ import msgpack -from ably.http.paginatedresult import PaginatedResult, format_params -from ably.types.annotation import ( +from ably_pubsub.core.http.paginatedresult import PaginatedResult, format_params +from ably_pubsub.core.types.annotation import ( Annotation, AnnotationAction, make_annotation_response_handler, ) -from ably.types.message import Message -from ably.types.options import Options -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.types.options import Options +from ably_pubsub.core.util.exceptions import AblyException log = logging.getLogger(__name__) diff --git a/ably/rest/auth.py b/core/src/ably_pubsub/core/rest/auth.py similarity index 96% rename from ably/rest/auth.py rename to core/src/ably_pubsub/core/rest/auth.py index d2057533..e9fc0417 100644 --- a/ably/rest/auth.py +++ b/core/src/ably_pubsub/core/rest/auth.py @@ -9,17 +9,17 @@ import httpx -from ably.types.options import Options +from ably_pubsub.core.types.options import Options if TYPE_CHECKING: - from ably.realtime.realtime import AblyRealtime - from ably.rest.rest import AblyRest + from ably_pubsub.core.realtime.realtime import AblyRealtime + from ably_pubsub.core.rest.rest import AblyRest -from ably.types.capability import Capability -from ably.types.tokendetails import TokenDetails -from ably.types.tokenrequest import TokenRequest -from ably.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException -from ably.util.helper import extract_url_params +from ably_pubsub.core.types.capability import Capability +from ably_pubsub.core.types.tokendetails import TokenDetails +from ably_pubsub.core.types.tokenrequest import TokenRequest +from ably_pubsub.core.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException +from ably_pubsub.core.util.helper import extract_url_params __all__ = ["Auth"] @@ -411,7 +411,7 @@ async def token_request_from_auth_url(self, method: str, url: str, token_params, # Use clean URL for the request url = clean_url - from ably.http.http import Response + from ably_pubsub.core.http.http import Response async with httpx.AsyncClient(http2=True) as client: resp = await client.request(method=method, url=url, headers=headers, params=params, data=body) response = Response(resp) diff --git a/ably/rest/channel.py b/core/src/ably_pubsub/core/rest/channel.py similarity index 96% rename from ably/rest/channel.py rename to core/src/ably_pubsub/core/rest/channel.py index 32cc7e7e..6517efe1 100644 --- a/ably/rest/channel.py +++ b/core/src/ably_pubsub/core/rest/channel.py @@ -8,20 +8,20 @@ import msgpack -from ably.http.paginatedresult import PaginatedResult, format_params -from ably.rest.annotations import RestAnnotations -from ably.types.channeldetails import ChannelDetails -from ably.types.message import ( +from ably_pubsub.core.http.paginatedresult import PaginatedResult, format_params +from ably_pubsub.core.rest.annotations import RestAnnotations +from ably_pubsub.core.types.channeldetails import ChannelDetails +from ably_pubsub.core.types.message import ( Message, MessageAction, MessageVersion, make_message_response_handler, make_single_message_response_handler, ) -from ably.types.operations import MessageOperation, PublishResult, UpdateDeleteResult -from ably.types.presence import Presence -from ably.util.crypto import get_cipher -from ably.util.exceptions import ( +from ably_pubsub.core.types.operations import MessageOperation, PublishResult, UpdateDeleteResult +from ably_pubsub.core.types.presence import Presence +from ably_pubsub.core.util.crypto import get_cipher +from ably_pubsub.core.util.exceptions import ( AblyException, IncompatibleClientIdException, catch_all, diff --git a/ably/rest/push.py b/core/src/ably_pubsub/core/rest/push.py similarity index 96% rename from ably/rest/push.py rename to core/src/ably_pubsub/core/rest/push.py index f99b2b1d..e8c5b051 100644 --- a/ably/rest/push.py +++ b/core/src/ably_pubsub/core/rest/push.py @@ -1,12 +1,12 @@ from typing import Optional -from ably.http.paginatedresult import PaginatedResult, format_params -from ably.types.channelsubscription import ( +from ably_pubsub.core.http.paginatedresult import PaginatedResult, format_params +from ably_pubsub.core.types.channelsubscription import ( PushChannelSubscription, channel_subscriptions_response_processor, channels_response_processor, ) -from ably.types.device import DeviceDetails, device_details_response_processor +from ably_pubsub.core.types.device import DeviceDetails, device_details_response_processor class Push: diff --git a/ably/rest/rest.py b/core/src/ably_pubsub/core/rest/rest.py similarity index 90% rename from ably/rest/rest.py rename to core/src/ably_pubsub/core/rest/rest.py index bc84e638..0a487532 100644 --- a/ably/rest/rest.py +++ b/core/src/ably_pubsub/core/rest/rest.py @@ -2,15 +2,15 @@ from typing import Optional from urllib.parse import urlencode -from ably.http.http import Http -from ably.http.paginatedresult import HttpPaginatedResponse, PaginatedResult, format_params -from ably.rest.auth import Auth -from ably.rest.channel import Channels -from ably.rest.push import Push -from ably.types.options import Options -from ably.types.stats import stats_response_processor -from ably.types.tokendetails import TokenDetails -from ably.util.exceptions import AblyException, catch_all +from ably_pubsub.core.http.http import Http +from ably_pubsub.core.http.paginatedresult import HttpPaginatedResponse, PaginatedResult, format_params +from ably_pubsub.core.rest.auth import Auth +from ably_pubsub.core.rest.channel import Channels +from ably_pubsub.core.rest.push import Push +from ably_pubsub.core.types.options import Options +from ably_pubsub.core.types.stats import stats_response_processor +from ably_pubsub.core.types.tokendetails import TokenDetails +from ably_pubsub.core.util.exceptions import AblyException, catch_all log = logging.getLogger(__name__) diff --git a/ably/scripts/__init__.py b/core/src/ably_pubsub/core/scripts/__init__.py similarity index 100% rename from ably/scripts/__init__.py rename to core/src/ably_pubsub/core/scripts/__init__.py diff --git a/ably/scripts/unasync.py b/core/src/ably_pubsub/core/scripts/unasync.py similarity index 89% rename from ably/scripts/unasync.py rename to core/src/ably_pubsub/core/scripts/unasync.py index d13e20f2..10f09075 100644 --- a/ably/scripts/unasync.py +++ b/core/src/ably_pubsub/core/scripts/unasync.py @@ -241,7 +241,10 @@ def run(): _TOKEN_REPLACE["aclose"] = "close" _TOKEN_REPLACE["assert_waiter"] = "assert_waiter_sync" - _IMPORTS_REPLACE["ably"] = "ably.sync" + # Matched as a substring of the dotted module path, so this must be the full + # `ably_pubsub.core` prefix: a bare "ably_pubsub" would also rewrite + # `ably_pubsub.server`, which is hand-written and never generated. + _IMPORTS_REPLACE["ably_pubsub.core"] = "ably_pubsub.core.sync" # here... for class_name in rename_classes: @@ -249,8 +252,10 @@ def run(): _STRING_REPLACE["Auth"] = "AuthSync" - src_dir_path = os.path.join(os.getcwd(), "ably") - dest_dir_path = os.path.join(os.getcwd(), "ably", "sync") + # The server distribution's sync door is hand-written, so only `core/` is a + # generation source. + src_dir_path = os.path.join(os.getcwd(), "core", "src", "ably_pubsub", "core") + dest_dir_path = os.path.join(src_dir_path, "sync") relevant_src_files = (set(find_files(src_dir_path, "*.py")) - set(find_files(dest_dir_path, "*.py"))) @@ -269,14 +274,17 @@ def run(): _IMPORTS_REPLACE["test.ably"] = "test.ably.sync" _STRING_REPLACE['/../assets/testAppSpec.json'] = '/../../assets/testAppSpec.json' - _STRING_REPLACE['ably.rest.auth.Auth.request_token'] = 'ably.sync.rest.auth.AuthSync.request_token' - _STRING_REPLACE['ably.rest.auth.TokenRequest'] = 'ably.sync.rest.auth.TokenRequest' - _STRING_REPLACE['ably.rest.rest.Http.post'] = 'ably.sync.rest.rest.HttpSync.post' + _STRING_REPLACE['ably_pubsub.core.rest.auth.Auth.request_token'] = \ + 'ably_pubsub.core.sync.rest.auth.AuthSync.request_token' + _STRING_REPLACE['ably_pubsub.core.rest.auth.TokenRequest'] = 'ably_pubsub.core.sync.rest.auth.TokenRequest' + _STRING_REPLACE['ably_pubsub.core.rest.rest.Http.post'] = 'ably_pubsub.core.sync.rest.rest.HttpSync.post' _STRING_REPLACE['httpx.AsyncClient.send'] = 'httpx.Client.send' - _STRING_REPLACE['ably.util.exceptions.AblyException.raise_for_response'] = \ - 'ably.sync.util.exceptions.AblyException.raise_for_response' - _STRING_REPLACE['ably.rest.rest.AblyRest.time'] = 'ably.sync.rest.rest.AblyRestSync.time' - _STRING_REPLACE['ably.rest.auth.Auth._timestamp'] = 'ably.sync.rest.auth.AuthSync._timestamp' + _STRING_REPLACE['ably_pubsub.core.util.exceptions.AblyException.raise_for_response'] = \ + 'ably_pubsub.core.sync.util.exceptions.AblyException.raise_for_response' + _STRING_REPLACE['ably_pubsub.core.rest.rest.AblyRest.time'] = \ + 'ably_pubsub.core.sync.rest.rest.AblyRestSync.time' + _STRING_REPLACE['ably_pubsub.core.rest.auth.Auth._timestamp'] = \ + 'ably_pubsub.core.sync.rest.auth.AuthSync._timestamp' # round 1 src_dir_path = os.path.join(os.getcwd(), "test", "ably") diff --git a/ably/transport/__init__.py b/core/src/ably_pubsub/core/transport/__init__.py similarity index 100% rename from ably/transport/__init__.py rename to core/src/ably_pubsub/core/transport/__init__.py diff --git a/ably/transport/defaults.py b/core/src/ably_pubsub/core/transport/defaults.py similarity index 100% rename from ably/transport/defaults.py rename to core/src/ably_pubsub/core/transport/defaults.py diff --git a/ably/transport/websockettransport.py b/core/src/ably_pubsub/core/transport/websockettransport.py similarity index 95% rename from ably/transport/websockettransport.py rename to core/src/ably_pubsub/core/transport/websockettransport.py index ad4f2856..3bd5968f 100644 --- a/ably/transport/websockettransport.py +++ b/core/src/ably_pubsub/core/transport/websockettransport.py @@ -10,12 +10,12 @@ import msgpack -from ably.http.httputils import HttpUtils -from ably.types.connectiondetails import ConnectionDetails -from ably.types.operations import PublishResult -from ably.util.eventemitter import EventEmitter -from ably.util.exceptions import AblyException -from ably.util.helper import Timer, unix_time_ms +from ably_pubsub.core.http.httputils import HttpUtils +from ably_pubsub.core.types.connectiondetails import ConnectionDetails +from ably_pubsub.core.types.operations import PublishResult +from ably_pubsub.core.util.eventemitter import EventEmitter +from ably_pubsub.core.util.exceptions import AblyException +from ably_pubsub.core.util.helper import Timer, unix_time_ms try: # websockets 15+ preferred imports @@ -29,7 +29,7 @@ from websockets.exceptions import ConnectionClosedOK, WebSocketException if TYPE_CHECKING: - from ably.realtime.connection import ConnectionManager + from ably_pubsub.core.realtime.connection import ConnectionManager log = logging.getLogger(__name__) @@ -78,7 +78,7 @@ def __init__(self, connection_manager: ConnectionManager, host: str, params: dic super().__init__() def connect(self): - headers = HttpUtils.default_headers() + headers = HttpUtils.default_headers(agents=self.options.agents) query_params = urllib.parse.urlencode(self.params) scheme = 'wss' if self.options.tls else 'ws' ws_url = f'{scheme}://{self.host}?{query_params}' diff --git a/ably/types/__init__.py b/core/src/ably_pubsub/core/types/__init__.py similarity index 100% rename from ably/types/__init__.py rename to core/src/ably_pubsub/core/types/__init__.py diff --git a/ably/types/annotation.py b/core/src/ably_pubsub/core/types/annotation.py similarity index 97% rename from ably/types/annotation.py rename to core/src/ably_pubsub/core/types/annotation.py index fea91468..a5ee3602 100644 --- a/ably/types/annotation.py +++ b/core/src/ably_pubsub/core/types/annotation.py @@ -1,11 +1,11 @@ import logging from enum import IntEnum -from ably.types.mixins import EncodeDataMixin -from ably.types.typedbuffer import TypedBuffer -from ably.util.crypto import CipherData -from ably.util.encoding import encode_data -from ably.util.helper import to_text +from ably_pubsub.core.types.mixins import EncodeDataMixin +from ably_pubsub.core.types.typedbuffer import TypedBuffer +from ably_pubsub.core.util.crypto import CipherData +from ably_pubsub.core.util.encoding import encode_data +from ably_pubsub.core.util.helper import to_text log = logging.getLogger(__name__) diff --git a/ably/types/authoptions.py b/core/src/ably_pubsub/core/types/authoptions.py similarity index 98% rename from ably/types/authoptions.py rename to core/src/ably_pubsub/core/types/authoptions.py index 7ee06af7..4f826442 100644 --- a/ably/types/authoptions.py +++ b/core/src/ably_pubsub/core/types/authoptions.py @@ -1,4 +1,4 @@ -from ably.util.exceptions import AblyException +from ably_pubsub.core.util.exceptions import AblyException class AuthOptions: diff --git a/ably/types/capability.py b/core/src/ably_pubsub/core/types/capability.py similarity index 100% rename from ably/types/capability.py rename to core/src/ably_pubsub/core/types/capability.py diff --git a/ably/types/channeldetails.py b/core/src/ably_pubsub/core/types/channeldetails.py similarity index 100% rename from ably/types/channeldetails.py rename to core/src/ably_pubsub/core/types/channeldetails.py diff --git a/ably/types/channelmode.py b/core/src/ably_pubsub/core/types/channelmode.py similarity index 96% rename from ably/types/channelmode.py rename to core/src/ably_pubsub/core/types/channelmode.py index 23ed735c..80c391ae 100644 --- a/ably/types/channelmode.py +++ b/core/src/ably_pubsub/core/types/channelmode.py @@ -2,7 +2,7 @@ from enum import Enum -from ably.types.flags import Flag +from ably_pubsub.core.types.flags import Flag class ChannelMode(int, Enum): diff --git a/ably/types/channeloptions.py b/core/src/ably_pubsub/core/types/channeloptions.py similarity index 93% rename from ably/types/channeloptions.py rename to core/src/ably_pubsub/core/types/channeloptions.py index 3e5052c6..750d1462 100644 --- a/ably/types/channeloptions.py +++ b/core/src/ably_pubsub/core/types/channeloptions.py @@ -2,9 +2,9 @@ from typing import Any -from ably.types.channelmode import ChannelMode -from ably.util.crypto import CipherParams -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.channelmode import ChannelMode +from ably_pubsub.core.util.crypto import CipherParams +from ably_pubsub.core.util.exceptions import AblyException class ChannelOptions: diff --git a/ably/types/channelstate.py b/core/src/ably_pubsub/core/types/channelstate.py similarity index 88% rename from ably/types/channelstate.py rename to core/src/ably_pubsub/core/types/channelstate.py index dcb68d67..e0313c45 100644 --- a/ably/types/channelstate.py +++ b/core/src/ably_pubsub/core/types/channelstate.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional -from ably.util.exceptions import AblyException +from ably_pubsub.core.util.exceptions import AblyException class ChannelState(str, Enum): diff --git a/ably/types/channelsubscription.py b/core/src/ably_pubsub/core/types/channelsubscription.py similarity index 97% rename from ably/types/channelsubscription.py rename to core/src/ably_pubsub/core/types/channelsubscription.py index b4c0dbf8..580b5bac 100644 --- a/ably/types/channelsubscription.py +++ b/core/src/ably_pubsub/core/types/channelsubscription.py @@ -1,4 +1,4 @@ -from ably.util import case +from ably_pubsub.core.util import case class PushChannelSubscription: diff --git a/ably/types/connectiondetails.py b/core/src/ably_pubsub/core/types/connectiondetails.py similarity index 100% rename from ably/types/connectiondetails.py rename to core/src/ably_pubsub/core/types/connectiondetails.py diff --git a/ably/types/connectionerrors.py b/core/src/ably_pubsub/core/types/connectionerrors.py similarity index 83% rename from ably/types/connectionerrors.py rename to core/src/ably_pubsub/core/types/connectionerrors.py index bb2fa1f4..41d93d66 100644 --- a/ably/types/connectionerrors.py +++ b/core/src/ably_pubsub/core/types/connectionerrors.py @@ -1,5 +1,5 @@ -from ably.types.connectionstate import ConnectionState -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.connectionstate import ConnectionState +from ably_pubsub.core.util.exceptions import AblyException ConnectionErrors = { ConnectionState.DISCONNECTED: AblyException( diff --git a/ably/types/connectionstate.py b/core/src/ably_pubsub/core/types/connectionstate.py similarity index 93% rename from ably/types/connectionstate.py rename to core/src/ably_pubsub/core/types/connectionstate.py index ec958358..1c73d8b5 100644 --- a/ably/types/connectionstate.py +++ b/core/src/ably_pubsub/core/types/connectionstate.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional -from ably.util.exceptions import AblyException +from ably_pubsub.core.util.exceptions import AblyException class ConnectionState(str, Enum): diff --git a/ably/types/device.py b/core/src/ably_pubsub/core/types/device.py similarity index 98% rename from ably/types/device.py rename to core/src/ably_pubsub/core/types/device.py index c2b84ee5..642e7b65 100644 --- a/ably/types/device.py +++ b/core/src/ably_pubsub/core/types/device.py @@ -1,4 +1,4 @@ -from ably.util import case +from ably_pubsub.core.util import case DevicePushTransportType = {'fcm', 'gcm', 'apns', 'web'} DevicePlatform = {'android', 'ios', 'browser'} diff --git a/ably/types/flags.py b/core/src/ably_pubsub/core/types/flags.py similarity index 100% rename from ably/types/flags.py rename to core/src/ably_pubsub/core/types/flags.py diff --git a/ably/types/message.py b/core/src/ably_pubsub/core/types/message.py similarity index 97% rename from ably/types/message.py rename to core/src/ably_pubsub/core/types/message.py index 2442a587..59efb72c 100644 --- a/ably/types/message.py +++ b/core/src/ably_pubsub/core/types/message.py @@ -1,12 +1,12 @@ import logging from enum import IntEnum -from ably.types.mixins import DeltaExtras, EncodeDataMixin -from ably.types.typedbuffer import TypedBuffer -from ably.util.crypto import CipherData -from ably.util.encoding import encode_data -from ably.util.exceptions import AblyException -from ably.util.helper import to_text +from ably_pubsub.core.types.mixins import DeltaExtras, EncodeDataMixin +from ably_pubsub.core.types.typedbuffer import TypedBuffer +from ably_pubsub.core.util.crypto import CipherData +from ably_pubsub.core.util.encoding import encode_data +from ably_pubsub.core.util.exceptions import AblyException +from ably_pubsub.core.util.helper import to_text log = logging.getLogger(__name__) diff --git a/ably/types/mixins.py b/core/src/ably_pubsub/core/types/mixins.py similarity index 97% rename from ably/types/mixins.py rename to core/src/ably_pubsub/core/types/mixins.py index 2d2b6041..85783d90 100644 --- a/ably/types/mixins.py +++ b/core/src/ably_pubsub/core/types/mixins.py @@ -2,8 +2,8 @@ import json import logging -from ably.util.crypto import CipherData -from ably.util.exceptions import AblyException +from ably_pubsub.core.util.crypto import CipherData +from ably_pubsub.core.util.exceptions import AblyException log = logging.getLogger(__name__) diff --git a/ably/types/operations.py b/core/src/ably_pubsub/core/types/operations.py similarity index 100% rename from ably/types/operations.py rename to core/src/ably_pubsub/core/types/operations.py diff --git a/ably/types/options.py b/core/src/ably_pubsub/core/types/options.py similarity index 92% rename from ably/types/options.py rename to core/src/ably_pubsub/core/types/options.py index 1dad41fb..30e13d35 100644 --- a/ably/types/options.py +++ b/core/src/ably_pubsub/core/types/options.py @@ -2,9 +2,9 @@ import random from abc import ABC, abstractmethod -from ably.transport.defaults import Defaults -from ably.types.authoptions import AuthOptions -from ably.util.exceptions import AblyException +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.types.authoptions import AuthOptions +from ably_pubsub.core.util.exceptions import AblyException log = logging.getLogger(__name__) @@ -34,7 +34,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, realti idempotent_rest_publishing=None, loop=None, auto_connect=True, suspended_retry_timeout=None, connectivity_check_url=None, channel_retry_timeout=Defaults.channel_retry_timeout, add_request_ids=False, - vcdiff_decoder: VCDiffDecoder = None, transport_params=None, **kwargs): + vcdiff_decoder: VCDiffDecoder = None, transport_params=None, agents=None, **kwargs): super().__init__(**kwargs) @@ -80,7 +80,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, realti ) if idempotent_rest_publishing is None: - from ably import api_version + from ably_pubsub.core import api_version idempotent_rest_publishing = api_version >= '1.2' if environment is not None and endpoint is None: @@ -129,6 +129,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, realti self.__add_request_ids = add_request_ids self.__vcdiff_decoder = vcdiff_decoder self.__transport_params = transport_params or {} + self.__agents = dict(agents) if agents else {} self.__hosts = self.__get_hosts() @property @@ -306,6 +307,18 @@ def vcdiff_decoder(self): def transport_params(self): return self.__transport_params + @property + def agents(self): + """Additional entries for the ``Ably-Agent`` header, as ``{name: version}``. + + A ``None`` version renders the entry as a bare flag, with no ``/version`` + suffix. Ably's own library and runtime entries are always sent and are + not configurable here; this is for the packages layered on top of the + core -- the side-declaring entry the server package stamps, or a + higher-level SDK's own attribution. + """ + return self.__agents + def __get_hosts(self): """ Return the list of hosts as they should be tried. First comes the main diff --git a/ably/types/presence.py b/core/src/ably_pubsub/core/types/presence.py similarity index 96% rename from ably/types/presence.py rename to core/src/ably_pubsub/core/types/presence.py index 7d1a3c05..6cd20132 100644 --- a/ably/types/presence.py +++ b/core/src/ably_pubsub/core/types/presence.py @@ -1,11 +1,11 @@ from datetime import datetime, timedelta from urllib import parse -from ably.http.paginatedresult import PaginatedResult -from ably.types.mixins import EncodeDataMixin -from ably.types.typedbuffer import TypedBuffer -from ably.util.crypto import CipherData -from ably.util.encoding import encode_data +from ably_pubsub.core.http.paginatedresult import PaginatedResult +from ably_pubsub.core.types.mixins import EncodeDataMixin +from ably_pubsub.core.types.typedbuffer import TypedBuffer +from ably_pubsub.core.util.crypto import CipherData +from ably_pubsub.core.util.encoding import encode_data def _ms_since_epoch(dt): diff --git a/ably/types/stats.py b/core/src/ably_pubsub/core/types/stats.py similarity index 100% rename from ably/types/stats.py rename to core/src/ably_pubsub/core/types/stats.py diff --git a/ably/types/tokendetails.py b/core/src/ably_pubsub/core/types/tokendetails.py similarity index 97% rename from ably/types/tokendetails.py rename to core/src/ably_pubsub/core/types/tokendetails.py index 771b29ec..d3251148 100644 --- a/ably/types/tokendetails.py +++ b/core/src/ably_pubsub/core/types/tokendetails.py @@ -1,7 +1,7 @@ import json import time -from ably.types.capability import Capability +from ably_pubsub.core.types.capability import Capability class TokenDetails: diff --git a/ably/types/tokenrequest.py b/core/src/ably_pubsub/core/types/tokenrequest.py similarity index 100% rename from ably/types/tokenrequest.py rename to core/src/ably_pubsub/core/types/tokenrequest.py diff --git a/ably/types/typedbuffer.py b/core/src/ably_pubsub/core/types/typedbuffer.py similarity index 100% rename from ably/types/typedbuffer.py rename to core/src/ably_pubsub/core/types/typedbuffer.py diff --git a/ably/util/__init__.py b/core/src/ably_pubsub/core/util/__init__.py similarity index 100% rename from ably/util/__init__.py rename to core/src/ably_pubsub/core/util/__init__.py diff --git a/ably/util/case.py b/core/src/ably_pubsub/core/util/case.py similarity index 100% rename from ably/util/case.py rename to core/src/ably_pubsub/core/util/case.py diff --git a/ably/util/crypto.py b/core/src/ably_pubsub/core/util/crypto.py similarity index 97% rename from ably/util/crypto.py rename to core/src/ably_pubsub/core/util/crypto.py index 8d8ddfd9..2763e8df 100644 --- a/ably/util/crypto.py +++ b/core/src/ably_pubsub/core/util/crypto.py @@ -7,8 +7,8 @@ except ImportError: from .nocrypto import AES, Random -from ably.types.typedbuffer import TypedBuffer -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.typedbuffer import TypedBuffer +from ably_pubsub.core.util.exceptions import AblyException log = logging.getLogger(__name__) diff --git a/ably/util/encoding.py b/core/src/ably_pubsub/core/util/encoding.py similarity index 90% rename from ably/util/encoding.py rename to core/src/ably_pubsub/core/util/encoding.py index 5187aec2..df4d9b55 100644 --- a/ably/util/encoding.py +++ b/core/src/ably_pubsub/core/util/encoding.py @@ -2,8 +2,8 @@ import json from typing import Any -from ably.util.crypto import CipherData -from ably.util.exceptions import AblyException +from ably_pubsub.core.util.crypto import CipherData +from ably_pubsub.core.util.exceptions import AblyException def encode_data(data: Any, encoding_array: list, binary: bool = False): diff --git a/ably/util/eventemitter.py b/core/src/ably_pubsub/core/util/eventemitter.py similarity index 99% rename from ably/util/eventemitter.py rename to core/src/ably_pubsub/core/util/eventemitter.py index 74f0beb6..93e0dfda 100644 --- a/ably/util/eventemitter.py +++ b/core/src/ably_pubsub/core/util/eventemitter.py @@ -3,7 +3,7 @@ from pyee.asyncio import AsyncIOEventEmitter -from ably.util.helper import is_callable_or_coroutine +from ably_pubsub.core.util.helper import is_callable_or_coroutine # pyee's event emitter doesn't support attaching a listener to all events # so to patch it, we create a wrapper which uses two event emitters, one diff --git a/ably/util/exceptions.py b/core/src/ably_pubsub/core/util/exceptions.py similarity index 100% rename from ably/util/exceptions.py rename to core/src/ably_pubsub/core/util/exceptions.py diff --git a/ably/util/helper.py b/core/src/ably_pubsub/core/util/helper.py similarity index 98% rename from ably/util/helper.py rename to core/src/ably_pubsub/core/util/helper.py index a35ebe6e..26a916f7 100644 --- a/ably/util/helper.py +++ b/core/src/ably_pubsub/core/util/helper.py @@ -9,7 +9,7 @@ import msgpack -from ably.util.exceptions import AblyException +from ably_pubsub.core.util.exceptions import AblyException def get_random_id(): diff --git a/ably/util/nocrypto.py b/core/src/ably_pubsub/core/util/nocrypto.py similarity index 100% rename from ably/util/nocrypto.py rename to core/src/ably_pubsub/core/util/nocrypto.py diff --git a/ably/vcdiff/__init__.py b/core/src/ably_pubsub/core/vcdiff/__init__.py similarity index 100% rename from ably/vcdiff/__init__.py rename to core/src/ably_pubsub/core/vcdiff/__init__.py diff --git a/ably/vcdiff/defaultvcdiffdecoder.py b/core/src/ably_pubsub/core/vcdiff/defaultvcdiffdecoder.py similarity index 92% rename from ably/vcdiff/defaultvcdiffdecoder.py rename to core/src/ably_pubsub/core/vcdiff/defaultvcdiffdecoder.py index ae2d3263..4e553773 100644 --- a/ably/vcdiff/defaultvcdiffdecoder.py +++ b/core/src/ably_pubsub/core/vcdiff/defaultvcdiffdecoder.py @@ -5,7 +5,7 @@ It implements the VCDiffDecoder interface. Usage: - from ably.vcdiff import AblyVCDiffDecoder, AblyRealtime + from ably_pubsub.core.vcdiff import AblyVCDiffDecoder, AblyRealtime # Create VCDiff decoder vcdiff_decoder = AblyVCDiffDecoder() @@ -19,8 +19,8 @@ import logging -from ably.types.options import VCDiffDecoder -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.options import VCDiffDecoder +from ably_pubsub.core.util.exceptions import AblyException log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index e4dbab6e..9c38daa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,108 +1,47 @@ -[project] -name = "ably" -version = "3.1.2" -description = "Python REST and Realtime client library SDK for Ably realtime messaging service" -readme = "LONG_DESCRIPTION.rst" -requires-python = ">=3.7" -license = { text = "Apache-2.0" } -authors = [ - { name = "Ably", email = "support@ably.com" } -] -classifiers = [ - "Development Status :: 6 - Mature", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "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", - "Programming Language :: Python :: 3.14", - "Topic :: Software Development :: Libraries :: Python Modules", -] -dependencies = [ - "msgpack>=1.0.0,<2.0.0", - "httpx>=0.24.1,<1.0; python_version=='3.7'", - "httpx>=0.25.0,<1.0; python_version>='3.8'", - "h2>=4.1.0,<5.0.0", - "websockets>=10.0,<12.0; python_version=='3.7'", - "websockets>=12.0,<15.0; python_version=='3.8'", - "websockets>=15.0,<16.0; python_version>='3.9'", - "pyee>=9.0.4,<10.0.0; python_version=='3.7'", - "pyee>=11.1.0,<14.0.0; python_version>='3.8'", -] +# Workspace root. This file is not published: the two distributions built from +# this repository are `core/` (ably-pubsub-core) and `server/` (ably-pubsub-server). +# It carries the shared developer tooling configuration only. -[project.optional-dependencies] -oldcrypto = ["pycrypto>=2.6.1,<3.0.0"] -crypto = ["pycryptodome"] -vcdiff = ["vcdiff-decoder>=0.1.0,<0.2.0"] +[tool.uv.workspace] +members = ["core", "server"] + +[tool.uv.sources] +ably-pubsub-core = { workspace = true } +ably-pubsub-server = { workspace = true } + +[dependency-groups] dev = [ + "ably-pubsub-core[crypto,vcdiff]", + "ably-pubsub-server", "pytest>=7.1,<8.0", - "pytest-asyncio>=0.21.0,<0.23.0; python_version=='3.7'", - "pytest-asyncio>=0.23.0,<1.0.0; python_version>='3.8'", + "pytest-asyncio>=0.23.0,<1.0.0", "mock>=4.0.3,<5.0.0", "pytest-cov>=2.4,<3.0", "ruff>=0.14.0,<1.0.0", "pytest-xdist>=1.15,<2.0", - "respx>=0.20.0,<0.21.0; python_version=='3.7'", - "respx>=0.22.0,<0.23.0; python_version>='3.8'", + "respx>=0.22.0,<0.23.0", "importlib-metadata>=4.12,<5.0", "pytest-timeout>=2.1.0,<3.0.0", - "async-case>=10.1.0,<11.0.0; python_version=='3.7'", "tokenize_rt", "vcdiff-decoder>=0.1.0a1", ] -[project.scripts] -unasync = "ably.scripts.unasync:run" - -[project.urls] -Homepage = "https://ably.com" -Repository = "https://github.com/ably/ably-python" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.sdist] -ignore-vcs = true -include = [ - "/ably", - "/COPYRIGHT", - "/README.md", - "/LICENSE", - "/LONG_DESCRIPTION.rst", - "/images", - "/setup.cfg", - "/pyproject.toml" -] -exclude = [ - "**/*.pyc", - "**/__pycache__" -] - -[tool.hatch.build.targets.wheel] -ignore-vcs = true -packages = ["ably"] - -[tool.pytest.ini_options] -timeout = 30 -asyncio_mode = "auto" - [[tool.uv.index]] name = "experimental" url = "https://test.pypi.org/simple/" explicit = true +[tool.pytest.ini_options] +timeout = 30 +asyncio_mode = "auto" + [tool.ruff] line-length = 115 +# The workspace root has no [project] table for ruff to infer this from; it must +# match `requires-python` in core/ and server/. +target-version = "py38" extend-exclude = [ - "ably/sync", + "core/src/ably_pubsub/core/sync", "test/ably/sync", ] @@ -111,8 +50,11 @@ extend-exclude = [ select = ["E", "W", "F", "N", "I", "UP", "B", "C4"] ignore = [ "N818", # exception name should end in 'Error' - "UP026", # mock -> unittest.mock (need mock package for Python 3.7 AsyncMock support) + "UP026", # mock -> unittest.mock (need mock package for AsyncMock support on old Pythons) ] +[tool.ruff.lint.isort] +known-first-party = ["ably_pubsub"] + [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] # imported but unused diff --git a/server/README.md b/server/README.md new file mode 100644 index 00000000..a67f195e --- /dev/null +++ b/server/README.md @@ -0,0 +1,99 @@ +# ably-pubsub-server + +The [Ably](https://ably.com) Pub/Sub SDK for Python **servers** — backends, jobs, +and any other trusted environment that holds an API key. + +Ably is the platform that powers synchronized digital experiences in realtime. +Pub/Sub is its foundational product: publish and subscribe messaging over +channels, presence, history, and token issuing. + +Choosing the package by side matters beyond naming: connections made by this +package declare themselves as server-side on the wire, and server connections +are exempt from monthly-active-user counting. + +## Installation + +```shell +pip install ably-pubsub-server +``` + +Optional extras: `crypto` (channel encryption, via pycryptodome) and `vcdiff` +(delta decoding). + +```shell +pip install "ably-pubsub-server[crypto]" +``` + +## Usage + +Create clients through the factory functions — they are the entry points of this +package. + +```python +from ably_pubsub.server import create_http_client, create_realtime_client +``` + +### Stateless HTTP client + +For publishing, history, presence reads, stats and token issuing: + +```python +import asyncio +from ably_pubsub.server import create_http_client + + +async def main(): + async with create_http_client(key='your-api-key') as client: + channel = client.channels.get('some-channel') + await channel.publish('greeting', 'hello') + +asyncio.run(main()) +``` + +A synchronous flavour of the HTTP client is available for code that has no event +loop: + +```python +from ably_pubsub.server.sync import create_http_client + +client = create_http_client(key='your-api-key') +client.channels.get('some-channel').publish('greeting', 'hello') +``` + +### Realtime client + +When the server also needs to subscribe to channels or enter presence over a +persistent connection: + +```python +import asyncio +from ably_pubsub.server import create_realtime_client + + +async def main(): + client = create_realtime_client(key='your-api-key') + channel = client.channels.get('some-channel') + await channel.subscribe(lambda message: print(message.data)) + await asyncio.sleep(10) + await client.close() + +asyncio.run(main()) +``` + +There is no synchronous realtime client. + +## Relationship to other packages + +- `ably-pubsub-core` — the shared implementation this package is built on. It is + an internal package; do not import `ably_pubsub.core` directly. Everything + supported is re-exported from `ably_pubsub.server`. +- `ably` — the 3.x package this one replaces. See + [UPDATING.md](https://github.com/ably/ably-python/blob/main/UPDATING.md). + +## Contributing + +See [CONTRIBUTING.md](https://github.com/ably/ably-python/blob/main/CONTRIBUTING.md). + +## License + +Apache License 2.0 — see [LICENSE](https://github.com/ably/ably-python/blob/main/LICENSE). diff --git a/server/pyproject.toml b/server/pyproject.toml new file mode 100644 index 00000000..19a9960a --- /dev/null +++ b/server/pyproject.toml @@ -0,0 +1,62 @@ +[project] +name = "ably-pubsub-server" +version = "4.0.0" +description = "Ably Pub/Sub SDK for Python servers — realtime messaging for trusted server environments" +readme = "README.md" +requires-python = ">=3.8" +license = { text = "Apache-2.0" } +authors = [ + { name = "Ably", email = "support@ably.com" } +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "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", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", +] +# Exact pin: core and server are released in lockstep, and two core versions in +# one environment would be indistinguishable on the wire. +dependencies = [ + "ably-pubsub-core==4.0.0", +] + +[project.optional-dependencies] +oldcrypto = ["ably-pubsub-core[oldcrypto]==4.0.0"] +crypto = ["ably-pubsub-core[crypto]==4.0.0"] +vcdiff = ["ably-pubsub-core[vcdiff]==4.0.0"] + +[project.urls] +Homepage = "https://ably.com" +Repository = "https://github.com/ably/ably-python" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.sdist] +ignore-vcs = true +include = [ + "/src/ably_pubsub", + "/README.md", + "/pyproject.toml" +] +exclude = [ + "**/*.pyc", + "**/__pycache__" +] + +[tool.hatch.build.targets.wheel] +ignore-vcs = true +# PEP 420: `src/ably_pubsub` has no __init__.py, so this ships +# `ably_pubsub/server/**` into the shared `ably_pubsub` namespace. +packages = ["src/ably_pubsub"] diff --git a/server/src/ably_pubsub/server/__init__.py b/server/src/ably_pubsub/server/__init__.py new file mode 100644 index 00000000..03801909 --- /dev/null +++ b/server/src/ably_pubsub/server/__init__.py @@ -0,0 +1,170 @@ +"""The Ably Pub/Sub SDK for servers. + +Servers are trusted environments which typically authenticate with an API key, +and whose connections are exempt from monthly-active-user counting. This package +names that side, so the client an application should reach for is the one whose +package matches where it runs. + +Use `create_http_client()` for publish, history, presence reads, stats and token +issuing over HTTP, and `create_realtime_client()` when the server also needs to +subscribe to channels or enter presence over a persistent connection. These +factories are the entry points of the `ably-pubsub-server` distribution; the +types they work with are re-exported here so that `ably_pubsub.core`, which is +an internal implementation package, never needs to be imported directly. +""" + +import asyncio +from typing import Optional + +from ably_pubsub.core import ( + AblyAuthException, + AblyException, + AblyRealtime, + AblyRest, + AblyVCDiffDecoder, + Annotation, + AnnotationAction, + Auth, + Capability, + ChannelMode, + ChannelOptions, + CipherParams, + DeviceDetails, + IncompatibleClientIdException, + MessageAction, + MessageOperation, + MessageVersion, + Options, + PublishResult, + Push, + PushChannelSubscription, + UpdateDeleteResult, + VCDiffDecoder, +) +from ably_pubsub.core.http.paginatedresult import HttpPaginatedResponse, PaginatedResult +from ably_pubsub.core.realtime.channel import RealtimeChannel +from ably_pubsub.core.realtime.connection import Connection +from ably_pubsub.core.realtime.presence import RealtimePresence +from ably_pubsub.core.rest.channel import Channel +from ably_pubsub.core.types.channeldetails import ( + ChannelDetails, + ChannelMetrics, + ChannelOccupancy, + ChannelStatus, +) +from ably_pubsub.core.types.channelstate import ChannelState, ChannelStateChange +from ably_pubsub.core.types.connectionstate import ( + ConnectionEvent, + ConnectionState, + ConnectionStateChange, +) +from ably_pubsub.core.types.message import Message, MessageAnnotations +from ably_pubsub.core.types.presence import Presence, PresenceAction, PresenceMessage +from ably_pubsub.core.types.stats import Stats +from ably_pubsub.core.types.tokendetails import TokenDetails +from ably_pubsub.core.types.tokenrequest import TokenRequest + +__version__ = '4.0.0' + +# The agent identifier declaring the server side. +# +# The `-server` suffix is load-bearing, not cosmetic. On API-key auth the realtime +# system grants the MAU server exemption by matching an agent entry ending in +# `-server`, and an identifier that is not yet in the ably-common registry is +# classified by that suffix alone. Renaming it without preserving the suffix +# silently reclassifies every client this package constructs. +# +# The entry is stamped WITHOUT a version, matching its registration in the +# ably-common agents registry (a pure flag, like `browser`): under lockstep +# versioning a version here always duplicates the ably-pubsub-python entry beside +# it, which keeps carrying identity, version and support status. Wire shape: +# ably-pubsub-python/4.0.0 python/3.12.1 ably-pubsub-server +SERVER_AGENT_IDENTIFIER = 'ably-pubsub-server' + + +def _agents_with_side(agents: Optional[dict]) -> dict: + """Return the caller's agent entries carrying this package's side entry. + + The caller's own entries are preserved, so an SDK layered on top of this + package keeps its attribution. The side entry is merged last and so wins a + collision on its own identifier: which side the package declares is the + package's to state, not the caller's to redefine. + """ + return {**(agents or {}), SERVER_AGENT_IDENTIFIER: None} + + +def create_http_client(key: Optional[str] = None, token: Optional[str] = None, + token_details: Optional[TokenDetails] = None, **options) -> AblyRest: + """Create a server Pub/Sub client that operates entirely over HTTP. + + Publishing, history, presence reads, stats and token issuing, with no + persistent connection. Takes the same arguments as the underlying client + constructor, and behaves identically to it. + """ + return AblyRest(key=key, token=token, token_details=token_details, + agents=_agents_with_side(options.pop('agents', None)), **options) + + +def create_realtime_client(key: Optional[str] = None, loop: Optional[asyncio.AbstractEventLoop] = None, + **options) -> AblyRealtime: + """Create a server Pub/Sub client with a persistent realtime connection. + + Everything the HTTP client does, plus subscribing to channels and entering + presence. Takes the same arguments as the underlying client constructor, and + behaves identically to it. + """ + return AblyRealtime(key=key, loop=loop, + agents=_agents_with_side(options.pop('agents', None)), **options) + + +__all__ = [ + 'AblyAuthException', + 'AblyException', + 'AblyRealtime', + 'AblyRest', + 'AblyVCDiffDecoder', + 'Annotation', + 'AnnotationAction', + 'Auth', + 'Capability', + 'Channel', + 'ChannelDetails', + 'ChannelMetrics', + 'ChannelMode', + 'ChannelOccupancy', + 'ChannelOptions', + 'ChannelState', + 'ChannelStateChange', + 'ChannelStatus', + 'CipherParams', + 'Connection', + 'ConnectionEvent', + 'ConnectionState', + 'ConnectionStateChange', + 'DeviceDetails', + 'HttpPaginatedResponse', + 'IncompatibleClientIdException', + 'Message', + 'MessageAction', + 'MessageAnnotations', + 'MessageOperation', + 'MessageVersion', + 'Options', + 'PaginatedResult', + 'Presence', + 'PresenceAction', + 'PresenceMessage', + 'PublishResult', + 'Push', + 'PushChannelSubscription', + 'RealtimeChannel', + 'RealtimePresence', + 'SERVER_AGENT_IDENTIFIER', + 'Stats', + 'TokenDetails', + 'TokenRequest', + 'UpdateDeleteResult', + 'VCDiffDecoder', + 'create_http_client', + 'create_realtime_client', +] diff --git a/server/src/ably_pubsub/server/sync.py b/server/src/ably_pubsub/server/sync.py new file mode 100644 index 00000000..1fabbd13 --- /dev/null +++ b/server/src/ably_pubsub/server/sync.py @@ -0,0 +1,104 @@ +"""The synchronous flavour of the Ably Pub/Sub SDK for servers. + +Offers the HTTP client without an event loop. There is no synchronous realtime +client, so a server that needs to subscribe should use +`ably_pubsub.server.create_realtime_client()` instead. + +Hand-written rather than generated: the code generator that produces +`ably_pubsub.core.sync` runs over the core distribution only. +""" + +from typing import Optional + +from ably_pubsub.core.sync import ( + AblyAuthException, + AblyException, + AblyRestSync, + AblyVCDiffDecoder, + Annotation, + AnnotationAction, + AuthSync, + Capability, + ChannelMode, + ChannelOptions, + CipherParams, + DeviceDetails, + IncompatibleClientIdException, + MessageAction, + MessageOperation, + MessageVersion, + Options, + PublishResult, + PushChannelSubscription, + PushSync, + UpdateDeleteResult, + VCDiffDecoder, +) +from ably_pubsub.core.sync.http.paginatedresult import HttpPaginatedResponseSync, PaginatedResultSync +from ably_pubsub.core.sync.rest.channel import ChannelSync +from ably_pubsub.core.sync.types.channeldetails import ( + ChannelDetails, + ChannelMetrics, + ChannelOccupancy, + ChannelStatus, +) +from ably_pubsub.core.sync.types.message import Message, MessageAnnotations +from ably_pubsub.core.sync.types.presence import Presence, PresenceAction, PresenceMessage +from ably_pubsub.core.sync.types.stats import Stats +from ably_pubsub.core.sync.types.tokendetails import TokenDetails +from ably_pubsub.core.sync.types.tokenrequest import TokenRequest +from ably_pubsub.server import SERVER_AGENT_IDENTIFIER, _agents_with_side + + +def create_http_client(key: Optional[str] = None, token: Optional[str] = None, + token_details: Optional[TokenDetails] = None, **options) -> AblyRestSync: + """Create a synchronous server Pub/Sub client that operates entirely over HTTP. + + Takes the same arguments as the underlying client constructor, and behaves + identically to it. + """ + return AblyRestSync(key=key, token=token, token_details=token_details, + agents=_agents_with_side(options.pop('agents', None)), **options) + + +__all__ = [ + 'AblyAuthException', + 'AblyException', + 'AblyRestSync', + 'AblyVCDiffDecoder', + 'Annotation', + 'AnnotationAction', + 'AuthSync', + 'Capability', + 'ChannelDetails', + 'ChannelMetrics', + 'ChannelMode', + 'ChannelOccupancy', + 'ChannelOptions', + 'ChannelStatus', + 'ChannelSync', + 'CipherParams', + 'DeviceDetails', + 'HttpPaginatedResponseSync', + 'IncompatibleClientIdException', + 'Message', + 'MessageAction', + 'MessageAnnotations', + 'MessageOperation', + 'MessageVersion', + 'Options', + 'PaginatedResultSync', + 'Presence', + 'PresenceAction', + 'PresenceMessage', + 'PublishResult', + 'PushChannelSubscription', + 'PushSync', + 'SERVER_AGENT_IDENTIFIER', + 'Stats', + 'TokenDetails', + 'TokenRequest', + 'UpdateDeleteResult', + 'VCDiffDecoder', + 'create_http_client', +] diff --git a/test/ably/realtime/eventemitter_test.py b/test/ably/realtime/eventemitter_test.py index 71db2c74..30abfa6d 100644 --- a/test/ably/realtime/eventemitter_test.py +++ b/test/ably/realtime/eventemitter_test.py @@ -2,7 +2,7 @@ import pytest -from ably.realtime.connection import ConnectionState +from ably_pubsub.core.realtime.connection import ConnectionState from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/presencemap_test.py b/test/ably/realtime/presencemap_test.py index 043baeb0..b40336cf 100644 --- a/test/ably/realtime/presencemap_test.py +++ b/test/ably/realtime/presencemap_test.py @@ -8,8 +8,8 @@ import pytest -from ably.realtime.presencemap import PresenceMap, _is_newer -from ably.types.presence import PresenceAction, PresenceMessage +from ably_pubsub.core.realtime.presencemap import PresenceMap, _is_newer +from ably_pubsub.core.types.presence import PresenceAction, PresenceMessage from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/realtimeannotations_test.py b/test/ably/realtime/realtimeannotations_test.py index a82b6b2b..2e3d1e3d 100644 --- a/test/ably/realtime/realtimeannotations_test.py +++ b/test/ably/realtime/realtimeannotations_test.py @@ -5,10 +5,10 @@ import pytest -from ably.types.annotation import Annotation, AnnotationAction -from ably.types.channelmode import ChannelMode -from ably.types.channeloptions import ChannelOptions -from ably.types.message import MessageAction +from ably_pubsub.core.types.annotation import Annotation, AnnotationAction +from ably_pubsub.core.types.channelmode import ChannelMode +from ably_pubsub.core.types.channeloptions import ChannelOptions +from ably_pubsub.core.types.message import MessageAction from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, ReusableFuture, assert_waiter @@ -332,7 +332,7 @@ async def on_annotation(annotation): pass # RTAN4e: Should log a warning (not raise), and still register the listener - with caplog.at_level(logging.WARNING, logger='ably.realtime.annotations'): + with caplog.at_level(logging.WARNING, logger='ably_pubsub.core.realtime.annotations'): await channel.annotations.subscribe(on_annotation) # Verify warning was logged mentioning the missing mode diff --git a/test/ably/realtime/realtimeauth_test.py b/test/ably/realtime/realtimeauth_test.py index 6ec53356..b3004a1c 100644 --- a/test/ably/realtime/realtimeauth_test.py +++ b/test/ably/realtime/realtimeauth_test.py @@ -5,11 +5,11 @@ import httpx import pytest -from ably.realtime.connection import ConnectionState -from ably.transport.websockettransport import ProtocolMessageAction -from ably.types.channelstate import ChannelState -from ably.types.connectionstate import ConnectionEvent -from ably.types.tokendetails import TokenDetails +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.types.channelstate import ChannelState +from ably_pubsub.core.types.connectionstate import ConnectionEvent +from ably_pubsub.core.types.tokendetails import TokenDetails from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, random_string diff --git a/test/ably/realtime/realtimechannel_publish_test.py b/test/ably/realtime/realtimechannel_publish_test.py index 9ecf10f9..9a9b7f5a 100644 --- a/test/ably/realtime/realtimechannel_publish_test.py +++ b/test/ably/realtime/realtimechannel_publish_test.py @@ -2,13 +2,13 @@ import pytest -from ably.realtime.channel import ChannelState -from ably.realtime.connection import ConnectionState -from ably.transport.websockettransport import ProtocolMessageAction -from ably.types.channeloptions import ChannelOptions -from ably.types.message import Message -from ably.util.crypto import CipherParams -from ably.util.exceptions import AblyException, IncompatibleClientIdException +from ably_pubsub.core.realtime.channel import ChannelState +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.types.channeloptions import ChannelOptions +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.util.crypto import CipherParams +from ably_pubsub.core.util.exceptions import AblyException, IncompatibleClientIdException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, WaitableEvent, assert_waiter @@ -328,7 +328,7 @@ async def test_msgserial_resets_on_new_connection_id(self): new_connection_id = 'new_connection_id_12345' # Simulate server sending CONNECTED with new connectionId - from ably.types.connectiondetails import ConnectionDetails + from ably_pubsub.core.types.connectiondetails import ConnectionDetails new_connection_details = ConnectionDetails( connection_state_ttl=120000, max_idle_interval=15000, @@ -365,7 +365,7 @@ async def test_msgserial_not_reset_on_same_connection_id(self): # Simulate reconnection with SAME connectionId (transport change, not new connection) same_connection_id = connection_manager.connection_id - from ably.types.connectiondetails import ConnectionDetails + from ably_pubsub.core.types.connectiondetails import ConnectionDetails connection_details = ConnectionDetails( connection_state_ttl=120000, max_idle_interval=15000, @@ -491,7 +491,7 @@ async def check_pending(): assert connection_manager.msg_serial == 1 # Simulate resume with SAME connectionId - from ably.types.connectiondetails import ConnectionDetails + from ably_pubsub.core.types.connectiondetails import ConnectionDetails connection_details = ConnectionDetails( connection_state_ttl=120000, max_idle_interval=15000, @@ -541,7 +541,7 @@ async def check_pending(): assert connection_manager.msg_serial == 1 # Simulate NEW connection (different connectionId = failed resume) - from ably.types.connectiondetails import ConnectionDetails + from ably_pubsub.core.types.connectiondetails import ConnectionDetails new_connection_details = ConnectionDetails( connection_state_ttl=120000, max_idle_interval=15000, diff --git a/test/ably/realtime/realtimechannel_test.py b/test/ably/realtime/realtimechannel_test.py index 6d2865f2..78eb0c2b 100644 --- a/test/ably/realtime/realtimechannel_test.py +++ b/test/ably/realtime/realtimechannel_test.py @@ -2,12 +2,12 @@ import pytest -from ably.realtime.channel import ChannelState, RealtimeChannel -from ably.realtime.connection import ConnectionState -from ably.transport.websockettransport import ProtocolMessageAction -from ably.types.channeloptions import ChannelOptions -from ably.types.message import Message -from ably.util.exceptions import AblyException +from ably_pubsub.core.realtime.channel import ChannelState, RealtimeChannel +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.types.channeloptions import ChannelOptions +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, random_string diff --git a/test/ably/realtime/realtimechannel_vcdiff_test.py b/test/ably/realtime/realtimechannel_vcdiff_test.py index 48a484a9..2d4842e9 100644 --- a/test/ably/realtime/realtimechannel_vcdiff_test.py +++ b/test/ably/realtime/realtimechannel_vcdiff_test.py @@ -3,10 +3,10 @@ import pytest -from ably import AblyVCDiffDecoder -from ably.realtime.connection import ConnectionState -from ably.types.channeloptions import ChannelOptions -from ably.types.options import VCDiffDecoder +from ably_pubsub.core import AblyVCDiffDecoder +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.types.channeloptions import ChannelOptions +from ably_pubsub.core.types.options import VCDiffDecoder from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, WaitableEvent diff --git a/test/ably/realtime/realtimechannelmutablemessages_test.py b/test/ably/realtime/realtimechannelmutablemessages_test.py index afe8a60f..3d891f4c 100644 --- a/test/ably/realtime/realtimechannelmutablemessages_test.py +++ b/test/ably/realtime/realtimechannelmutablemessages_test.py @@ -3,9 +3,9 @@ import pytest -from ably import AblyException, CipherParams, MessageAction -from ably.types.message import Message -from ably.types.operations import MessageOperation +from ably_pubsub.core import AblyException, CipherParams, MessageAction +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.types.operations import MessageOperation from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, WaitableEvent, assert_waiter diff --git a/test/ably/realtime/realtimeconnection_test.py b/test/ably/realtime/realtimeconnection_test.py index 2593eb3e..cdf3edbd 100644 --- a/test/ably/realtime/realtimeconnection_test.py +++ b/test/ably/realtime/realtimeconnection_test.py @@ -10,10 +10,10 @@ # websockets 14 and earlier fallback from websockets.server import serve as ws_serve -from ably.realtime.connection import ConnectionEvent, ConnectionState -from ably.transport.defaults import Defaults -from ably.transport.websockettransport import ProtocolMessageAction -from ably.util.exceptions import AblyException +from ably_pubsub.core.realtime.connection import ConnectionEvent, ConnectionState +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/realtimeinit_test.py b/test/ably/realtime/realtimeinit_test.py index 4009d046..9183ea9e 100644 --- a/test/ably/realtime/realtimeinit_test.py +++ b/test/ably/realtime/realtimeinit_test.py @@ -2,9 +2,9 @@ import pytest -from ably import Auth -from ably.realtime.connection import ConnectionState -from ably.util.exceptions import AblyAuthException +from ably_pubsub.core import Auth +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.util.exceptions import AblyAuthException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/realtimepresence_test.py b/test/ably/realtime/realtimepresence_test.py index 86a073c7..42c815a7 100644 --- a/test/ably/realtime/realtimepresence_test.py +++ b/test/ably/realtime/realtimepresence_test.py @@ -9,10 +9,10 @@ import pytest -from ably.realtime.connection import ConnectionState -from ably.types.channelstate import ChannelState -from ably.types.presence import PresenceAction -from ably.util.exceptions import AblyException +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.types.channelstate import ChannelState +from ably_pubsub.core.types.presence import PresenceAction +from ably_pubsub.core.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/realtime/realtimeresume_test.py b/test/ably/realtime/realtimeresume_test.py index bfe77efa..6e20a707 100644 --- a/test/ably/realtime/realtimeresume_test.py +++ b/test/ably/realtime/realtimeresume_test.py @@ -2,9 +2,9 @@ import pytest -from ably.realtime.channel import ChannelState -from ably.realtime.connection import ConnectionState -from ably.transport.websockettransport import ProtocolMessageAction +from ably_pubsub.core.realtime.channel import ChannelState +from ably_pubsub.core.realtime.connection import ConnectionState +from ably_pubsub.core.transport.websockettransport import ProtocolMessageAction from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, random_string diff --git a/test/ably/rest/encoders_test.py b/test/ably/rest/encoders_test.py index f8023c5d..09e3243a 100644 --- a/test/ably/rest/encoders_test.py +++ b/test/ably/rest/encoders_test.py @@ -1,23 +1,18 @@ import base64 import json import logging -import sys from unittest import mock +from unittest.mock import AsyncMock import msgpack import pytest -from ably import CipherParams -from ably.types.message import Message -from ably.util.crypto import get_cipher +from ably_pubsub.core import CipherParams +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.util.crypto import get_cipher from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase -if sys.version_info >= (3, 8): - from unittest.mock import AsyncMock -else: - from mock import AsyncMock - log = logging.getLogger(__name__) @@ -31,7 +26,7 @@ async def setup(self): async def test_text_utf8(self): channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', 'foó') _, kwargs = post_mock.call_args assert json.loads(kwargs['body'])['data'] == 'foó' @@ -41,7 +36,7 @@ async def test_str(self): # This test only makes sense for py2 channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', 'foo') _, kwargs = post_mock.call_args assert json.loads(kwargs['body'])['data'] == 'foo' @@ -50,7 +45,7 @@ async def test_str(self): async def test_with_binary_type(self): channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args raw_data = json.loads(kwargs['body'])['data'] @@ -60,7 +55,7 @@ async def test_with_binary_type(self): async def test_with_bytes_type(self): channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', b'foo') _, kwargs = post_mock.call_args raw_data = json.loads(kwargs['body'])['data'] @@ -70,7 +65,7 @@ async def test_with_bytes_type(self): async def test_with_json_dict_data(self): channel = self.ably.channels["persisted:publish"] data = {'foó': 'bár'} - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args raw_data = json.loads(json.loads(kwargs['body'])['data']) @@ -80,7 +75,7 @@ async def test_with_json_dict_data(self): async def test_with_json_list_data(self): channel = self.ably.channels["persisted:publish"] data = ['foó', 'bár'] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args raw_data = json.loads(json.loads(kwargs['body'])['data']) @@ -161,7 +156,7 @@ def decrypt(self, payload, options=None): async def test_text_utf8(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', 'fóo') _, kwargs = post_mock.call_args assert json.loads(kwargs['body'])['encoding'].strip('/') == 'utf-8/cipher+aes-128-cbc/base64' @@ -172,7 +167,7 @@ async def test_str(self): # This test only makes sense for py2 channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', 'foo') _, kwargs = post_mock.call_args assert json.loads(kwargs['body'])['data'] == 'foo' @@ -182,7 +177,7 @@ async def test_with_binary_type(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args @@ -195,7 +190,7 @@ async def test_with_json_dict_data(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) data = {'foó': 'bár'} - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args assert json.loads(kwargs['body'])['encoding'].strip('/') == 'json/utf-8/cipher+aes-128-cbc/base64' @@ -206,7 +201,7 @@ async def test_with_json_list_data(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) data = ['foó', 'bár'] - with mock.patch('ably.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: + with mock.patch('ably_pubsub.core.rest.rest.Http.post', new_callable=AsyncMock) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args assert json.loads(kwargs['body'])['encoding'].strip('/') == 'json/utf-8/cipher+aes-128-cbc/base64' @@ -269,7 +264,7 @@ def decode(self, data): async def test_text_utf8(self): channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', 'foó') _, kwargs = post_mock.call_args @@ -279,7 +274,7 @@ async def test_text_utf8(self): async def test_with_binary_type(self): channel = self.ably.channels["persisted:publish"] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args @@ -289,7 +284,7 @@ async def test_with_binary_type(self): async def test_with_json_dict_data(self): channel = self.ably.channels["persisted:publish"] data = {'foó': 'bár'} - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args @@ -300,7 +295,7 @@ async def test_with_json_dict_data(self): async def test_with_json_list_data(self): channel = self.ably.channels["persisted:publish"] data = ['foó', 'bár'] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args @@ -367,7 +362,7 @@ def decode(self, data): async def test_text_utf8(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', 'fóo') _, kwargs = post_mock.call_args @@ -379,7 +374,7 @@ async def test_with_binary_type(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args @@ -393,7 +388,7 @@ async def test_with_json_dict_data(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) data = {'foó': 'bár'} - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args @@ -405,7 +400,7 @@ async def test_with_json_list_data(self): channel = self.ably.channels.get("persisted:publish_enc", cipher=self.cipher_params) data = ['foó', 'bár'] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish('event', data) _, kwargs = post_mock.call_args diff --git a/test/ably/rest/restannotations_test.py b/test/ably/rest/restannotations_test.py index fcf2c696..07276dad 100644 --- a/test/ably/rest/restannotations_test.py +++ b/test/ably/rest/restannotations_test.py @@ -4,9 +4,9 @@ import pytest -from ably import AblyException -from ably.types.annotation import Annotation, AnnotationAction -from ably.types.message import Message +from ably_pubsub.core import AblyException +from ably_pubsub.core.types.annotation import Annotation, AnnotationAction +from ably_pubsub.core.types.message import Message from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, assert_waiter diff --git a/test/ably/rest/restauth_test.py b/test/ably/rest/restauth_test.py index 185021e1..8986b390 100644 --- a/test/ably/rest/restauth_test.py +++ b/test/ably/rest/restauth_test.py @@ -1,26 +1,21 @@ import base64 import logging -import sys import time import uuid from unittest import mock +from unittest.mock import AsyncMock from urllib.parse import parse_qs import pytest import respx from httpx import AsyncClient, Response -import ably -from ably import AblyAuthException, AblyRest, Auth -from ably.types.tokendetails import TokenDetails +import ably_pubsub.core.types.tokenrequest +from ably_pubsub.core import AblyAuthException, AblyRest, Auth +from ably_pubsub.core.types.tokendetails import TokenDetails from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol -if sys.version_info >= (3, 8): - from unittest.mock import AsyncMock -else: - from mock import AsyncMock - log = logging.getLogger(__name__) @@ -198,7 +193,7 @@ async def test_authorize_always_creates_new_token(self): async def test_authorize_create_new_token_if_expired(self): token = await self.ably.auth.authorize() - with mock.patch('ably.rest.auth.Auth.token_details_has_expired', + with mock.patch('ably_pubsub.core.rest.auth.Auth.token_details_has_expired', return_value=True): new_token = await self.ably.auth.authorize() @@ -212,7 +207,7 @@ async def test_authorize_returns_a_token_details(self): async def test_authorize_adheres_to_request_token(self): token_params = {'ttl': 10, 'client_id': 'client_id'} auth_params = {'auth_url': 'somewhere.com', 'query_time': True} - with mock.patch('ably.rest.auth.Auth.request_token', new_callable=AsyncMock) as request_mock: + with mock.patch('ably_pubsub.core.rest.auth.Auth.request_token', new_callable=AsyncMock) as request_mock: await self.ably.auth.authorize(token_params, auth_params) token_called, auth_called = request_mock.call_args @@ -251,7 +246,7 @@ async def test_if_parameters_are_stored_and_used_as_defaults(self): auth_options = dict(self.ably.auth.auth_options.auth_options) auth_options['auth_headers'] = {'a_headers': 'a_value'} await self.ably.auth.authorize({'ttl': 555}, auth_options) - with mock.patch('ably.rest.auth.Auth.request_token', + with mock.patch('ably_pubsub.core.rest.auth.Auth.request_token', wraps=self.ably.auth.request_token) as request_mock: await self.ably.auth.authorize() @@ -263,7 +258,7 @@ async def test_if_parameters_are_stored_and_used_as_defaults(self): auth_options = dict(self.ably.auth.auth_options.auth_options) auth_options['auth_headers'] = None await self.ably.auth.authorize({}, auth_options) - with mock.patch('ably.rest.auth.Auth.request_token', + with mock.patch('ably_pubsub.core.rest.auth.Auth.request_token', wraps=self.ably.auth.request_token) as request_mock: await self.ably.auth.authorize() @@ -283,8 +278,8 @@ async def test_timestamp_is_not_stored(self): # call authorize again with timestamp set timestamp = await self.ably.time() - with mock.patch('ably.rest.auth.TokenRequest', - wraps=ably.types.tokenrequest.TokenRequest) as tr_mock: + with mock.patch('ably_pubsub.core.rest.auth.TokenRequest', + wraps=ably_pubsub.core.types.tokenrequest.TokenRequest) as tr_mock: auth_options = dict(self.ably.auth.auth_options.auth_options) auth_options['auth_headers'] = {'a_headers': 'a_value'} token_2 = await self.ably.auth.authorize( @@ -295,8 +290,8 @@ async def test_timestamp_is_not_stored(self): assert tr_mock.call_args[1]['timestamp'] == timestamp # call authorize again with no params - with mock.patch('ably.rest.auth.TokenRequest', - wraps=ably.types.tokenrequest.TokenRequest) as tr_mock: + with mock.patch('ably_pubsub.core.rest.auth.TokenRequest', + wraps=ably_pubsub.core.types.tokenrequest.TokenRequest) as tr_mock: token_4 = await self.ably.auth.authorize() assert isinstance(token_4, TokenDetails) assert token_2 != token_4 diff --git a/test/ably/rest/restcapability_test.py b/test/ably/rest/restcapability_test.py index c95c651d..3d66d087 100644 --- a/test/ably/rest/restcapability_test.py +++ b/test/ably/rest/restcapability_test.py @@ -1,7 +1,7 @@ import pytest -from ably.types.capability import Capability -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.capability import Capability +from ably_pubsub.core.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/restchannelhistory_test.py b/test/ably/rest/restchannelhistory_test.py index a9a2245b..c53c53bb 100644 --- a/test/ably/rest/restchannelhistory_test.py +++ b/test/ably/rest/restchannelhistory_test.py @@ -3,8 +3,8 @@ import pytest import respx -from ably import AblyException -from ably.http.paginatedresult import PaginatedResult +from ably_pubsub.core import AblyException +from ably_pubsub.core.http.paginatedresult import PaginatedResult from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/restchannelmutablemessages_test.py b/test/ably/rest/restchannelmutablemessages_test.py index b4f32ef4..c439365b 100644 --- a/test/ably/rest/restchannelmutablemessages_test.py +++ b/test/ably/rest/restchannelmutablemessages_test.py @@ -3,9 +3,9 @@ import pytest -from ably import AblyException, CipherParams, MessageAction -from ably.types.message import Message -from ably.types.operations import MessageOperation +from ably_pubsub.core import AblyException, CipherParams, MessageAction +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.types.operations import MessageOperation from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, assert_waiter diff --git a/test/ably/rest/restchannelpublish_test.py b/test/ably/rest/restchannelpublish_test.py index 41c2018b..963a377c 100644 --- a/test/ably/rest/restchannelpublish_test.py +++ b/test/ably/rest/restchannelpublish_test.py @@ -10,11 +10,11 @@ import msgpack import pytest -from ably import AblyException, IncompatibleClientIdException, api_version -from ably.rest.auth import Auth -from ably.types.message import Message -from ably.types.tokendetails import TokenDetails -from ably.util import case +from ably_pubsub.core import AblyException, IncompatibleClientIdException, api_version +from ably_pubsub.core.rest.auth import Auth +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.types.tokendetails import TokenDetails +from ably_pubsub.core.util import case from test.ably import utils from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, assert_waiter, dont_vary_protocol @@ -103,7 +103,7 @@ async def test_message_list_generate_one_request(self): expected_messages = [Message(f"name-{i}", str(i)) for i in range(3)] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish(messages=expected_messages) assert post_mock.call_count == 1 @@ -184,7 +184,7 @@ async def test_publish_message_null_name_and_data_keys_arent_sent(self): channel = self.ably.channels[ self.get_channel_name('persisted:null_name_and_data_keys_arent_sent_channel')] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish(name=None, data=None) @@ -244,7 +244,7 @@ async def test_publish_message_without_client_id_on_identified_client(self): channel = self.ably_with_client_id.channels[ self.get_channel_name('persisted:no_client_id_identified_client')] - with mock.patch('ably.rest.rest.Http.post', + with mock.patch('ably_pubsub.core.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: await channel.publish(name='publish', data='test') diff --git a/test/ably/rest/restchannels_test.py b/test/ably/rest/restchannels_test.py index b5e59957..339f685f 100644 --- a/test/ably/rest/restchannels_test.py +++ b/test/ably/rest/restchannels_test.py @@ -2,9 +2,9 @@ import pytest -from ably import AblyException -from ably.rest.channel import Channel, Channels, Presence -from ably.util.crypto import generate_random_key +from ably_pubsub.core import AblyException +from ably_pubsub.core.rest.channel import Channel, Channels, Presence +from ably_pubsub.core.util.crypto import generate_random_key from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/rest/restcrypto_test.py b/test/ably/rest/restcrypto_test.py index 94812b29..7aee1cea 100644 --- a/test/ably/rest/restcrypto_test.py +++ b/test/ably/rest/restcrypto_test.py @@ -6,9 +6,9 @@ import pytest from Crypto import Random -from ably import AblyException -from ably.types.message import Message -from ably.util.crypto import CipherParams, generate_random_key, get_cipher, get_default_params +from ably_pubsub.core import AblyException +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.util.crypto import CipherParams, generate_random_key, get_cipher, get_default_params from test.ably import utils from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, BaseTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/resthttp_test.py b/test/ably/rest/resthttp_test.py index 01bc6ba6..f339790e 100644 --- a/test/ably/rest/resthttp_test.py +++ b/test/ably/rest/resthttp_test.py @@ -9,10 +9,10 @@ import respx from httpx import Response -from ably import AblyRest -from ably.transport.defaults import Defaults -from ably.types.options import Options -from ably.util.exceptions import AblyException +from ably_pubsub.core import AblyRest +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.types.options import Options +from ably_pubsub.core.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase @@ -184,7 +184,7 @@ async def test_request_headers(self): # Agent assert 'Ably-Agent' in r.request.headers - expr = r"^ably-python\/\d+.\d+.\d+(-beta\.\d+)? python\/\d.\d+.\d+$" + expr = r"^ably-pubsub-python\/\d+.\d+.\d+(-beta\.\d+)? python\/\d.\d+.\d+$" assert re.search(expr, r.request.headers['Ably-Agent']) await ably.close() diff --git a/test/ably/rest/restinit_test.py b/test/ably/rest/restinit_test.py index 25e7c5af..04017fb7 100644 --- a/test/ably/rest/restinit_test.py +++ b/test/ably/rest/restinit_test.py @@ -3,9 +3,9 @@ import pytest from httpx import AsyncClient -from ably import AblyException, AblyRest -from ably.transport.defaults import Defaults -from ably.types.tokendetails import TokenDetails +from ably_pubsub.core import AblyException, AblyRest +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.types.tokendetails import TokenDetails from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol @@ -168,8 +168,8 @@ async def test_query_time_param(self): use_binary_protocol=self.use_binary_protocol) timestamp = ably.auth._timestamp - with patch('ably.rest.rest.AblyRest.time', wraps=ably.time) as server_time,\ - patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: + with patch('ably_pubsub.core.rest.rest.AblyRest.time', wraps=ably.time) as server_time,\ + patch('ably_pubsub.core.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: await ably.auth.request_token() assert local_time.call_count == 1 assert server_time.call_count == 1 diff --git a/test/ably/rest/restpaginatedresult_test.py b/test/ably/rest/restpaginatedresult_test.py index 9aa85689..6b6f4a4b 100644 --- a/test/ably/rest/restpaginatedresult_test.py +++ b/test/ably/rest/restpaginatedresult_test.py @@ -2,7 +2,7 @@ import respx from httpx import Response -from ably.http.paginatedresult import PaginatedResult +from ably_pubsub.core.http.paginatedresult import PaginatedResult from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase diff --git a/test/ably/rest/restpresence_test.py b/test/ably/rest/restpresence_test.py index 8767b0c6..48c284b4 100644 --- a/test/ably/rest/restpresence_test.py +++ b/test/ably/rest/restpresence_test.py @@ -3,8 +3,8 @@ import pytest import respx -from ably.http.paginatedresult import PaginatedResult -from ably.types.presence import PresenceMessage +from ably_pubsub.core.http.paginatedresult import PaginatedResult +from ably_pubsub.core.types.presence import PresenceMessage from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/restpush_test.py b/test/ably/rest/restpush_test.py index 867e8b90..57dd4789 100644 --- a/test/ably/rest/restpush_test.py +++ b/test/ably/rest/restpush_test.py @@ -5,8 +5,8 @@ import pytest -from ably import AblyAuthException, AblyException, DeviceDetails, PushChannelSubscription -from ably.http.paginatedresult import PaginatedResult +from ably_pubsub.core import AblyAuthException, AblyException, DeviceDetails, PushChannelSubscription +from ably_pubsub.core.http.paginatedresult import PaginatedResult from test.ably.testapp import TestApp from test.ably.utils import ( BaseAsyncTestCase, diff --git a/test/ably/rest/restrequest_test.py b/test/ably/rest/restrequest_test.py index 967da19e..f0ff20fc 100644 --- a/test/ably/rest/restrequest_test.py +++ b/test/ably/rest/restrequest_test.py @@ -2,9 +2,9 @@ import pytest import respx -from ably import AblyRest -from ably.http.paginatedresult import HttpPaginatedResponse -from ably.transport.defaults import Defaults +from ably_pubsub.core import AblyRest +from ably_pubsub.core.http.paginatedresult import HttpPaginatedResponse +from ably_pubsub.core.transport.defaults import Defaults from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/reststats_test.py b/test/ably/rest/reststats_test.py index cef28817..eecdfe8b 100644 --- a/test/ably/rest/reststats_test.py +++ b/test/ably/rest/reststats_test.py @@ -3,9 +3,9 @@ import pytest -from ably.http.paginatedresult import PaginatedResult -from ably.types.stats import Stats -from ably.util.exceptions import AblyException +from ably_pubsub.core.http.paginatedresult import PaginatedResult +from ably_pubsub.core.types.stats import Stats +from ably_pubsub.core.util.exceptions import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/resttime_test.py b/test/ably/rest/resttime_test.py index 4b78620a..e3ce2888 100644 --- a/test/ably/rest/resttime_test.py +++ b/test/ably/rest/resttime_test.py @@ -2,7 +2,7 @@ import pytest -from ably import AblyException +from ably_pubsub.core import AblyException from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol diff --git a/test/ably/rest/resttoken_test.py b/test/ably/rest/resttoken_test.py index 5052f1be..e58635cf 100644 --- a/test/ably/rest/resttoken_test.py +++ b/test/ably/rest/resttoken_test.py @@ -5,9 +5,9 @@ import pytest -from ably import AblyException, AblyRest, Capability -from ably.types.tokendetails import TokenDetails -from ably.types.tokenrequest import TokenRequest +from ably_pubsub.core import AblyException, AblyRest, Capability +from ably_pubsub.core.types.tokendetails import TokenDetails +from ably_pubsub.core.types.tokenrequest import TokenRequest from test.ably.testapp import TestApp from test.ably.utils import BaseAsyncTestCase, VaryByProtocolTestsMetaclass, dont_vary_protocol @@ -120,8 +120,8 @@ async def test_token_generation_with_invalid_ttl(self): async def test_token_generation_with_local_time(self): timestamp = self.ably.auth._timestamp - with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ - patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: + with patch('ably_pubsub.core.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ + patch('ably_pubsub.core.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: await self.ably.auth.request_token() assert local_time.called assert not server_time.called @@ -129,8 +129,8 @@ async def test_token_generation_with_local_time(self): # RSA10k async def test_token_generation_with_server_time(self): timestamp = self.ably.auth._timestamp - with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ - patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: + with patch('ably_pubsub.core.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ + patch('ably_pubsub.core.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: await self.ably.auth.request_token(query_time=True) assert local_time.call_count == 1 assert server_time.call_count == 1 @@ -182,8 +182,8 @@ async def test_key_name_and_secret_are_required(self): @dont_vary_protocol async def test_with_local_time(self): timestamp = self.ably.auth._timestamp - with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ - patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: + with patch('ably_pubsub.core.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ + patch('ably_pubsub.core.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: await self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret, query_time=False) assert local_time.called @@ -193,8 +193,8 @@ async def test_with_local_time(self): @dont_vary_protocol async def test_with_server_time(self): timestamp = self.ably.auth._timestamp - with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ - patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: + with patch('ably_pubsub.core.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ + patch('ably_pubsub.core.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: await self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret, query_time=True) assert local_time.call_count == 1 @@ -329,7 +329,7 @@ async def test_hmac(self): # AO2g @dont_vary_protocol async def test_query_server_time(self): - with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time: + with patch('ably_pubsub.core.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time: await self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret, query_time=True) assert server_time.call_count == 1 diff --git a/test/ably/testapp.py b/test/ably/testapp.py index f657fdd4..2d2a6864 100644 --- a/test/ably/testapp.py +++ b/test/ably/testapp.py @@ -2,12 +2,12 @@ import logging import os -from ably.realtime.realtime import AblyRealtime -from ably.rest.rest import AblyRest -from ably.transport.defaults import Defaults -from ably.types.capability import Capability -from ably.types.options import Options -from ably.util.exceptions import AblyException +from ably_pubsub.core.realtime.realtime import AblyRealtime +from ably_pubsub.core.rest.rest import AblyRest +from ably_pubsub.core.transport.defaults import Defaults +from ably_pubsub.core.types.capability import Capability +from ably_pubsub.core.types.options import Options +from ably_pubsub.core.util.exceptions import AblyException log = logging.getLogger(__name__) diff --git a/test/ably/utils.py b/test/ably/utils.py index ae19e0b5..368a8012 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -11,7 +11,7 @@ import respx from httpx import Response -from ably.http.http import Http +from ably_pubsub.core.http.http import Http class BaseTestCase: diff --git a/test/unit/annotation_test.py b/test/unit/annotation_test.py index 2cfb0bef..977d86cb 100644 --- a/test/unit/annotation_test.py +++ b/test/unit/annotation_test.py @@ -15,11 +15,11 @@ import pytest -from ably.rest.annotations import construct_validate_annotation, serial_from_msg_or_serial -from ably.types.annotation import Annotation, AnnotationAction -from ably.types.message import Message -from ably.util.crypto import generate_random_key, get_cipher -from ably.util.exceptions import AblyException +from ably_pubsub.core.rest.annotations import construct_validate_annotation, serial_from_msg_or_serial +from ably_pubsub.core.types.annotation import Annotation, AnnotationAction +from ably_pubsub.core.types.message import Message +from ably_pubsub.core.util.crypto import generate_random_key, get_cipher +from ably_pubsub.core.util.exceptions import AblyException # --- RSAN1a3: type validation --- diff --git a/test/unit/http_test.py b/test/unit/http_test.py index 61e0d35e..00d9aef8 100644 --- a/test/unit/http_test.py +++ b/test/unit/http_test.py @@ -1,4 +1,4 @@ -from ably import AblyRest +from ably_pubsub.core import AblyRest def test_http_get_rest_hosts_works_when_fallback_realtime_host_is_set(): diff --git a/test/unit/message_test.py b/test/unit/message_test.py index 4902d6b5..e3f663e8 100644 --- a/test/unit/message_test.py +++ b/test/unit/message_test.py @@ -1,4 +1,4 @@ -import ably.types.message +import ably_pubsub.core.types.message # TM2a, TM2c, TM2f @@ -14,7 +14,7 @@ def test_update_inner_message_fields_tm2(): } ] } - ably.types.message.Message.update_inner_message_fields(proto_msg) + ably_pubsub.core.types.message.Message.update_inner_message_fields(proto_msg) messages: list[dict] = proto_msg.get('messages') msg_index = 0 for msg in messages: @@ -37,7 +37,7 @@ def test_update_inner_message_fields_for_presence_msg_tm2(): } ] } - ably.types.message.Message.update_inner_message_fields(proto_msg) + ably_pubsub.core.types.message.Message.update_inner_message_fields(proto_msg) presence_messages: list[dict] = proto_msg.get('presence') msg_index = 0 for presence_msg in presence_messages: diff --git a/test/unit/mutable_message_test.py b/test/unit/mutable_message_test.py index 8ce603b9..86fe9062 100644 --- a/test/unit/mutable_message_test.py +++ b/test/unit/mutable_message_test.py @@ -1,5 +1,5 @@ -from ably import MessageAction, MessageOperation, MessageVersion, UpdateDeleteResult -from ably.types.message import Message +from ably_pubsub.core import MessageAction, MessageOperation, MessageVersion, UpdateDeleteResult +from ably_pubsub.core.types.message import Message def test_message_version_none_values_filtered(): @@ -137,7 +137,7 @@ def test_message_extras_none_excluded_from_as_dict(): # RSL15b, RTL32b, TM2u def test_message_annotations_preserved_in_as_dict(): """Test that annotations are included when a Message with annotations is serialized.""" - from ably.types.message import MessageAnnotations + from ably_pubsub.core.types.message import MessageAnnotations annotations = MessageAnnotations(summary={'reaction:distinct.v1': {'thumbsup': 5}}) message = Message( name='test', diff --git a/test/unit/options_test.py b/test/unit/options_test.py index d3ba6129..d349ade3 100644 --- a/test/unit/options_test.py +++ b/test/unit/options_test.py @@ -1,7 +1,7 @@ import pytest -from ably.types.options import Options -from ably.util.exceptions import AblyException +from ably_pubsub.core.types.options import Options +from ably_pubsub.core.util.exceptions import AblyException # REC1b1: endpoint is incompatible with deprecated options diff --git a/test/unit/pubsub_packaging_test.py b/test/unit/pubsub_packaging_test.py new file mode 100644 index 00000000..b71aa961 --- /dev/null +++ b/test/unit/pubsub_packaging_test.py @@ -0,0 +1,105 @@ +"""Invariants of the two distributions built from this repository. + +`ably_pubsub` is assembled at install time out of `ably-pubsub-core` and +`ably-pubsub-server`. That only holds together if the namespace stays a PEP 420 +namespace, if the two wheels never claim the same files, and if the versions +stay in lockstep with the server's exact pin on the core. None of those fail +anywhere closer to the mistake than a release, so they are asserted here. +""" + +import re +import shutil +import subprocess +import zipfile +from pathlib import Path + +import pytest + +import ably_pubsub.core +import ably_pubsub.server + +REPO_ROOT = Path(__file__).resolve().parents[2] +CORE_SRC = REPO_ROOT / 'core' / 'src' / 'ably_pubsub' +SERVER_SRC = REPO_ROOT / 'server' / 'src' / 'ably_pubsub' + + +def pyproject_field(path, field): + # Read rather than parse: tomllib only arrived in Python 3.11, and these are + # double-quoted scalars at the top of the [project] table. + match = re.search(rf'^{field} = "([^"]+)"', path.read_text(), re.MULTILINE) + assert match, f'no {field} in {path}' + return match.group(1) + + +# PEP 420: an __init__.py here would belong to whichever distribution shipped it, +# so the other one's subpackage would vanish when that distribution was removed +@pytest.mark.parametrize('src', [CORE_SRC, SERVER_SRC], ids=['core', 'server']) +def test_ably_pubsub_is_a_namespace_directory(src): + assert src.is_dir() + assert not (src / '__init__.py').exists() + + +def test_each_distribution_ships_only_its_own_subpackage(): + assert {p.name for p in CORE_SRC.iterdir()} == {'core'} + assert {p.name for p in SERVER_SRC.iterdir()} == {'server'} + + +def test_the_three_version_sites_agree(): + core_version = pyproject_field(REPO_ROOT / 'core' / 'pyproject.toml', 'version') + server_version = pyproject_field(REPO_ROOT / 'server' / 'pyproject.toml', 'version') + assert core_version == ably_pubsub.core.lib_version + assert server_version == ably_pubsub.core.lib_version + assert ably_pubsub.server.__version__ == ably_pubsub.core.lib_version + + +def test_the_server_pins_the_core_exactly_including_every_extra(): + pyproject = (REPO_ROOT / 'server' / 'pyproject.toml').read_text() + pins = set(re.findall(r'"ably-pubsub-core(?:\[[\w,]+\])?==([^"]+)"', pyproject)) + assert pins == {ably_pubsub.core.lib_version} + + +@pytest.mark.parametrize('extra', ['crypto', 'vcdiff', 'oldcrypto']) +def test_the_server_forwards_each_core_extra(extra): + pyproject = (REPO_ROOT / 'server' / 'pyproject.toml').read_text() + assert f'"ably-pubsub-core[{extra}]=={ably_pubsub.core.lib_version}"' in pyproject + + +def test_the_server_re_exports_everything_it_declares_public(): + missing = [name for name in ably_pubsub.server.__all__ if not hasattr(ably_pubsub.server, name)] + assert missing == [] + + +def test_the_server_declares_everything_it_re_exports(): + """Nothing pulled out of the core is public by accident and undocumented.""" + from_the_core = { + name for name, value in vars(ably_pubsub.server).items() + if not name.startswith('_') and name[0].isupper() + and getattr(value, '__module__', '').startswith('ably_pubsub.') + } + assert from_the_core + assert from_the_core <= set(ably_pubsub.server.__all__) + + +@pytest.mark.timeout(300) +@pytest.mark.skipif(shutil.which('uv') is None, reason='needs uv to build the distributions') +def test_the_two_wheels_do_not_overlap(tmp_path): + for package in ('ably-pubsub-core', 'ably-pubsub-server'): + subprocess.run( + ['uv', 'build', '--package', package, '--wheel', '--out-dir', str(tmp_path)], + cwd=REPO_ROOT, check=True, capture_output=True) + + wheels = {} + for wheel in tmp_path.glob('*.whl'): + with zipfile.ZipFile(wheel) as z: + wheels[wheel.name.split('-')[0]] = set(z.namelist()) + + assert set(wheels) == {'ably_pubsub_core', 'ably_pubsub_server'} + core, server = wheels['ably_pubsub_core'], wheels['ably_pubsub_server'] + + assert core & server == set() + # The generated sync flavour is not in version control, so a build that + # forgot to run unasync first would otherwise publish silently + assert any(name.startswith('ably_pubsub/core/sync/') for name in core) + assert 'ably_pubsub/__init__.py' not in core | server + assert all(name.startswith(('ably_pubsub/core/', 'ably_pubsub_core-')) for name in core) + assert all(name.startswith(('ably_pubsub/server/', 'ably_pubsub_server-')) for name in server) diff --git a/test/unit/pubsub_reexport_test.py b/test/unit/pubsub_reexport_test.py new file mode 100644 index 00000000..8d6ea966 --- /dev/null +++ b/test/unit/pubsub_reexport_test.py @@ -0,0 +1,93 @@ +"""The server package re-exports the core's public surface. + +`ably_pubsub.core` is an internal implementation package: consumers are meant to +reach only for `ably_pubsub.server`. So every type the core declares public has +to reach the server package too, or it is unreachable by supported means. +Trimming the surface to what a server actually needs is a later change (091d); +until then, a name that reaches the core and not the server is an omission, and +this catches it. +""" + +import ably_pubsub.core +import ably_pubsub.core.sync +import ably_pubsub.server +from ably_pubsub.server import sync as server_sync + +# ably_pubsub.core.sync is generated from ably_pubsub.core by unasync, so it +# carries a realtime client with the awaits stripped out of code that still +# calls asyncio. That is an artefact of the generation rather than a usable +# client — nothing tests it, and the sync entry point deliberately offers the +# HTTP client only. +SYNC_ONLY_BY_GENERATION = {'AblyRealtime'} + + +def exported_types(module): + """The classes a package offers, which is all the core's __init__ exports.""" + return {name for name in dir(module) if not name.startswith('_') and name[0].isupper()} + + +def test_the_server_re_exports_the_core_surface(): + assert exported_types(ably_pubsub.core) <= set(ably_pubsub.server.__all__) + + +def test_the_sync_door_re_exports_the_sync_core_surface(): + assert exported_types(ably_pubsub.core.sync) - SYNC_ONLY_BY_GENERATION <= set(server_sync.__all__) + + +# 3.x users reached for these under `ably.types.*` and `ably.http.*`, which have no +# supported 4.0 equivalent outside the internal core — so they have to be nameable +# from the server package or they are not nameable at all. Guarding the list keeps +# a future trim of the surface (091d) from dropping one silently. +REACHABLE_BY_A_CONSUMER = { + 'Channel', + 'ChannelDetails', + 'ChannelMetrics', + 'ChannelOccupancy', + 'ChannelState', + 'ChannelStateChange', + 'ChannelStatus', + 'Connection', + 'ConnectionEvent', + 'ConnectionState', + 'ConnectionStateChange', + 'HttpPaginatedResponse', + 'Message', + 'MessageAnnotations', + 'PaginatedResult', + 'Presence', + 'PresenceAction', + 'PresenceMessage', + 'RealtimeChannel', + 'RealtimePresence', + 'Stats', + 'TokenDetails', + 'TokenRequest', +} + +# The sync flavour has no realtime client, so the realtime object and state types +# have no counterpart there; unasync renames the rest. +SYNC_REACHABLE_BY_A_CONSUMER = (REACHABLE_BY_A_CONSUMER - { + 'Channel', + 'ChannelState', + 'ChannelStateChange', + 'Connection', + 'ConnectionEvent', + 'ConnectionState', + 'ConnectionStateChange', + 'HttpPaginatedResponse', + 'PaginatedResult', + 'RealtimeChannel', + 'RealtimePresence', +}) | {'ChannelSync', 'HttpPaginatedResponseSync', 'PaginatedResultSync'} + + +def test_the_types_a_consumer_names_are_reachable_from_the_server_package(): + assert REACHABLE_BY_A_CONSUMER <= set(ably_pubsub.server.__all__) + missing = [name for name in REACHABLE_BY_A_CONSUMER if not hasattr(ably_pubsub.server, name)] + assert missing == [] + + +def test_the_types_a_consumer_names_are_reachable_from_the_sync_door(): + assert SYNC_REACHABLE_BY_A_CONSUMER <= set(server_sync.__all__) + missing = [name for name in SYNC_REACHABLE_BY_A_CONSUMER if not hasattr(server_sync, name)] + assert missing == [] diff --git a/test/unit/pubsub_server_test.py b/test/unit/pubsub_server_test.py new file mode 100644 index 00000000..e750549b --- /dev/null +++ b/test/unit/pubsub_server_test.py @@ -0,0 +1,153 @@ +"""The `ably_pubsub.server` factory doors. + +The doors are the entry points of the server distribution: they construct the +core's clients unchanged, and they stamp the agent entry that declares the +server side. The header assertions here are what billing reads, so they are +written to fail loudly rather than to be forgiving. +""" + +import re + +import httpx +import pytest +import respx +from mock import MagicMock, patch +from websockets.exceptions import WebSocketException + +import ably_pubsub.core +from ably_pubsub.core import AblyRealtime, AblyRest +from ably_pubsub.core.sync import AblyRestSync +from ably_pubsub.core.transport.websockettransport import WebSocketTransport +from ably_pubsub.core.util.exceptions import AblyException +from ably_pubsub.server import ( + SERVER_AGENT_IDENTIFIER, + create_http_client, + create_realtime_client, +) +from ably_pubsub.server import sync as server_sync + +# The versionless side flag comes last, after the library and runtime entries. +# `ably-pubsub-server/` is the regression ably-js#2297 guards against: +# a `name/None` rendering would send a version the registry does not define. +SERVER_AGENT = re.compile(r'^ably-pubsub-python/\d+\.\d+\.\d+(\S*)? python/\S+ ably-pubsub-server$') + +TEST_KEY = 'name:secret' + + +def assert_declares_the_server_side(agent): + assert SERVER_AGENT.match(agent), agent + assert f'{SERVER_AGENT_IDENTIFIER}/' not in agent, agent + + +async def agent_header_of(client): + """The Ably-Agent an HTTP client puts on the wire.""" + with respx.mock: + route = respx.get(url__regex=r'.*/time').mock( + return_value=httpx.Response(200, json=[1234567890000])) + await client.time() + return route.calls.last.request.headers['Ably-Agent'] + + +def sync_agent_header_of(client): + with respx.mock: + route = respx.get(url__regex=r'.*/time').mock( + return_value=httpx.Response(200, json=[1234567890000])) + client.time() + return route.calls.last.request.headers['Ably-Agent'] + + +class TestFactories: + """The doors return the core's clients, and pass everything through.""" + + def test_clients_are_the_core_clients(self): + assert isinstance(create_http_client(token='foo'), AblyRest) + assert isinstance(create_realtime_client(token='foo', auto_connect=False), AblyRealtime) + assert isinstance(server_sync.create_http_client(token='foo'), AblyRestSync) + + def test_the_version_is_the_core_version(self): + assert ably_pubsub.server.__version__ == ably_pubsub.core.lib_version + + def test_options_are_passed_through(self): + client = create_http_client(key=TEST_KEY, client_id='me', tls=False) + assert client.options.key_name == 'name' + assert client.options.client_id == 'me' + assert client.options.tls is False + + def test_realtime_options_are_passed_through(self): + client = create_realtime_client(key=TEST_KEY, client_id='me', auto_connect=False) + assert client.options.key_name == 'name' + assert client.options.client_id == 'me' + + def test_sync_options_are_passed_through(self): + client = server_sync.create_http_client(key=TEST_KEY, client_id='me', tls=False) + assert client.options.key_name == 'name' + assert client.options.client_id == 'me' + assert client.options.tls is False + + def test_the_key_can_be_positional_as_on_the_constructor(self): + assert create_http_client(TEST_KEY).options.key_name == 'name' + assert create_realtime_client(TEST_KEY, auto_connect=False).options.key_name == 'name' + assert server_sync.create_http_client(TEST_KEY).options.key_name == 'name' + + def test_token_auth_is_passed_through(self): + assert create_http_client(token='foo').options.auth_token == 'foo' + + def test_authentication_is_still_required(self): + with pytest.raises(ValueError): + create_http_client() + + def test_everything_declared_public_is_importable(self): + for module in (ably_pubsub.server, server_sync): + missing = [name for name in module.__all__ if not hasattr(module, name)] + assert missing == [] + + +class TestAgentHeader: + + async def test_the_http_door_declares_the_server_side(self): + client = create_http_client(TEST_KEY) + assert_declares_the_server_side(await agent_header_of(client)) + await client.close() + + def test_the_sync_http_door_declares_the_server_side(self): + client = server_sync.create_http_client(TEST_KEY) + assert_declares_the_server_side(sync_agent_header_of(client)) + client.close() + + async def test_the_realtime_door_declares_the_server_side_on_the_handshake(self): + client = create_realtime_client(TEST_KEY, auto_connect=False) + connect = MagicMock(side_effect=WebSocketException('not connecting in a unit test')) + with patch('ably_pubsub.core.transport.websockettransport.ws_connect', connect): + transport = WebSocketTransport(client.connection.connection_manager, 'realtime.example', {}) + transport.connect() + # The connection cannot succeed here; what matters is the headers it + # handed to websockets before failing. + with pytest.raises(AblyException): + await transport.ws_connect_task + headers = connect.call_args.kwargs['additional_headers'] + assert_declares_the_server_side(headers['Ably-Agent']) + await client.close() + + async def test_the_core_client_alone_declares_no_side(self): + client = AblyRest(TEST_KEY) + agent = await agent_header_of(client) + assert re.match(r'^ably-pubsub-python/\d+\.\d+\.\d+(\S*)? python/\S+$', agent), agent + assert SERVER_AGENT_IDENTIFIER not in agent + await client.close() + + async def test_caller_supplied_agents_survive(self): + client = create_http_client(TEST_KEY, agents={'my-sdk': '1.0'}) + agent = await agent_header_of(client) + assert 'my-sdk/1.0' in agent + assert agent.endswith(f' {SERVER_AGENT_IDENTIFIER}') + await client.close() + + async def test_the_side_entry_cannot_be_overridden_by_the_caller(self): + client = create_http_client(TEST_KEY, agents={SERVER_AGENT_IDENTIFIER: 'x'}) + assert_declares_the_server_side(await agent_header_of(client)) + await client.close() + + def test_a_versionless_agent_renders_as_a_bare_flag(self): + from ably_pubsub.core.http.httputils import HttpUtils + agent = HttpUtils.default_headers(agents={'flag': None, 'versioned': '1.2'})['Ably-Agent'] + assert agent.endswith('flag versioned/1.2') diff --git a/uv.lock b/uv.lock index 30a4df76..1849fe9e 100644 --- a/uv.lock +++ b/uv.lock @@ -1,29 +1,47 @@ version = 1 -revision = 3 -requires-python = ">=3.7" +revision = 2 +requires-python = ">=3.8" resolution-markers = [ "python_full_version >= '3.10'", "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", - "python_full_version < '3.8'", + "python_full_version < '3.9'", ] -[[package]] -name = "ably" -version = "3.1.2" -source = { editable = "." } +[manifest] +members = [ + "ably-pubsub-core", + "ably-pubsub-server", +] + +[manifest.dependency-groups] +dev = [ + { name = "ably-pubsub-core", extras = ["crypto", "vcdiff"], editable = "core" }, + { name = "ably-pubsub-server", editable = "server" }, + { name = "importlib-metadata", specifier = ">=4.12,<5.0" }, + { name = "mock", specifier = ">=4.0.3,<5.0.0" }, + { name = "pytest", specifier = ">=7.1,<8.0" }, + { name = "pytest-asyncio", specifier = ">=0.23.0,<1.0.0" }, + { name = "pytest-cov", specifier = ">=2.4,<3.0" }, + { name = "pytest-timeout", specifier = ">=2.1.0,<3.0.0" }, + { name = "pytest-xdist", specifier = ">=1.15,<2.0" }, + { name = "respx", specifier = ">=0.22.0,<0.23.0" }, + { name = "ruff", specifier = ">=0.14.0,<1.0.0" }, + { name = "tokenize-rt" }, + { name = "vcdiff-decoder", specifier = ">=0.1.0a1" }, +] + +[[package]] +name = "ably-pubsub-core" +version = "4.0.0" +source = { editable = "core" } dependencies = [ { name = "h2", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "h2", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "httpx", version = "0.24.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "httpx", version = "0.28.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, - { name = "msgpack", version = "1.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "msgpack", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "httpx" }, + { name = "msgpack", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "msgpack", version = "1.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "pyee", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "pyee", version = "13.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, - { name = "websockets", version = "11.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "websockets", version = "13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pyee" }, + { name = "websockets", version = "13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "websockets", version = "15.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] @@ -31,24 +49,6 @@ dependencies = [ crypto = [ { name = "pycryptodome" }, ] -dev = [ - { name = "async-case", marker = "python_full_version < '3.8'" }, - { name = "importlib-metadata" }, - { name = "mock" }, - { name = "pytest" }, - { name = "pytest-asyncio", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "pytest-asyncio", version = "0.23.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, - { name = "pytest-cov" }, - { name = "pytest-timeout" }, - { name = "pytest-xdist" }, - { name = "respx", version = "0.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "respx", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, - { name = "ruff" }, - { name = "tokenize-rt", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "tokenize-rt", version = "6.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, - { name = "tokenize-rt", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "vcdiff-decoder" }, -] oldcrypto = [ { name = "pycrypto" }, ] @@ -58,65 +58,58 @@ vcdiff = [ [package.metadata] requires-dist = [ - { name = "async-case", marker = "python_full_version == '3.7.*' and extra == 'dev'", specifier = ">=10.1.0,<11.0.0" }, { name = "h2", specifier = ">=4.1.0,<5.0.0" }, - { name = "httpx", marker = "python_full_version == '3.7.*'", specifier = ">=0.24.1,<1.0" }, - { name = "httpx", marker = "python_full_version >= '3.8'", specifier = ">=0.25.0,<1.0" }, - { name = "importlib-metadata", marker = "extra == 'dev'", specifier = ">=4.12,<5.0" }, - { name = "mock", marker = "extra == 'dev'", specifier = ">=4.0.3,<5.0.0" }, + { name = "httpx", specifier = ">=0.25.0,<1.0" }, { name = "msgpack", specifier = ">=1.0.0,<2.0.0" }, { name = "pycrypto", marker = "extra == 'oldcrypto'", specifier = ">=2.6.1,<3.0.0" }, { name = "pycryptodome", marker = "extra == 'crypto'" }, - { name = "pyee", marker = "python_full_version == '3.7.*'", specifier = ">=9.0.4,<10.0.0" }, - { name = "pyee", marker = "python_full_version >= '3.8'", specifier = ">=11.1.0,<14.0.0" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.1,<8.0" }, - { name = "pytest-asyncio", marker = "python_full_version == '3.7.*' and extra == 'dev'", specifier = ">=0.21.0,<0.23.0" }, - { name = "pytest-asyncio", marker = "python_full_version >= '3.8' and extra == 'dev'", specifier = ">=0.23.0,<1.0.0" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=2.4,<3.0" }, - { name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.1.0,<3.0.0" }, - { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=1.15,<2.0" }, - { name = "respx", marker = "python_full_version == '3.7.*' and extra == 'dev'", specifier = ">=0.20.0,<0.21.0" }, - { name = "respx", marker = "python_full_version >= '3.8' and extra == 'dev'", specifier = ">=0.22.0,<0.23.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.0,<1.0.0" }, - { name = "tokenize-rt", marker = "extra == 'dev'" }, - { name = "vcdiff-decoder", marker = "extra == 'dev'", specifier = ">=0.1.0a1" }, + { name = "pyee", specifier = ">=11.1.0,<14.0.0" }, { name = "vcdiff-decoder", marker = "extra == 'vcdiff'", specifier = ">=0.1.0,<0.2.0" }, - { name = "websockets", marker = "python_full_version == '3.7.*'", specifier = ">=10.0,<12.0" }, { name = "websockets", marker = "python_full_version == '3.8.*'", specifier = ">=12.0,<15.0" }, { name = "websockets", marker = "python_full_version >= '3.9'", specifier = ">=15.0,<16.0" }, ] -provides-extras = ["oldcrypto", "crypto", "vcdiff", "dev"] +provides-extras = ["oldcrypto", "crypto", "vcdiff"] [[package]] -name = "anyio" -version = "3.7.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] +name = "ably-pubsub-server" +version = "4.0.0" +source = { editable = "server" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, - { name = "idna", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "sniffio", marker = "python_full_version < '3.8'" }, - { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "ably-pubsub-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/99/2dfd53fd55ce9838e6ff2d4dac20ce58263798bd1a0dbe18b3a9af3fcfce/anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780", size = 142927, upload-time = "2023-07-05T16:45:02.294Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5", size = 80896, upload-time = "2023-07-05T16:44:59.805Z" }, + +[package.optional-dependencies] +crypto = [ + { name = "ably-pubsub-core", extra = ["crypto"] }, +] +oldcrypto = [ + { name = "ably-pubsub-core", extra = ["oldcrypto"] }, +] +vcdiff = [ + { name = "ably-pubsub-core", extra = ["vcdiff"] }, ] +[package.metadata] +requires-dist = [ + { name = "ably-pubsub-core", editable = "core" }, + { name = "ably-pubsub-core", extras = ["crypto"], marker = "extra == 'crypto'", editable = "core" }, + { name = "ably-pubsub-core", extras = ["oldcrypto"], marker = "extra == 'oldcrypto'", editable = "core" }, + { name = "ably-pubsub-core", extras = ["vcdiff"], marker = "extra == 'vcdiff'", editable = "core" }, +] +provides-extras = ["oldcrypto", "crypto", "vcdiff"] + [[package]] name = "anyio" version = "4.5.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] dependencies = [ - { name = "exceptiongroup", marker = "python_full_version == '3.8.*'" }, - { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, - { name = "sniffio", marker = "python_full_version == '3.8.*'" }, - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, + { name = "idna", marker = "python_full_version < '3.9'" }, + { name = "sniffio", marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/9a7ce600ebe7804daf90d4d48b1c0510a4561ddce43a596be46676f82343/anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b", size = 171293, upload-time = "2024-10-13T22:18:03.307Z" } wheels = [ @@ -133,7 +126,7 @@ resolution-markers = [ ] dependencies = [ { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, - { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "idna", marker = "python_full_version >= '3.9'" }, { name = "sniffio", marker = "python_full_version >= '3.9'" }, { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, ] @@ -142,12 +135,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] -[[package]] -name = "async-case" -version = "10.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/09/87f2a23f5696ac6deb2fff92421f8af46226ea2410d101b453d5aa63e53a/async_case-10.1.0.tar.gz", hash = "sha256:b819f68c78f6c640ab1101ecf69fac189402b490901fa2abc314c48edab5d3da", size = 3668, upload-time = "2022-03-15T21:56:16.795Z" } - [[package]] name = "certifi" version = "2026.1.4" @@ -166,82 +153,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coverage" -version = "7.2.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575, upload-time = "2023-05-29T20:08:50.273Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", size = 200724, upload-time = "2023-05-29T20:07:03.422Z" }, - { url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", size = 201024, upload-time = "2023-05-29T20:07:05.694Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", size = 229528, upload-time = "2023-05-29T20:07:07.307Z" }, - { url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", size = 227842, upload-time = "2023-05-29T20:07:09.331Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", size = 228717, upload-time = "2023-05-29T20:07:11.38Z" }, - { url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", size = 234632, upload-time = "2023-05-29T20:07:13.376Z" }, - { url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", size = 232875, upload-time = "2023-05-29T20:07:15.093Z" }, - { url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", size = 234094, upload-time = "2023-05-29T20:07:17.013Z" }, - { url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", size = 203184, upload-time = "2023-05-29T20:07:18.69Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", size = 204096, upload-time = "2023-05-29T20:07:20.153Z" }, - { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895, upload-time = "2023-05-29T20:07:21.963Z" }, - { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120, upload-time = "2023-05-29T20:07:23.765Z" }, - { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178, upload-time = "2023-05-29T20:07:25.281Z" }, - { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754, upload-time = "2023-05-29T20:07:27.044Z" }, - { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558, upload-time = "2023-05-29T20:07:28.743Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509, upload-time = "2023-05-29T20:07:30.434Z" }, - { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924, upload-time = "2023-05-29T20:07:32.065Z" }, - { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977, upload-time = "2023-05-29T20:07:34.184Z" }, - { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168, upload-time = "2023-05-29T20:07:35.869Z" }, - { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185, upload-time = "2023-05-29T20:07:37.39Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020, upload-time = "2023-05-29T20:07:38.724Z" }, - { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994, upload-time = "2023-05-29T20:07:40.274Z" }, - { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358, upload-time = "2023-05-29T20:07:41.998Z" }, - { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316, upload-time = "2023-05-29T20:07:43.539Z" }, - { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159, upload-time = "2023-05-29T20:07:44.982Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127, upload-time = "2023-05-29T20:07:46.522Z" }, - { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833, upload-time = "2023-05-29T20:07:47.992Z" }, - { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463, upload-time = "2023-05-29T20:07:49.939Z" }, - { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347, upload-time = "2023-05-29T20:07:51.909Z" }, - { url = "https://files.pythonhosted.org/packages/80/d7/67937c80b8fd4c909fdac29292bc8b35d9505312cff6bcab41c53c5b1df6/coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f", size = 200580, upload-time = "2023-05-29T20:07:54.076Z" }, - { url = "https://files.pythonhosted.org/packages/7a/05/084864fa4bbf8106f44fb72a56e67e0cd372d3bf9d893be818338c81af5d/coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb", size = 226237, upload-time = "2023-05-29T20:07:56.28Z" }, - { url = "https://files.pythonhosted.org/packages/67/a2/6fa66a50e6e894286d79a3564f42bd54a9bd27049dc0a63b26d9924f0aa3/coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9", size = 224256, upload-time = "2023-05-29T20:07:58.189Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c0/73f139794c742840b9ab88e2e17fe14a3d4668a166ff95d812ac66c0829d/coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd", size = 225550, upload-time = "2023-05-29T20:08:00.383Z" }, - { url = "https://files.pythonhosted.org/packages/03/ec/6f30b4e0c96ce03b0e64aec46b4af2a8c49b70d1b5d0d69577add757b946/coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a", size = 232440, upload-time = "2023-05-29T20:08:02.495Z" }, - { url = "https://files.pythonhosted.org/packages/22/c1/2f6c1b6f01a0996c9e067a9c780e1824351dbe17faae54388a4477e6d86f/coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959", size = 230897, upload-time = "2023-05-29T20:08:04.382Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d6/53e999ec1bf7498ca4bc5f3b8227eb61db39068d2de5dcc359dec5601b5a/coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02", size = 232024, upload-time = "2023-05-29T20:08:06.031Z" }, - { url = "https://files.pythonhosted.org/packages/e9/40/383305500d24122dbed73e505a4d6828f8f3356d1f68ab6d32c781754b81/coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f", size = 203293, upload-time = "2023-05-29T20:08:07.598Z" }, - { url = "https://files.pythonhosted.org/packages/0e/bc/7e3a31534fabb043269f14fb64e2bb2733f85d4cf39e5bbc71357c57553a/coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0", size = 204040, upload-time = "2023-05-29T20:08:09.919Z" }, - { url = "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5", size = 200689, upload-time = "2023-05-29T20:08:11.594Z" }, - { url = "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5", size = 200986, upload-time = "2023-05-29T20:08:13.228Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9", size = 230648, upload-time = "2023-05-29T20:08:15.11Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e3/f552d5871943f747165b92a924055c5d6daa164ae659a13f9018e22f3990/coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6", size = 228511, upload-time = "2023-05-29T20:08:16.877Z" }, - { url = "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e", size = 229852, upload-time = "2023-05-29T20:08:18.47Z" }, - { url = "https://files.pythonhosted.org/packages/0d/31/340428c238eb506feb96d4fb5c9ea614db1149517f22cc7ab8c6035ef6d9/coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050", size = 235578, upload-time = "2023-05-29T20:08:20.298Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ce/97c1dd6592c908425622fe7f31c017d11cf0421729b09101d4de75bcadc8/coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5", size = 234079, upload-time = "2023-05-29T20:08:22.365Z" }, - { url = "https://files.pythonhosted.org/packages/de/a3/5a98dc9e239d0dc5f243ef5053d5b1bdcaa1dee27a691dfc12befeccf878/coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f", size = 234991, upload-time = "2023-05-29T20:08:24.974Z" }, - { url = "https://files.pythonhosted.org/packages/4a/fb/78986d3022e5ccf2d4370bc43a5fef8374f092b3c21d32499dee8e30b7b6/coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e", size = 203160, upload-time = "2023-05-29T20:08:26.701Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1c/6b3c9c363fb1433c79128e0d692863deb761b1b78162494abb9e5c328bc0/coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c", size = 204085, upload-time = "2023-05-29T20:08:28.146Z" }, - { url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", size = 200725, upload-time = "2023-05-29T20:08:29.851Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", size = 201022, upload-time = "2023-05-29T20:08:31.429Z" }, - { url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", size = 229102, upload-time = "2023-05-29T20:08:32.982Z" }, - { url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", size = 227441, upload-time = "2023-05-29T20:08:35.044Z" }, - { url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", size = 228265, upload-time = "2023-05-29T20:08:36.861Z" }, - { url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", size = 234217, upload-time = "2023-05-29T20:08:38.837Z" }, - { url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", size = 232466, upload-time = "2023-05-29T20:08:40.768Z" }, - { url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", size = 233669, upload-time = "2023-05-29T20:08:42.944Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", size = 203199, upload-time = "2023-05-29T20:08:44.734Z" }, - { url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", size = 204109, upload-time = "2023-05-29T20:08:46.417Z" }, - { url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", size = 193207, upload-time = "2023-05-29T20:08:48.153Z" }, -] - [[package]] name = "coverage" version = "7.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } wheels = [ @@ -539,8 +456,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } @@ -548,56 +464,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] -[[package]] -name = "execnet" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/c8/d382dc7a1e68a165f4a4ab612a08b20d8534a7d20cc590630b734ca0c54b/execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af", size = 161098, upload-time = "2023-07-09T17:14:03.468Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/9c/a079946da30fac4924d92dbc617e5367d454954494cf1e71567bcc4e00ee/execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41", size = 37097, upload-time = "2023-07-09T17:14:01.888Z" }, -] - [[package]] name = "execnet" version = "2.1.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] -[[package]] -name = "h11" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, -] - [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, @@ -608,8 +487,7 @@ name = "h2" version = "4.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", - "python_full_version < '3.8'", + "python_full_version < '3.9'", ] dependencies = [ { name = "hpack", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, @@ -642,8 +520,7 @@ name = "hpack" version = "4.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", - "python_full_version < '3.8'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/fda93fb4d957db19b0f6b370e79d586b3e8528b20252c729c476a2c02954/hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095", size = 49117, upload-time = "2020-08-30T10:35:57.868Z" } wheels = [ @@ -663,75 +540,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, ] -[[package]] -name = "httpcore" -version = "0.17.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "anyio", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "certifi", marker = "python_full_version < '3.8'" }, - { name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "sniffio", marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/ad/c98ecdbfe04417e71e143bf2f2fb29128e4787d78d1cedba21bd250c7e7a/httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888", size = 62676, upload-time = "2023-07-05T12:09:31.29Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/2c/2bde7ff8dd2064395555220cbf7cba79991172bf5315a07eb3ac7688d9f1/httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87", size = 74513, upload-time = "2023-07-05T12:09:29.425Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.8'" }, - { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "certifi" }, + { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httpx" -version = "0.24.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "certifi", marker = "python_full_version < '3.8'" }, - { name = "httpcore", version = "0.17.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "idna", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "sniffio", marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/2a/114d454cb77657dbf6a293e69390b96318930ace9cd96b51b99682493276/httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd", size = 81858, upload-time = "2023-05-19T00:50:56.678Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/91/e41f64f03d2a13aee7e8c819d82ee3aa7cdc484d18c0ae859742597d5aa0/httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd", size = 75377, upload-time = "2023-05-19T00:50:54.91Z" }, -] - [[package]] name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] dependencies = [ - { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "anyio", version = "4.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "certifi", marker = "python_full_version >= '3.8'" }, - { name = "httpcore", version = "1.0.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, - { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ @@ -743,8 +574,7 @@ name = "hyperframe" version = "6.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", - "python_full_version < '3.8'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/5a/2a/4747bff0a17f7281abe73e955d60d80aae537a5d203f417fa1c2e7578ebb/hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914", size = 25008, upload-time = "2021-04-17T12:11:22.757Z" } wheels = [ @@ -764,27 +594,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, ] -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, -] - [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, @@ -795,9 +608,7 @@ name = "importlib-metadata" version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "zipp", version = "3.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "zipp", version = "3.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/12/ab288357b884ebc807e3f4eff63ce5ba6b941ba61499071bf19f1bbc7f7f/importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d", size = 50445, upload-time = "2022-10-01T17:09:15.698Z" } @@ -805,25 +616,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/98/c277899f5aa21f6e6946e1c83f2af650cbfee982763ffb91db07ff7d3a13/importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116", size = 23010, upload-time = "2022-10-01T17:09:13.903Z" }, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, -] - [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ @@ -851,76 +650,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/03/b7e605db4a57c0f6fba744b11ef3ddf4ddebcada35022927a2b5fc623fdf/mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", size = 28536, upload-time = "2020-12-10T07:33:11.564Z" }, ] -[[package]] -name = "msgpack" -version = "1.0.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/a1/eba11a0d4b764bc62966a565b470f8c6f38242723ba3057e9b5098678c30/msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c", size = 127834, upload-time = "2023-03-08T17:50:48.564Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4a/36d936e54cf71e23ad276564465f6a54fb129e3d61520b76e13e0bb29167/msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9", size = 129738, upload-time = "2023-03-08T17:49:18.464Z" }, - { url = "https://files.pythonhosted.org/packages/f2/da/770118f8d48e11cc9a2c7cb60d7d3c8016266526bd42c6ff5bd21013d099/msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198", size = 74671, upload-time = "2023-03-08T17:49:20.311Z" }, - { url = "https://files.pythonhosted.org/packages/73/99/f338ce8b69e934c04e5d9187f85de1ae395882cd56e7deb48e78a1749af8/msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81", size = 70230, upload-time = "2023-03-08T17:49:21.958Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/bc7fdb75a35bf32c7c529c247dcadfd0502aac2309e207a89b0be6fe42ea/msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7", size = 309410, upload-time = "2023-03-08T17:49:23.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/67/f992ada3b42889f1b984e5651d63ea21ca3a92049cff6d75fe0a4a63e422/msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3", size = 316846, upload-time = "2023-03-08T17:49:24.786Z" }, - { url = "https://files.pythonhosted.org/packages/10/fe/9e004c4deb457f1ef1ad88c1188da5691ff1855e0d03a5ac3635ae1f6530/msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b", size = 311396, upload-time = "2023-03-08T17:49:26.075Z" }, - { url = "https://files.pythonhosted.org/packages/95/c9/560c3203c4327881c9f2de26c42dacdd9567bfe7fa43458e2a680c4bdcaf/msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c", size = 311165, upload-time = "2023-03-08T17:49:27.494Z" }, - { url = "https://files.pythonhosted.org/packages/10/ca/50c3a5e92d459a942169747315afd8c226d05427eccff903ddf33135c574/msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd", size = 348664, upload-time = "2023-03-08T17:49:28.736Z" }, - { url = "https://files.pythonhosted.org/packages/29/56/1fb6b96aab759ab3bc05b03ba6d936b350db72aac203cde56ea6bd001237/msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a", size = 316731, upload-time = "2023-03-08T17:49:30.041Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e9/b47f9e93fc381885624c40cbbbd0480b18ae11ca588162fe724d43495372/msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea", size = 57134, upload-time = "2023-03-08T17:49:31.365Z" }, - { url = "https://files.pythonhosted.org/packages/3c/e5/3d436bed11849ba05d777ed3fd1a0440170bad460335ea541dd6946047ed/msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a", size = 61631, upload-time = "2023-03-08T17:49:32.482Z" }, - { url = "https://files.pythonhosted.org/packages/27/ad/4edfe383ec3185611441179ffee8cbc8155d7575fbad73f6d31015e35451/msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0", size = 127502, upload-time = "2023-03-08T17:49:33.974Z" }, - { url = "https://files.pythonhosted.org/packages/c1/57/01f2d8805160f559ec21d095fc7576a26fbaed2475af24ce4a135c380c14/msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898", size = 73747, upload-time = "2023-03-08T17:49:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fe/8a7747ca57074307a2e8f1de58441952a9dbdf9e8a8e5873d53a5ce0835c/msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a", size = 69041, upload-time = "2023-03-08T17:49:36.44Z" }, - { url = "https://files.pythonhosted.org/packages/33/0a/aa7b53ae17cf1dc1c352d705ab3162fc572c55048cc3177c1a88009c47fd/msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a", size = 316114, upload-time = "2023-03-08T17:49:38.252Z" }, - { url = "https://files.pythonhosted.org/packages/6b/6d/de239d77d347f1990c41b4800075a15e06f748186dd120166270dd071734/msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705", size = 325080, upload-time = "2023-03-08T17:49:39.528Z" }, - { url = "https://files.pythonhosted.org/packages/7e/1c/9d0fd241a4e88e1cd2f5babea4a27ac25b1b86dbbc05fa10741e82079a93/msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d", size = 319393, upload-time = "2023-03-08T17:49:40.755Z" }, - { url = "https://files.pythonhosted.org/packages/b8/bc/1d5fe4732dc78ff86aaf677596da08f0ae736e60ca8ab49c1f1c7366cb1a/msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9", size = 316118, upload-time = "2023-03-08T17:49:41.964Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e9/c79ecc36cfa34d850a01773565e0fccafd69efff07172028c3a5f758b83f/msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7", size = 354984, upload-time = "2023-03-08T17:49:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/45/85/6b55b0cabad846d3e730226a897f878f8f63ee505668bb6c55a697b0bfb0/msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed", size = 323580, upload-time = "2023-03-08T17:49:44.71Z" }, - { url = "https://files.pythonhosted.org/packages/0e/69/3d10e741dd2bbb806af5cdc76551735baab5f5f9773701eb05502c913a6e/msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c", size = 56419, upload-time = "2023-03-08T17:49:46.603Z" }, - { url = "https://files.pythonhosted.org/packages/6b/79/0dec8f035160464ca88b221cc79691a71cf88dc25207c17f1d918b2c7bb0/msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2", size = 60781, upload-time = "2023-03-08T17:49:47.912Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c1/1b591574ba71481fbf38359a8fca5108e4ad130a6dbb9b2acb3e9277d0fe/msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c", size = 72520, upload-time = "2023-03-08T17:50:02.199Z" }, - { url = "https://files.pythonhosted.org/packages/62/57/170af6c6fccd2d950ea01e1faa58cae9643226fa8705baded11eca3aa8b5/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b", size = 289288, upload-time = "2023-03-08T17:50:04.213Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c4/f2c8695ae69d1425eddc5e2f849c525b562dc8409bc2979e525f3dd4fecd/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f", size = 299695, upload-time = "2023-03-08T17:50:05.622Z" }, - { url = "https://files.pythonhosted.org/packages/62/5c/9c7fed4ca0235a2d7b8d15b4047c328976b97d2b227719e54cad1e47c244/msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f", size = 293149, upload-time = "2023-03-08T17:50:06.915Z" }, - { url = "https://files.pythonhosted.org/packages/ef/13/c110d89d5079169354394dc226e6f84d818722939bc1fe3f9c25f982e903/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d", size = 292899, upload-time = "2023-03-08T17:50:08.215Z" }, - { url = "https://files.pythonhosted.org/packages/72/ac/2eda5af7cd1450c52d031e48c76b280eac5bb2e588678876612f95be34ab/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086", size = 334408, upload-time = "2023-03-08T17:50:10Z" }, - { url = "https://files.pythonhosted.org/packages/e8/60/78906f564804aae23eb1102eca8b8830f1e08a649c179774c05fa7dc0aad/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf", size = 302791, upload-time = "2023-03-08T17:50:11.414Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b8/89cb1809b076a4651169851aa1f98128b75cbfe14034b914c9040b13c4cf/msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77", size = 57095, upload-time = "2023-03-08T17:50:12.741Z" }, - { url = "https://files.pythonhosted.org/packages/e8/1f/be19c9c9cfdcc2ae8ee8c65dbe5f281cc1f3331f9b9523735f39b090b448/msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82", size = 62112, upload-time = "2023-03-08T17:50:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f7/df5814697c25bdebb14ea97d27ddca04f5d4c6e249f096d086fea521c139/msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c", size = 126923, upload-time = "2023-03-08T17:50:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/33/52/099f0dde1283bac7bf267ab941dfa3b7c89ee701e4252973f8d3c10e68d6/msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d", size = 73246, upload-time = "2023-03-08T17:50:16.487Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1f/cc3e8274934c8323f6106dae22cba8bad413166f4efb3819573de58c215c/msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb", size = 68947, upload-time = "2023-03-08T17:50:17.767Z" }, - { url = "https://files.pythonhosted.org/packages/19/0c/2c3b443df88f5d400f2e19a3d867564d004b26e137f18c2f2663913987bc/msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba", size = 313568, upload-time = "2023-03-08T17:50:19.016Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/bc319ba061f6dc9077745988be288705b3f9f18c5a209772a3e8fcd419fd/msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1", size = 322443, upload-time = "2023-03-08T17:50:20.341Z" }, - { url = "https://files.pythonhosted.org/packages/2f/21/e488871f8e498efe14821b0c870eb95af52cfafb9b8dd41d83fad85b383b/msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87", size = 315490, upload-time = "2023-03-08T17:50:22.301Z" }, - { url = "https://files.pythonhosted.org/packages/28/8f/c58c53c884217cc572c19349c7e1129b5a6eae36df0a017aae3a8f3d7aa8/msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb", size = 324288, upload-time = "2023-03-08T17:50:23.739Z" }, - { url = "https://files.pythonhosted.org/packages/0d/90/44edef4a8c6f035b054c4b017c5adcb22a35ec377e17e50dd5dced279a6b/msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48", size = 361405, upload-time = "2023-03-08T17:50:24.992Z" }, - { url = "https://files.pythonhosted.org/packages/56/50/bfcc0fad07067b6f1b09d940272ec749d5fe82570d938c2348c3ad0babf7/msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0", size = 329585, upload-time = "2023-03-08T17:50:26.387Z" }, - { url = "https://files.pythonhosted.org/packages/80/f0/c1fadb4e4a38fda19e35b1b6f887d72cc9c57778af43b53f64a8cd62e922/msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e", size = 57668, upload-time = "2023-03-08T17:50:28.037Z" }, - { url = "https://files.pythonhosted.org/packages/da/46/855bdcbf004fd87b6a4451e8dcd61329439dcd9039887f71ca5085769216/msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1", size = 62509, upload-time = "2023-03-08T17:50:29.85Z" }, - { url = "https://files.pythonhosted.org/packages/12/6e/0cfd1dc07f61a6ac606587a393f489c3ca463469d285a73c8e5e2f61b021/msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025", size = 130498, upload-time = "2023-03-08T17:50:31.551Z" }, - { url = "https://files.pythonhosted.org/packages/4b/3d/cc5eb6d69e0ecde80a78cc42f48579971ec333e509d56a4a6de1a2c40ba2/msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5", size = 75178, upload-time = "2023-03-08T17:50:32.78Z" }, - { url = "https://files.pythonhosted.org/packages/bf/68/032e62ad44f92ba6a4ae7c45054843cdec7f0c405ecdfd166f25123b0c47/msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd", size = 70460, upload-time = "2023-03-08T17:50:34.57Z" }, - { url = "https://files.pythonhosted.org/packages/49/57/a28120d82f8e77622a1e1efc652389c71145f6b89b47b39814a7c6038373/msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437", size = 313499, upload-time = "2023-03-08T17:50:36.329Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ff/ca74e519c47139b6c08fb21db5ead2bd2eed6cb1225f9be69390cdb48182/msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f", size = 322301, upload-time = "2023-03-08T17:50:38.097Z" }, - { url = "https://files.pythonhosted.org/packages/43/87/6507d56f62b958d822ae4ffe1c4507ed7d3cf37ad61114665816adcf4adc/msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282", size = 316630, upload-time = "2023-03-08T17:50:39.397Z" }, - { url = "https://files.pythonhosted.org/packages/67/f8/e3ab674f4a945308362e9342297fe6b35a89dd0f648aa325aabffa5dc210/msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d", size = 316251, upload-time = "2023-03-08T17:50:41.153Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f1/45b73a9e97f702bcb5f51569b93990e456bc969363e55122374c22ed7d24/msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8", size = 352781, upload-time = "2023-03-08T17:50:42.435Z" }, - { url = "https://files.pythonhosted.org/packages/17/10/be97811782473d709d07b65a3955a5a76d47686aff3d62bb41d48aea7c92/msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11", size = 321996, upload-time = "2023-03-08T17:50:43.726Z" }, - { url = "https://files.pythonhosted.org/packages/18/3f/3860151fbdf50e369bbe4ffd307a588417669c725025e383f3ce5893690f/msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc", size = 57827, upload-time = "2023-03-08T17:50:45.517Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/3ca00fb1e53bcacf8c186fa6aff2d2086862b12e289bcf38227d9d40bd86/msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164", size = 62775, upload-time = "2023-03-08T17:50:47.305Z" }, -] - [[package]] name = "msgpack" version = "1.1.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } wheels = [ @@ -1057,53 +792,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/a7/b1992b4fb3da3b413f5fb78a63bad42f256c3be2352eb69273c3789c2c96/msgpack-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:67016ae8c8965124fdede9d3769528ad8284f14d635337ffa6a713a580f6c030", size = 71540, upload-time = "2025-10-08T09:15:55.573Z" }, ] -[[package]] -name = "packaging" -version = "24.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882, upload-time = "2024-03-10T09:39:28.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488, upload-time = "2024-03-10T09:39:25.947Z" }, -] - [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "pluggy" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613, upload-time = "2023-06-21T09:12:28.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695, upload-time = "2023-06-21T09:12:27.397Z" }, -] - [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ @@ -1178,32 +881,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/62/2392b7879f4d2c1bfa20815720b89d464687877851716936b9609959c201/pycryptodome-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ce64e84a962b63a47a592690bdc16a7eaf709d2c2697ababf24a0def566899a6", size = 1802461, upload-time = "2025-05-17T17:21:41.722Z" }, ] -[[package]] -name = "pyee" -version = "9.1.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2c/ebe4fd8213b3d720b193a62f07169607e945dd02a08edc45b28ca52fbe07/pyee-9.1.1.tar.gz", hash = "sha256:a1ebcd38d92e7d780635ab3442c0f6ce49840d9bfe0b0549921a6188860af0db", size = 22634, upload-time = "2023-06-09T06:13:29.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/53/39b67ce3841a5bb2d444f64ed969fb79ebd5bfed6867c3f88f3916407270/pyee-9.1.1-py2.py3-none-any.whl", hash = "sha256:f4a9853503d2f5a69d4350b54ba70841ebc535c53ebfaaa40c0fb47e63e78b3e", size = 15073, upload-time = "2023-06-09T06:13:27.255Z" }, -] - [[package]] name = "pyee" version = "13.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] dependencies = [ - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } @@ -1218,50 +901,24 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, - { name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "packaging", version = "25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, - { name = "pluggy", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "packaging" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "tomli", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" }, ] -[[package]] -name = "pytest-asyncio" -version = "0.21.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "pytest", marker = "python_full_version < '3.8'" }, - { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/53/57663d99acaac2fcdafdc697e52a9b1b7d6fcf36616281ff9768a44e7ff3/pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45", size = 30656, upload-time = "2024-04-29T13:23:24.738Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/ce/1e4b53c213dce25d6e8b163697fbce2d43799d76fa08eea6ad270451c370/pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b", size = 13368, upload-time = "2024-04-29T13:23:23.126Z" }, -] - [[package]] name = "pytest-asyncio" version = "0.23.8" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] dependencies = [ - { name = "pytest", marker = "python_full_version >= '3.8'" }, + { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/b4/0b378b7bf26a8ae161c3890c0b48a91a04106c5713ce81b4b080ea2f4f18/pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3", size = 46920, upload-time = "2024-07-17T17:39:34.617Z" } wheels = [ @@ -1273,8 +930,7 @@ name = "pytest-cov" version = "2.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "coverage", version = "7.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest" }, @@ -1315,8 +971,7 @@ name = "pytest-xdist" version = "1.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "execnet", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "execnet", version = "2.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "execnet" }, { name = "pytest" }, { name = "pytest-forked" }, { name = "six" }, @@ -1326,32 +981,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/fc/30821e7799bddd56989523ee003cde488c6e6053dfd29ba07db2ba934a04/pytest_xdist-1.34.0-py2.py3-none-any.whl", hash = "sha256:ba5d10729372d65df3ac150872f9df5d2ed004a3b0d499cc0164aafedd8c7b66", size = 36841, upload-time = "2020-07-27T23:05:23.851Z" }, ] -[[package]] -name = "respx" -version = "0.20.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -dependencies = [ - { name = "httpx", version = "0.24.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e0df26ea5c7145d95f1ab8ecb20f0778dd8af718e56747977dca9d28362a/respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643", size = 26080, upload-time = "2023-07-20T23:01:23.618Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/47/8c5a8b02c2144770fe353585b6db21e392c4318b8cff897738159feff562/respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9", size = 22849, upload-time = "2023-07-20T23:01:21.994Z" }, -] - [[package]] name = "respx" version = "0.22.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] dependencies = [ - { name = "httpx", version = "0.28.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "httpx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/7c/96bd0bc759cf009675ad1ee1f96535edcb11e9666b985717eb8c87192a95/respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91", size = 28439, upload-time = "2024-12-19T22:33:59.374Z" } wheels = [ @@ -1402,24 +1037,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] -[[package]] -name = "tokenize-rt" -version = "5.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/40/01/fb40ea8c465f680bf7aa3f5bee39c62ba8b7f52c38048c27aa95aff4f779/tokenize_rt-5.0.0.tar.gz", hash = "sha256:3160bc0c3e8491312d0485171dea861fc160a240f5f5766b72a1165408d10740", size = 5329, upload-time = "2022-10-03T23:28:00.861Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/12/4c7495f25b4c9131706f3aaffb185d4de32c02a6ee49d875e929c5b7c919/tokenize_rt-5.0.0-py2.py3-none-any.whl", hash = "sha256:c67772c662c6b3dc65edf66808577968fb10badfc2042e3027196bed4daf9e5a", size = 5848, upload-time = "2022-10-03T23:27:59.459Z" }, -] - [[package]] name = "tokenize-rt" version = "6.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/7d/09/6257dabdeab5097d72c5d874f29b33cd667ec411af6667922d84f85b79b5/tokenize_rt-6.0.0.tar.gz", hash = "sha256:b9711bdfc51210211137499b5e355d3de5ec88a85d2025c520cbb921b5194367", size = 5360, upload-time = "2024-08-04T21:01:19.405Z" } wheels = [ @@ -1448,27 +1071,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] -[[package]] -name = "tomli" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164, upload-time = "2022-02-08T10:54:04.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757, upload-time = "2022-02-08T10:54:02.017Z" }, -] - [[package]] name = "tomli" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version == '3.8.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, @@ -1514,24 +1120,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] -[[package]] -name = "typing-extensions" -version = "4.7.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876, upload-time = "2023-07-02T14:20:55.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232, upload-time = "2023-07-02T14:20:53.275Z" }, -] - [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ @@ -1560,92 +1154,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/d5/0d1153f2dbaa02a11b2491d26b59f08e203409dacb91853c26c13bc28cb6/vcdiff_decoder-0.1.0-py3-none-any.whl", hash = "sha256:42f4e3d77b3bd4be881853858ee471a11d6a474fda375482d589b8576b91318f", size = 26333, upload-time = "2025-09-19T17:15:13.611Z" }, ] -[[package]] -name = "websockets" -version = "11.0.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/3b/2ed38e52eed4cf277f9df5f0463a99199a04d9e29c9e227cfafa57bd3993/websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016", size = 104235, upload-time = "2023-05-07T14:25:20.083Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/76/88640f8aeac7eb0d058b913e7bb72682f8d569db44c7d30e576ec4777ce1/websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac", size = 123714, upload-time = "2023-05-07T14:23:15.309Z" }, - { url = "https://files.pythonhosted.org/packages/b9/6b/26b28115b46e23e74ede76d95792eedfe8c58b21f4daabfff1e9f159c8fe/websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d", size = 120949, upload-time = "2023-05-07T14:23:17.656Z" }, - { url = "https://files.pythonhosted.org/packages/f3/82/2d1f3395d47fab65fa8b801e2251b324300ed8db54753b6fb7919cef0c11/websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f", size = 121032, upload-time = "2023-05-07T14:23:20.096Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ec/56bdd12d847e4fc2d0a7ba2d7f1476f79cda50599d11ffb6080b86f21ef1/websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564", size = 130620, upload-time = "2023-05-07T14:23:21.545Z" }, - { url = "https://files.pythonhosted.org/packages/c9/fb/ae5ed4be3514287cf8f6c348c87e1392a6e3f4d6eadae75c18847a2f84b6/websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11", size = 129628, upload-time = "2023-05-07T14:23:23.105Z" }, - { url = "https://files.pythonhosted.org/packages/58/0a/7570e15661a0a546c3a1152d95fe8c05480459bab36247f0acbf41f01a41/websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca", size = 129938, upload-time = "2023-05-07T14:23:24.959Z" }, - { url = "https://files.pythonhosted.org/packages/ba/6c/5c0322b2875e8395e6bf0eff11f43f3e25da7ef5b12f4d908cd3a19ea841/websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54", size = 134663, upload-time = "2023-05-07T14:23:26.382Z" }, - { url = "https://files.pythonhosted.org/packages/de/0e/d7274e4d41d7b34f204744c27a23707be2ecefaf6f7df2145655f086ecd7/websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4", size = 133900, upload-time = "2023-05-07T14:23:28.307Z" }, - { url = "https://files.pythonhosted.org/packages/82/3c/00f051abcf88aec5e952a8840076749b0b26a30c219dcae8ba70200998aa/websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526", size = 134520, upload-time = "2023-05-07T14:23:30.734Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7b/4d4ecd29be7d08486e38f987a6603c491296d1e33fe55127d79aebb0333e/websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69", size = 124152, upload-time = "2023-05-07T14:23:33.183Z" }, - { url = "https://files.pythonhosted.org/packages/98/a7/0ed69892981351e5acf88fac0ff4c801fabca2c3bdef9fca4c7d3fde8c53/websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f", size = 124674, upload-time = "2023-05-07T14:23:35.331Z" }, - { url = "https://files.pythonhosted.org/packages/16/49/ae616bd221efba84a3d78737b417f704af1ffa36f40dcaba5eb954dd4753/websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb", size = 123748, upload-time = "2023-05-07T14:23:37.977Z" }, - { url = "https://files.pythonhosted.org/packages/0a/84/68b848a373493b58615d6c10e9e8ccbaadfd540f84905421739a807704f8/websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288", size = 120975, upload-time = "2023-05-07T14:23:40.339Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a8/e81533499f84ef6cdd95d11d5b05fa827c0f097925afd86f16e6a2631d8e/websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d", size = 121017, upload-time = "2023-05-07T14:23:41.874Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ca/65d6986665888494eca4d5435a9741c822022996f0f4200c57ce4b9242f7/websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3", size = 131200, upload-time = "2023-05-07T14:23:43.309Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/a8a582ebeeecc8b5f332997d44c57e241748f8a9856e06a38a5a13b30796/websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b", size = 130195, upload-time = "2023-05-07T14:23:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5e/b25c60067d700e811dccb4e3c318eeadd3a19d8b3620de9f97434af777a7/websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6", size = 130569, upload-time = "2023-05-07T14:23:46.926Z" }, - { url = "https://files.pythonhosted.org/packages/14/fc/5cbbf439c925e1e184a0392ec477a30cee2fabc0e63807c1d4b6d570fb52/websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97", size = 136015, upload-time = "2023-05-07T14:23:48.43Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d8/a997d3546aef9cc995a1126f7d7ade96c0e16c1a0efb9d2d430aee57c925/websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf", size = 135292, upload-time = "2023-05-07T14:23:50.744Z" }, - { url = "https://files.pythonhosted.org/packages/89/8f/707a05d5725f956c78d252a5fd73b89fa3ac57dd3959381c2d1acb41cb13/websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd", size = 135890, upload-time = "2023-05-07T14:23:52.707Z" }, - { url = "https://files.pythonhosted.org/packages/b5/94/ac47552208583d5dbcce468430c1eb2ae18962f6b3a694a2b7727cc60d4a/websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c", size = 124149, upload-time = "2023-05-07T14:23:53.848Z" }, - { url = "https://files.pythonhosted.org/packages/e1/7c/0ad6e7ef0a054d73092f616d20d3d9bd3e1b837554cb20a52d8dd9f5b049/websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8", size = 124670, upload-time = "2023-05-07T14:23:55.812Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a8/8900184ab0b06b6e620ba7e92cf2faa5caa9ba86e148541b8fff1c7b6646/websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152", size = 120868, upload-time = "2023-05-07T14:23:57.24Z" }, - { url = "https://files.pythonhosted.org/packages/44/a8/66c3a66b70b01a6c55fde486298766177fa11dd0d3a2c1cfc6820f25b4dc/websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f", size = 130557, upload-time = "2023-05-07T14:23:59.274Z" }, - { url = "https://files.pythonhosted.org/packages/70/fc/71377f36ef3049f3bc7db7c0f3a7696929d5f836d7a18777131d994192a9/websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b", size = 129640, upload-time = "2023-05-07T14:24:01.412Z" }, - { url = "https://files.pythonhosted.org/packages/36/19/0da435afb26a6c47c0c045a82e414912aa2ac10de5721276a342bd9fdfee/websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb", size = 129903, upload-time = "2023-05-07T14:24:02.872Z" }, - { url = "https://files.pythonhosted.org/packages/38/ed/b8b133416536b6816e480594864e5950051db522714623eefc9e5275ec04/websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007", size = 135302, upload-time = "2023-05-07T14:24:04.326Z" }, - { url = "https://files.pythonhosted.org/packages/e9/26/1dfaa81788f61c485b4d65f1b28a19615e39f9c45100dce5e2cbf5ad1352/websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0", size = 134562, upload-time = "2023-05-07T14:24:05.829Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f7/1e852351e8073c32885172a6bef64c95d14c13ff3634b01d4a1086321491/websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af", size = 135191, upload-time = "2023-05-07T14:24:07.659Z" }, - { url = "https://files.pythonhosted.org/packages/19/d3/2ea3f95d83033675144b0848a0ae2e4998b3f763da09ec3df6bce97ea4e6/websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f", size = 124138, upload-time = "2023-05-07T14:24:09.697Z" }, - { url = "https://files.pythonhosted.org/packages/94/8c/266155c14b7a26deca6fa4c4d5fd15b0ab32725d78a2acfcf6b24943585d/websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de", size = 124672, upload-time = "2023-05-07T14:24:12.595Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/60eccd7e9703bbe93fc4167d1e7ada7e8e8e51544122198d63fd8e3460b7/websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0", size = 123706, upload-time = "2023-05-07T14:24:14.633Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ec/7e2b9bebc2e9b4a48404144106bbc6a7ace781feeb0e6a3829551e725fa5/websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae", size = 120944, upload-time = "2023-05-07T14:24:16.144Z" }, - { url = "https://files.pythonhosted.org/packages/8a/bd/a5e5973899d78d44a540f50a9e30b01c6771e8bf7883204ee762060cf95a/websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99", size = 121030, upload-time = "2023-05-07T14:24:17.905Z" }, - { url = "https://files.pythonhosted.org/packages/ec/3f/0c5cae14e9e86401105833383405787ae4caddd476a8fc5561259253dab7/websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa", size = 130811, upload-time = "2023-05-07T14:24:21.175Z" }, - { url = "https://files.pythonhosted.org/packages/a0/39/acc3d4b15c5207ef7cca823c37eca8c74e3e1a1a63a397798986be3bdef7/websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86", size = 129876, upload-time = "2023-05-07T14:24:22.498Z" }, - { url = "https://files.pythonhosted.org/packages/58/05/2efb520317340ece74bfc4d88e8f011dd71a4e6c263000bfffb71a343685/websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c", size = 130158, upload-time = "2023-05-07T14:24:25.193Z" }, - { url = "https://files.pythonhosted.org/packages/30/a5/d641f2a9a4b4079cfddbb0726fc1b914be76a610aaedb45e4760899a4ce1/websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0", size = 134494, upload-time = "2023-05-07T14:24:26.463Z" }, - { url = "https://files.pythonhosted.org/packages/ca/20/25211be61d50189650fb0ec6084b6d6339f5c7c6436a6c217608dcb553e4/websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e", size = 133735, upload-time = "2023-05-07T14:24:29.598Z" }, - { url = "https://files.pythonhosted.org/packages/c6/91/f36454b87edf10a95be9c7212d2dcb8c606ddbf7a183afdc498933acdd19/websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788", size = 134368, upload-time = "2023-05-07T14:24:30.923Z" }, - { url = "https://files.pythonhosted.org/packages/58/68/9403771de1b1c21a2e878e4841815af8c9f8893b094654934e2a5ee4dbc8/websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74", size = 124148, upload-time = "2023-05-07T14:24:32.299Z" }, - { url = "https://files.pythonhosted.org/packages/25/25/48540419005d07ed2d368a7eafb44ed4f33a2691ae4c210850bf31123c4a/websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f", size = 124665, upload-time = "2023-05-07T14:24:34.482Z" }, - { url = "https://files.pythonhosted.org/packages/c0/21/cb9dfbbea8dc0ad89ced52630e7e61edb425fb9fdc6002f8d0c5dd26b94b/websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8", size = 123707, upload-time = "2023-05-07T14:24:36.007Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f2/8a3eb016be19743c7eb9e67c855df0fdfa5912534ffaf83a05b62667d761/websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd", size = 120963, upload-time = "2023-05-07T14:24:37.478Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1a/3da73e69ebc00649d11ed836541c92c1a2df0b8a8aa641a2c8746e7c2b9c/websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016", size = 121014, upload-time = "2023-05-07T14:24:39.009Z" }, - { url = "https://files.pythonhosted.org/packages/d9/36/5741e62ccf629c8e38cc20f930491f8a33ce7dba972cae93dba3d6f02552/websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61", size = 130408, upload-time = "2023-05-07T14:24:40.825Z" }, - { url = "https://files.pythonhosted.org/packages/66/89/799f595c67b97a8a17e13d2764e088f631616bd95668aaa4c04b7cada136/websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b", size = 129407, upload-time = "2023-05-07T14:24:42.479Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9c/2356ecb952fd3992b73f7a897d65e57d784a69b94bb8d8fd5f97531e5c02/websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd", size = 129712, upload-time = "2023-05-07T14:24:44.186Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f5/15998b164c183af0513bba744b51ecb08d396ff86c0db3b55d62624d1f15/websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7", size = 134386, upload-time = "2023-05-07T14:24:45.702Z" }, - { url = "https://files.pythonhosted.org/packages/8a/77/a04d2911f6e2b9e781ce7ffc1e8516b54b85f985369eec8c853fd619d8e8/websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1", size = 133639, upload-time = "2023-05-07T14:24:46.966Z" }, - { url = "https://files.pythonhosted.org/packages/72/89/0d150939f2e592ed78c071d69237ac1c872462cc62a750c5f592f3d4ab18/websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311", size = 134260, upload-time = "2023-05-07T14:24:48.633Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6e/0fd7274042f46acb589161407f4b505b44c68d369437ce919bae1fa9b8c4/websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128", size = 124146, upload-time = "2023-05-07T14:24:50.566Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3f/65dfa50084a06ab0a05f3ca74195c2c17a1c075b8361327d831ccce0a483/websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e", size = 124665, upload-time = "2023-05-07T14:24:52.111Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2f/3ad8ac4a9dc9d685e098e534180a36ed68fe2e85e82e225e00daec86bb94/websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf", size = 120795, upload-time = "2023-05-07T14:24:53.421Z" }, - { url = "https://files.pythonhosted.org/packages/a7/8c/7100e9cf310fe1d83d1ae1322203f4eb2b767a7c2b301c1e70db6270306f/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5", size = 122910, upload-time = "2023-05-07T14:24:54.851Z" }, - { url = "https://files.pythonhosted.org/packages/78/b2/df5452031b02b857851139806308f2af7c749069e25bfe15f2d559ade6e7/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998", size = 122516, upload-time = "2023-05-07T14:24:56.477Z" }, - { url = "https://files.pythonhosted.org/packages/03/28/3a51ffcf51ac45746639f83128908bbb1cd212aa631e42d15a7acebce5cb/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b", size = 122462, upload-time = "2023-05-07T14:24:57.77Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fb/2af7fc3ce2c3f1378d48a15802b4ff2caf6c0dfac13291e73c557caf04f7/websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb", size = 124704, upload-time = "2023-05-07T14:24:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/20/62/5c6039c4069912adb27889ddd000403a2de9e0fe6aebe439b4e6b128a6b8/websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20", size = 120795, upload-time = "2023-05-07T14:25:01.047Z" }, - { url = "https://files.pythonhosted.org/packages/38/30/01a10fbf4cc1e7ffa07be9b0401501918fc9433d71fb7da4cfcef3bd26ca/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931", size = 122908, upload-time = "2023-05-07T14:25:02.734Z" }, - { url = "https://files.pythonhosted.org/packages/99/23/43071c989c0f87f612e7bccee98d00b04bddd3aca0cdc1ffaf31f6f8a4b4/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9", size = 122515, upload-time = "2023-05-07T14:25:04.803Z" }, - { url = "https://files.pythonhosted.org/packages/b6/96/0d586c25d043aeab9457dad8e407251e3baf314d871215f91847e7b995c4/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280", size = 122465, upload-time = "2023-05-07T14:25:06.352Z" }, - { url = "https://files.pythonhosted.org/packages/27/e9/605b0618d0864e9be7c2a78f22bff57aba9cf56b9fccde3205db9023ae22/websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b", size = 124707, upload-time = "2023-05-07T14:25:07.782Z" }, - { url = "https://files.pythonhosted.org/packages/1b/3d/3dc77699fa4d003f2e810c321592f80f62b81d7b78483509de72ffe581fd/websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82", size = 120795, upload-time = "2023-05-07T14:25:09.785Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1b/5c83c40f8d3efaf0bb2fdf05af94fb920f74842b7aaf31d7598e3ee44d58/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c", size = 122909, upload-time = "2023-05-07T14:25:11.243Z" }, - { url = "https://files.pythonhosted.org/packages/32/2c/ab8ea64e9a7d8bf62a7ea7a037fb8d328d8bd46dbfe083787a9d452a148e/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d", size = 122517, upload-time = "2023-05-07T14:25:12.881Z" }, - { url = "https://files.pythonhosted.org/packages/8b/97/34178f5f7c29e679372d597cebfeff2aa45991d741d938117d4616e81a74/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4", size = 122463, upload-time = "2023-05-07T14:25:15.154Z" }, - { url = "https://files.pythonhosted.org/packages/ed/45/466944e00b324ae3a1fddb305b4abf641f582e131548f07bcd970971b154/websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602", size = 124707, upload-time = "2023-05-07T14:25:17.112Z" }, - { url = "https://files.pythonhosted.org/packages/47/96/9d5749106ff57629b54360664ae7eb9afd8302fad1680ead385383e33746/websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6", size = 118056, upload-time = "2023-05-07T14:25:18.508Z" }, -] - [[package]] name = "websockets" version = "13.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/e2/73/9223dbc7be3dcaf2a7bbf756c351ec8da04b1fa573edaf545b95f6b0c7fd/websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878", size = 158549, upload-time = "2024-09-21T17:34:21.54Z" } wheels = [ @@ -1816,24 +1330,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] -[[package]] -name = "zipp" -version = "3.15.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.8'", -] -sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454, upload-time = "2023-02-25T02:17:22.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758, upload-time = "2023-02-25T02:17:20.807Z" }, -] - [[package]] name = "zipp" version = "3.20.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.8.*'", + "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } wheels = [