diff --git a/examples/examples_common.py b/examples/examples_common.py index c8921b6..c939ad4 100644 --- a/examples/examples_common.py +++ b/examples/examples_common.py @@ -9,13 +9,12 @@ from nucypher_async._drivers.http_client import HTTPClient from nucypher_async._drivers.ssl import fetch_certificate -from nucypher_async._drivers.time import SystemClock +from nucypher_async._drivers.time import BaseClock, SystemClock from nucypher_async._mocks import MockCBDClient, MockClock, MockIdentityClient, MockPREClient from nucypher_async._p2p import Contact, NodeClient, Operator -from nucypher_async.base.time import BaseClock from nucypher_async.blockchain.cbd import CBDClient from nucypher_async.blockchain.identity import AmountT, IdentityAccount, IdentityClient -from nucypher_async.blockchain.pre import PREAccount, PREClient +from nucypher_async.blockchain.pre import PREAccount, PREAmount, PREClient from nucypher_async.characters import MasterKey from nucypher_async.characters.cbd import Decryptor from nucypher_async.characters.pre import Reencryptor @@ -54,6 +53,9 @@ async def local(cls, nursery: trio.Nursery) -> "Context": cbd_client = MockCBDClient() clock = MockClock() + pre_account = PREAccount.random() + pre_client.mock_set_balance(pre_account.address, PREAmount.ether(1)) + logger.info("Mocked mode - starting nodes") for i in range(3): @@ -74,7 +76,7 @@ async def local(cls, nursery: trio.Nursery) -> "Context": AmountT.ether(40000), ) - logger = logger.get_child(f"Node{i + 1}") + node_logger = logger.get_child(f"Node{i + 1}") http_server_config = HTTPServerConfig.from_typed_values( bind_to_address=LOCALHOST, @@ -88,7 +90,7 @@ async def local(cls, nursery: trio.Nursery) -> "Context": pre_client=pre_client, cbd_client=cbd_client, node_client=NodeClient(HTTPClient()), - logger=logger, + logger=node_logger, seed_contacts=seed_contacts, clock=clock, ) @@ -118,7 +120,7 @@ async def local(cls, nursery: trio.Nursery) -> "Context": clock=clock, seed_contact=Contact(LOCALHOST, PORT_BASE), server_handles=handles, - pre_account=PREAccount.random(), + pre_account=pre_account, ) @classmethod diff --git a/examples/grant_and_retrieve.py b/examples/grant_and_retrieve.py index b415e04..384e60f 100644 --- a/examples/grant_and_retrieve.py +++ b/examples/grant_and_retrieve.py @@ -8,9 +8,9 @@ from nucypher_async.blockchain.pre import PREAccountSigner from nucypher_async.characters import MasterKey -from nucypher_async.characters.pre import Delegator, Publisher, Recipient +from nucypher_async.characters.pre import Delegator, EncryptedMessage, Publisher, Recipient from nucypher_async.client.network import NetworkClient -from nucypher_async.client.pre import LocalPREClient, pre_encrypt +from nucypher_async.client.pre import LocalPREClient async def main(*, mocked: bool = True) -> None: @@ -62,13 +62,13 @@ async def main(*, mocked: bool = True) -> None: ) message = b"a secret message" - message_kit = pre_encrypt(enacted_policy, message) + encrypted_message = EncryptedMessage(enacted_policy.policy, message) context.logger.info("Bob retrieves and decrypts") decrypted = await bob_client.decrypt( bob, enacted_policy, - message_kit, + encrypted_message, alice.card(), publisher.card(), ) diff --git a/nucypher_async/_drivers/asgi.py b/nucypher_async/_drivers/asgi.py index 2d6f3c0..9fce904 100644 --- a/nucypher_async/_drivers/asgi.py +++ b/nucypher_async/_drivers/asgi.py @@ -83,7 +83,8 @@ async def body_json(self) -> JSON: @property def remote_host(self) -> str | None: - # TODO: we can get the port here too + # We could get a port here too, but it won't be useful, + # because it won't correspond to the port the server (if any) is bound to. return self._request.client.host if self._request.client else None @property diff --git a/nucypher_async/_drivers/http_client.py b/nucypher_async/_drivers/http_client.py index 1890d49..48c080b 100644 --- a/nucypher_async/_drivers/http_client.py +++ b/nucypher_async/_drivers/http_client.py @@ -7,28 +7,23 @@ import httpx -from .._utils import temp_file from ..base.types import JSON -from .ssl import SSLCertificate, fetch_certificate +from .ssl import SSLCertificate, fetch_certificate, make_client_ssl_context class HTTPClientError(Exception): pass -# TODO: move logic to _drivers/ssl -def make_ssl_context(certificate: SSLCertificate) -> ssl.SSLContext: - # Cannot create a context with a certificate directly, see https://bugs.python.org/issue16487. - # So instead we do it via a temporary file, and negate the performance penalty by caching. - with temp_file(certificate.to_pem_bytes()) as certificate_filename: - return ssl.create_default_context(cafile=str(certificate_filename)) - - class HTTPClient: # The default certificate cache size is chosen to cover the possible size of the network, # which at its best only had a few hundred nodes. def __init__(self, certificate_cache_size: int = 1024): - self._cached_ssl_context = lru_cache(maxsize=certificate_cache_size)(make_ssl_context) + @lru_cache(maxsize=certificate_cache_size) + def cached_ssl_context(certificate: SSLCertificate) -> ssl.SSLContext: + return make_client_ssl_context(certificate) + + self._cached_ssl_context = cached_ssl_context async def fetch_certificate(self, host: str, port: int) -> SSLCertificate: return await fetch_certificate(host, port) @@ -60,9 +55,11 @@ def json(self) -> JSON: return cast("JSON", self._response.json()) @property - def status_code(self) -> http.HTTPStatus: - # TODO: should we add a handling of an unrecognized status code? - return http.HTTPStatus(self._response.status_code) + def status_code(self) -> http.HTTPStatus | None: + try: + return http.HTTPStatus(self._response.status_code) + except ValueError: + return None class HTTPClientSession: diff --git a/nucypher_async/_drivers/http_server.py b/nucypher_async/_drivers/http_server.py index b573d5c..5dbd6db 100644 --- a/nucypher_async/_drivers/http_server.py +++ b/nucypher_async/_drivers/http_server.py @@ -1,6 +1,5 @@ """Encapsulates a specific HTTP server running our ASGI app (currently ``hypercorn``).""" -import os from abc import ABC, abstractmethod from ipaddress import IPv4Address from ssl import SSLContext @@ -12,9 +11,8 @@ from hypercorn.trio import serve from hypercorn.typing import ASGIFramework -from .._utils import temp_file from ..logging import Logger -from .ssl import SSLCertificate, SSLPrivateKey +from .ssl import SSLCertificate, SSLPrivateKey, fill_ssl_context if TYPE_CHECKING: # pragma: no cover import logging @@ -42,35 +40,18 @@ def __init__( # Have to keep the unencrypted private key in memory, # but at least we're not leaking it in the filesystem. - # TODO: Can we do better? Zeroize it on cleanup? self.__ssl_private_key = ssl_private_key def create_ssl_context(self) -> SSLContext | None: - # sanity check - if self.certfile or self.keyfile or self.ca_certs: - raise RuntimeError( - "Certificate/keyfile must be passed to the constructor in the serialized form" - ) - context = super().create_ssl_context() # Since ssl_enabled() returns True, the context will be created, # but with no certificates loaded. assert context is not None, "SSL context was not created" - # Encrypt the temporary file we create with an emphemeral password. - keyfile_password = os.urandom(32) - - # TODO: move logic to _drivers/ssl - if self.__ssl_ca_chain: - chain_data = b"\n".join(cert.to_pem_bytes() for cert in self.__ssl_ca_chain).decode() - context.load_verify_locations(cadata=chain_data) - - with ( - temp_file(self.__ssl_certificate.to_pem_bytes()) as certfile, - temp_file(self.__ssl_private_key.to_pem_bytes(keyfile_password)) as keyfile, - ): - context.load_cert_chain(certfile=certfile, keyfile=keyfile, password=keyfile_password) + fill_ssl_context( + context, self.__ssl_private_key, self.__ssl_certificate, self.__ssl_ca_chain + ) return context diff --git a/nucypher_async/_drivers/ssl.py b/nucypher_async/_drivers/ssl.py index 1865a18..5effca9 100644 --- a/nucypher_async/_drivers/ssl.py +++ b/nucypher_async/_drivers/ssl.py @@ -1,4 +1,6 @@ +import secrets import ssl +from collections.abc import Iterable from ipaddress import ip_address from typing import cast, get_args @@ -14,6 +16,8 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.x509.oid import NameOID +from .._utils import temp_file + class SSLPrivateKey: @classmethod @@ -85,8 +89,6 @@ def self_signed( host: str, days_valid: int = 365, ) -> "SSLCertificate": - # TODO: assert that the start date is in UTC? - public_key = private_key._public_key() # noqa: SLF001 end_date = start_date.shift(days=days_valid) @@ -140,6 +142,10 @@ def list_from_pem_bytes(cls, data: bytes) -> list["SSLCertificate"]: certs_bytes = data.split(start_line) return [cls.from_pem_bytes(start_line + cert_bytes) for cert_bytes in certs_bytes[1:]] + @staticmethod + def list_to_pem_str(certificates: Iterable["SSLCertificate"]) -> str: + return b"\n".join(certificate.to_pem_bytes() for certificate in certificates).decode() + @classmethod def from_der_bytes(cls, data: bytes) -> "SSLCertificate": return cls(x509.load_der_x509_certificate(data)) @@ -175,6 +181,34 @@ def not_valid_after(self) -> arrow.Arrow: return arrow.get(self._certificate.not_valid_after_utc) +def make_client_ssl_context(certificate: SSLCertificate) -> ssl.SSLContext: + # Cannot create a context with a certificate directly, see https://bugs.python.org/issue16487. + # So instead we do it via a temporary file, and negate the performance penalty by caching. + with temp_file(certificate.to_pem_bytes()) as certificate_filename: + return ssl.create_default_context(cafile=str(certificate_filename)) + + +def fill_ssl_context( + context: ssl.SSLContext, + private_key: SSLPrivateKey, + certificate: SSLCertificate, + ca_chain: Iterable[SSLCertificate] | None, +) -> None: + if ca_chain: + context.load_verify_locations(cadata=SSLCertificate.list_to_pem_str(ca_chain)) + + # Encrypt the temporary file we create with an emphemeral password. + # Would be nice to zeroize it in the end, but we cannot zeroize `bytes`, + # and `cryptography` does not accept `bytearray` as a password. + keyfile_password = secrets.token_bytes(32) + + with ( + temp_file(certificate.to_pem_bytes()) as certfile, + temp_file(private_key.to_pem_bytes(keyfile_password)) as keyfile, + ): + context.load_cert_chain(certfile=certfile, keyfile=keyfile, password=keyfile_password) + + async def fetch_certificate(host: str, port: int) -> SSLCertificate: # Do not verify the certificate, it is self-signed context = ssl.create_default_context() diff --git a/nucypher_async/_drivers/time.py b/nucypher_async/_drivers/time.py index 4d87ee0..ccfbe68 100644 --- a/nucypher_async/_drivers/time.py +++ b/nucypher_async/_drivers/time.py @@ -1,6 +1,18 @@ +from abc import ABC, abstractmethod + import arrow -from ..base.time import BaseClock + +class BaseClock(ABC): + """ + An abstract class for getting the current time. + A behavior different from just returning the system time may be needed for tests. + """ + + # not a staticmethod since some implementations may need to maintain an internal state + @abstractmethod + def utcnow(self) -> arrow.Arrow: + pass class SystemClock(BaseClock): diff --git a/nucypher_async/_mocks/asgi.py b/nucypher_async/_mocks/asgi.py index ef1681a..2d7ee43 100644 --- a/nucypher_async/_mocks/asgi.py +++ b/nucypher_async/_mocks/asgi.py @@ -14,7 +14,7 @@ LifespanStartupEvent, ) -from .._drivers.http_client import HTTPClient, HTTPClientSession, HTTPResponse +from .._drivers.http_client import HTTPClient, HTTPClientError, HTTPClientSession, HTTPResponse from .._drivers.http_server import HTTPServable, HTTPServableApp from .._drivers.ssl import SSLCertificate from ..proxy import ProxyServer @@ -93,10 +93,11 @@ def get_server(self, host: str, port: int) -> tuple[SSLCertificate, LifespanMana class MockHTTPClient(HTTPClient): - def __init__(self, mock_network: MockHTTPNetwork, host: str | None = None): - # TODO: do we actually need to be able to specify the client's host? + def __init__(self, mock_network: MockHTTPNetwork, client_host: str | None = None): self._mock_network = mock_network - self._host = host or "passive client" + # Since the nodes use HTTP for P2P messaging, + # we need to be able to report the client's hostname (used for DDoS protection). + self._client_host = client_host or "passive client" async def fetch_certificate(self, host: str, port: int) -> SSLCertificate: certificate, _manager = self._mock_network.get_server(host, port) @@ -104,15 +105,21 @@ async def fetch_certificate(self, host: str, port: int) -> SSLCertificate: @asynccontextmanager async def session( - self, _certificate: SSLCertificate | None = None + self, certificate: SSLCertificate | None = None ) -> AsyncIterator["MockHTTPClientSession"]: - yield MockHTTPClientSession(self._mock_network, self._host) + yield MockHTTPClientSession(self._mock_network, self._client_host, certificate) class MockHTTPClientSession(HTTPClientSession): - def __init__(self, mock_network: MockHTTPNetwork, host: str = "mock_hostname"): + def __init__( + self, + mock_network: MockHTTPNetwork, + client_host: str = "mock_hostname", + certificate: SSLCertificate | None = None, + ): self._mock_network = mock_network - self._host = host + self._client_host = client_host + self._certificate = certificate async def get(self, url: str, params: Mapping[str, str] = {}) -> HTTPResponse: response = await self._request("get", url, params=params) @@ -126,11 +133,14 @@ async def _request(self, method: str, url: str, *args: Any, **kwargs: Any) -> ht url_parts = urlparse(url) assert url_parts.hostname is not None, "Hostname is missing from the url" assert url_parts.port is not None, "Port is missing from the url" - _certificate, manager = self._mock_network.get_server(url_parts.hostname, url_parts.port) - # TODO: check the cerificate's validity here + certificate, manager = self._mock_network.get_server(url_parts.hostname, url_parts.port) + + if self._certificate is not None and certificate != self._certificate: + raise HTTPClientError("Certificate mismatch") + # Unfortunately there are no unified types for hypercorn and httpx, # so we have to cast manually. app = cast("httpx._transports.asgi._ASGIApp", manager.app) # noqa: SLF001 - transport = httpx.ASGITransport(app=app, client=(str(self._host), 9999)) + transport = httpx.ASGITransport(app=app, client=(self._client_host, 9999)) async with httpx.AsyncClient(transport=transport) as client: return await client.request(method, url, *args, **kwargs) diff --git a/nucypher_async/_mocks/eth.py b/nucypher_async/_mocks/eth.py index 0d1334d..043a8ad 100644 --- a/nucypher_async/_mocks/eth.py +++ b/nucypher_async/_mocks/eth.py @@ -59,16 +59,26 @@ async def call(self, call: BoundMethodCall) -> Any: async def transact( self, signer: Signer, call: BoundMethodCall, amount: Amount | None = None ) -> None: - # TODO: change the caller's balance appropriately - # TODO: check that the call is payable if amount is not 0 - # Lower the type from specific currency amount = Amount.wei(0 if amount is None else amount.as_wei()) + signer_balance = self._balances[signer.address] + if signer_balance < amount: + raise ValueError( + f"Not enough funds for {signer.address.checksum}: " + f"need {amount}, got {signer_balance}" + ) + + if amount.as_wei() != 0 and not call.payable: + raise ValueError( + "The call is not payable, but the transaction has funds attached to it" + ) + # Lower the signer address type address = Address(bytes(signer.address)) self._contracts[call.contract_address].transact(address, amount, call.data_bytes) + self._balances[signer.address] -= amount def set_balance(self, address: Address, amount: Amount) -> None: # Lower the type from specific currency diff --git a/nucypher_async/_mocks/identity.py b/nucypher_async/_mocks/identity.py index c5f3791..dc903ab 100644 --- a/nucypher_async/_mocks/identity.py +++ b/nucypher_async/_mocks/identity.py @@ -39,7 +39,7 @@ def isOperatorConfirmed(self, operator_address: Address) -> bool: # noqa: N802 def getActiveStakingProviders( # noqa: N802 self, _start_index: int, _max_staking_providers: int ) -> tuple[int, list[bytes]]: - # TODO: support pagination + # TODO (#47): implement pagination total = sum(amount.as_wei() for amount in self._stakes.values()) return total, [ bytes(address) + amount.as_wei().to_bytes(12, byteorder="big") diff --git a/nucypher_async/_mocks/time.py b/nucypher_async/_mocks/time.py index f9f1b5e..9a5c240 100644 --- a/nucypher_async/_mocks/time.py +++ b/nucypher_async/_mocks/time.py @@ -1,9 +1,10 @@ import arrow import trio -from ..base.time import BaseClock +from .._drivers.time import BaseClock +# TODO (#45): derive from `trio.abc.Clock`. class MockClock(BaseClock): def __init__(self) -> None: self._start: float | None = None diff --git a/nucypher_async/_p2p/_fleet_sensor.py b/nucypher_async/_p2p/_fleet_sensor.py index ed46db5..2b53c65 100644 --- a/nucypher_async/_p2p/_fleet_sensor.py +++ b/nucypher_async/_p2p/_fleet_sensor.py @@ -13,7 +13,7 @@ from attrs import evolve, frozen from sortedcontainers import SortedKeyList -from ..base.time import BaseClock +from .._drivers.time import BaseClock from ..blockchain.identity import AmountT, IdentityAddress from ._keys import Contact from ._node_info import NodeInfo @@ -429,6 +429,9 @@ def next_learning_in(self) -> float: def is_empty(self) -> bool: return self._contacts_db.is_empty() and self._verified_nodes_db.is_empty() + def has_no_new_contacts(self) -> bool: + return self._contacts_db.is_empty() + def next_verification_in(self) -> float: if self._contacts_db.is_empty() and self._verified_nodes_db.is_empty(): return datetime.timedelta.max.total_seconds() diff --git a/nucypher_async/_p2p/_fleet_state.py b/nucypher_async/_p2p/_fleet_state.py index e5ee4da..fef16c4 100644 --- a/nucypher_async/_p2p/_fleet_state.py +++ b/nucypher_async/_p2p/_fleet_state.py @@ -3,7 +3,7 @@ from nucypher_core import FleetStateChecksum -from ..base.time import BaseClock +from .._drivers.time import BaseClock from ._keys import Contact from ._node_info import NodeInfo from ._verification import VerifiedNodeInfo diff --git a/nucypher_async/_p2p/_keys.py b/nucypher_async/_p2p/_keys.py index 101a759..8f92b7e 100644 --- a/nucypher_async/_p2p/_keys.py +++ b/nucypher_async/_p2p/_keys.py @@ -13,7 +13,7 @@ import trio from .._drivers.ssl import SSLCertificate, SSLPrivateKey -from ..base.time import BaseClock +from .._drivers.time import BaseClock class Contact: diff --git a/nucypher_async/_p2p/_learner.py b/nucypher_async/_p2p/_learner.py index 9af47a1..2c069de 100644 --- a/nucypher_async/_p2p/_learner.py +++ b/nucypher_async/_p2p/_learner.py @@ -3,9 +3,8 @@ import trio -from .._drivers.time import SystemClock +from .._drivers.time import BaseClock, SystemClock from .._utils import wait_for_any -from ..base.time import BaseClock from ..blockchain.identity import AmountT, IdentityAddress, IdentityClient from ..domain import Domain from ..logging import NULL_LOGGER, Logger @@ -299,6 +298,9 @@ def get_verified_nodes(self) -> list[VerifiedNodeInfo]: def get_snapshot(self) -> FleetSensorSnapshot: return self._fleet_sensor.get_snapshot() + def has_no_new_contacts(self) -> bool: + return self._fleet_sensor.has_no_new_contacts() + async def verification_task(self, stop_event: trio.Event) -> None: while True: await self.verification_round() diff --git a/nucypher_async/_p2p/_node_client.py b/nucypher_async/_p2p/_node_client.py index 771b87d..df543bc 100644 --- a/nucypher_async/_p2p/_node_client.py +++ b/nucypher_async/_p2p/_node_client.py @@ -103,7 +103,9 @@ async def exchange_node_info( response = try_deserialize(response_bytes, MetadataResponse) try: - payload = response.verify(node_info.verifying_key) + # It's an inconsistency in `nucypher_core` that the Umbral verifying key is used + # for general P2P messages. + payload = response.verify(node_info.pre_verifying_key) except Exception as exc: # TODO: can we narrow it down? # TODO: should it be a separate error class? raise PeerError.invalid_message(MetadataResponse, exc) from exc @@ -151,7 +153,7 @@ async def reencrypt( verified_cfrags = response.verify( capsules=capsules, alice_verifying_key=delegator_card.verifying_key, - ursula_verifying_key=node_info.verifying_key, + ursula_verifying_key=node_info.pre_verifying_key, policy_encrypting_key=treasure_map.policy_encrypting_key, bob_encrypting_key=recipient_card.encrypting_key, ) diff --git a/nucypher_async/_p2p/_node_info.py b/nucypher_async/_p2p/_node_info.py index fae246c..49e7adf 100644 --- a/nucypher_async/_p2p/_node_info.py +++ b/nucypher_async/_p2p/_node_info.py @@ -2,7 +2,7 @@ import arrow from nucypher_core import NodeMetadata, NodeMetadataPayload -from nucypher_core.umbral import PublicKey +from nucypher_core.umbral import PublicKey as UmbralPublicKey from ..blockchain.identity import IdentityAddress from ..domain import Domain @@ -45,21 +45,25 @@ def domain(self) -> Domain: return Domain(self._metadata_payload.domain) @property - def encrypting_key(self) -> PublicKey: + def pre_encrypting_key(self) -> UmbralPublicKey: return self._metadata_payload.encrypting_key @property - def verifying_key(self) -> PublicKey: + def pre_verifying_key(self) -> UmbralPublicKey: return self._metadata_payload.verifying_key @cached_property def timestamp(self) -> arrow.Arrow: return arrow.get(self._metadata_payload.timestamp_epoch) - def __bytes__(self) -> bytes: - # TODO: cache it too? + @cached_property + def _bytes(self) -> bytes: + # Cannot apply `cached_property` to `__bytes__()` directly, so have to use a trampoline. return bytes(self.metadata) + def __bytes__(self) -> bytes: + return self._bytes + @classmethod def from_bytes(cls, data: bytes) -> "NodeInfo": return cls(NodeMetadata.from_bytes(data)) diff --git a/nucypher_async/_p2p/_storage.py b/nucypher_async/_p2p/_storage.py index 936d1ea..1b08bad 100644 --- a/nucypher_async/_p2p/_storage.py +++ b/nucypher_async/_p2p/_storage.py @@ -4,8 +4,6 @@ from ._node_info import NodeInfo -# TODO: add the ability to save certificates -# TODO: add `get_child()` so that it could be used hierarchically class BaseStorage(ABC): @abstractmethod def get_my_node_info(self) -> NodeInfo | None: ... diff --git a/nucypher_async/_p2p/_verification.py b/nucypher_async/_p2p/_verification.py index 66f35f7..0ea05b4 100644 --- a/nucypher_async/_p2p/_verification.py +++ b/nucypher_async/_p2p/_verification.py @@ -1,12 +1,11 @@ from nucypher_core import Address, NodeMetadata, NodeMetadataPayload -from nucypher_core.ferveo import FerveoPublicKey -from nucypher_core.umbral import PublicKey, RecoverableSignature, Signer -from ..base.time import BaseClock +from .._drivers.time import BaseClock from ..blockchain.identity import IdentityAddress, IdentityClientSession -from ..characters.pre import Reencryptor, ReencryptorCard +from ..characters.cbd import DkgNodeCard +from ..characters.pre import ReencryptorCard from ..domain import Domain -from ._keys import Contact, PeerPrivateKey, PeerPublicKey, SecureContact +from ._keys import Contact, PeerPublicKey, SecureContact from ._node_info import NodeInfo from ._operator import Operator @@ -84,34 +83,33 @@ class VerifiedNodeInfo(NodeInfo): @classmethod def generate( cls, - peer_private_key: PeerPrivateKey, + operator: Operator, peer_public_key: PeerPublicKey | None, - signer: Signer, - encrypting_key: PublicKey, - dkg_key: FerveoPublicKey, - operator_signature: RecoverableSignature, + reencryptor_card: ReencryptorCard, + dkg_node_card: DkgNodeCard, clock: BaseClock, staking_provider_address: IdentityAddress, contact: Contact, domain: Domain, ) -> "VerifiedNodeInfo": - # TODO: use the character instead of several arguments? - public_key = peer_public_key or PeerPublicKey.generate(peer_private_key, clock, contact) + public_key = peer_public_key or PeerPublicKey.generate( + operator.peer_private_key, clock, contact + ) payload = NodeMetadataPayload( staking_provider_address=Address(bytes(staking_provider_address)), domain=domain.value, timestamp_epoch=int(clock.utcnow().timestamp()), - operator_signature=operator_signature, - verifying_key=signer.verifying_key(), - encrypting_key=encrypting_key, - ferveo_public_key=dkg_key, + operator_signature=operator.signature, + verifying_key=operator.verifying_key, + encrypting_key=reencryptor_card.encrypting_key, + ferveo_public_key=dkg_node_card.public_key, # Abstraction leak here, ideally NodeMetadata should - # have a field like `peer_public_key`. + # have a field like `transport_public_key`. certificate_der=bytes(public_key), host=str(contact.host), port=contact.port, ) - metadata = NodeMetadata(signer=signer, payload=payload) + metadata = NodeMetadata(signer=operator.signer, payload=payload) return cls(metadata) @classmethod @@ -143,12 +141,11 @@ def checked_local( cls, clock: BaseClock, node_info: NodeInfo, - operator: Operator, # TODO: use OperatorCard? - reencryptor: Reencryptor, # TODO: use ReencryptorCard + operator: Operator, + reencryptor_card: ReencryptorCard, staking_provider_address: IdentityAddress, contact: Contact, domain: Domain, - peer_private_key: PeerPrivateKey, # TODO: this is a property of Operator peer_public_key: PeerPublicKey | None, ) -> "VerifiedNodeInfo": _verify_peer_shared( @@ -159,7 +156,7 @@ def checked_local( expected_operator_address=operator.address, ) - if not peer_private_key.matches(node_info.public_key): + if not operator.peer_private_key.matches(node_info.public_key): raise PeerVerificationError( "The peer public key in the metadata does not match the given peer private key" ) @@ -169,7 +166,11 @@ def checked_local( "The peer public key in the metadata does not match the given peer public key" ) - # TODO: check that peer_public_key.host == contact.host? Or is that redundant at this point? + if peer_public_key is not None and peer_public_key.declared_host != contact.host: + raise PeerVerificationError( + f"The host declared in the public key ({peer_public_key.declared_host}) " + f"is different from the external host in the config ({contact.host})" + ) if node_info.staking_provider_address != staking_provider_address: raise PeerVerificationError( @@ -178,23 +179,22 @@ def checked_local( f"{staking_provider_address} recorded in the blockchain" ) - if node_info.verifying_key != operator.verifying_key: + if node_info.pre_verifying_key != operator.verifying_key: raise PeerVerificationError( - f"Verifying key mismatch: {node_info.verifying_key} in the metadata, " + f"Verifying key mismatch: {node_info.pre_verifying_key} in the metadata, " f"{operator.verifying_key} derived from the master key" ) - if node_info.encrypting_key != reencryptor.encrypting_key: + if node_info.pre_encrypting_key != reencryptor_card.encrypting_key: raise PeerVerificationError( - f"Encrypting key mismatch: {node_info.encrypting_key} in the metadata, " - f"{reencryptor.encrypting_key} derived from the master key" + f"Encrypting key mismatch: {node_info.pre_encrypting_key} in the metadata, " + f"{reencryptor_card.encrypting_key} derived from the master key" ) return cls(node_info.metadata) def reencryptor_card(self) -> ReencryptorCard: - # TODO: this method seems out of place here? - return ReencryptorCard(encrypting_key=self.encrypting_key) + return ReencryptorCard(encrypting_key=self.pre_encrypting_key) def __str__(self) -> str: return f"VerifiedNodeInfo({self.staking_provider_address.checksum})" diff --git a/nucypher_async/base/time.py b/nucypher_async/base/time.py deleted file mode 100644 index ebd0b5d..0000000 --- a/nucypher_async/base/time.py +++ /dev/null @@ -1,15 +0,0 @@ -from abc import ABC, abstractmethod - -import arrow - - -class BaseClock(ABC): - """ - An abstract class for getting the current time. - A behavior different from just returning the system time may be needed for tests. - """ - - # not a staticmethod since some implementations may need to maintain an internal state - @abstractmethod - def utcnow(self) -> arrow.Arrow: - pass diff --git a/nucypher_async/blockchain/cbd.py b/nucypher_async/blockchain/cbd.py index 00bf672..b5798b8 100644 --- a/nucypher_async/blockchain/cbd.py +++ b/nucypher_async/blockchain/cbd.py @@ -281,7 +281,7 @@ async def get_ritual(self, ritual_id: int) -> "OnChainRitual": call = self._coordinator.method.rituals(ritual_id) ritual = await self._backend_session.call(call) - # TODO: workaround for https://github.com/nucypher/ferveo/issues/209 + # TODO (#49): workaround for https://github.com/nucypher/ferveo/issues/209 # The `AggregatedTranscript` in `ferveo` has an additional field (dkg public key) # compared to the aggregated transcript saved on chain. # Since we use serde/bincode in rust, we need a metadata field for the public key, diff --git a/nucypher_async/blockchain/identity.py b/nucypher_async/blockchain/identity.py index e10100c..cd58029 100644 --- a/nucypher_async/blockchain/identity.py +++ b/nucypher_async/blockchain/identity.py @@ -199,7 +199,6 @@ async def get_operator_address( async def is_staking_provider_authorized( self, staking_provider_address: IdentityAddress ) -> bool: - # TODO: casting for now, see https://github.com/fjarri/pons/issues/41 return cast( "bool", await self._backend_session.call( @@ -208,7 +207,6 @@ async def is_staking_provider_authorized( ) async def is_operator_confirmed(self, operator_address: IdentityAddress) -> bool: - # TODO: casting for now, see https://github.com/fjarri/pons/issues/41 return cast( "bool", await self._backend_session.call( @@ -223,7 +221,7 @@ async def get_balance(self, address: IdentityAddress) -> AmountETH: async def get_active_staking_providers( self, start_index: int = 0, max_staking_providers: int = 0 ) -> dict[IdentityAddress, AmountT]: - # TODO: implement pagination + # TODO (#47): implement pagination _total_staked, staking_providers_data = await self._backend_session.call( self._taco_application.method.getActiveStakingProviders( start_index, max_staking_providers diff --git a/nucypher_async/blockchain/pre.py b/nucypher_async/blockchain/pre.py index 913a865..b7676aa 100644 --- a/nucypher_async/blockchain/pre.py +++ b/nucypher_async/blockchain/pre.py @@ -160,7 +160,6 @@ async def is_policy_active(self, hrac: HRAC) -> bool: is_active = await self._backend_session.call( self._manager.method.isPolicyActive(bytes(hrac)) ) - # TODO: casting for now, see https://github.com/fjarri/pons/issues/41 return cast("bool", is_active) async def get_policy_cost(self, shares: int, policy_start: int, policy_end: int) -> PREAmount: diff --git a/nucypher_async/characters/cbd.py b/nucypher_async/characters/cbd.py index 2d3200e..7f802ee 100644 --- a/nucypher_async/characters/cbd.py +++ b/nucypher_async/characters/cbd.py @@ -86,7 +86,7 @@ def from_on_chain_ritual( ) def _make_dkg_struct(self, me: "DkgNodeCard") -> Dkg: - # TODO: the address argument to Validator has no effect on cryptographic operations, + # TODO (#53): the address argument to Validator has no effect on cryptographic operations, # to be removed in https://github.com/nucypher/ferveo/pull/220 validators = [ Validator( @@ -180,7 +180,7 @@ def card(self) -> DkgNodeCard: def participate_in_ritual( self, ritual_id: int, participants: Sequence[PlannedParticipant], threshold: int ) -> tuple[Participant, Transcript]: - # TODO: the address argument to Validator has no effect on cryptographic operations, + # TODO (#53): the address argument to Validator has no effect on cryptographic operations, # to be removed in https://github.com/nucypher/ferveo/pull/220 validators = [ Validator( @@ -234,7 +234,8 @@ def make_decryption_share( def make_threshold_decryption_response( self, ritual: ActiveRitual, decryption_share: DecryptionShareSimple ) -> ThresholdDecryptionResponse: - # TODO: need the serialization because of https://github.com/nucypher/nucypher-core/issues/121 + # TODO (#38): need the serialization because of + # https://github.com/nucypher/nucypher-core/issues/121 return ThresholdDecryptionResponse(ritual.id, bytes(decryption_share)) def encrypt_threshold_decryption_response( @@ -286,12 +287,14 @@ def decrypt_threshold_decryption_response( def decrypt_with_responses( message_kit: ThresholdMessageKit, responses: Iterable[ThresholdDecryptionResponse] ) -> bytes: - # TODO: need the deserialization because of https://github.com/nucypher/nucypher-core/issues/121 + # TODO (#38): need the deserialization because of + # https://github.com/nucypher/nucypher-core/issues/121 decryption_shares = [ DecryptionShareSimple.from_bytes(response.decryption_share) for response in responses ] - # TODO: need the conversion because of https://github.com/nucypher/nucypher-core/issues/119 + # TODO (#39): need the conversion because of + # https://github.com/nucypher/nucypher-core/issues/119 return bytes( message_kit.decrypt_with_shared_secret( combine_decryption_shares_simple(decryption_shares) diff --git a/nucypher_async/characters/pre.py b/nucypher_async/characters/pre.py index 08e917d..8009092 100644 --- a/nucypher_async/characters/pre.py +++ b/nucypher_async/characters/pre.py @@ -7,10 +7,10 @@ Conditions, EncryptedKeyFrag, EncryptedTreasureMap, + MessageKit, + RetrievalKit, TreasureMap, ) -from nucypher_core import MessageKit as CoreMessageKit -from nucypher_core import RetrievalKit as CoreRetrievalKit from nucypher_core.umbral import ( Capsule, PublicKey, @@ -112,32 +112,36 @@ def card(self) -> "PublisherCard": return PublisherCard(verifying_key=self.verifying_key) -# TODO: rename to `EncryptedMessage` and remove `Encryptor` character? -class MessageKit: +class EncryptedMessage: def __init__(self, policy: Policy, message: bytes, conditions: Conditions | None = None): - self.core_message_kit = CoreMessageKit( + self.message_kit = MessageKit( policy_encrypting_key=policy.encrypting_key, plaintext=message, conditions=conditions ) - self.capsule = self.core_message_kit.capsule - self.conditions = self.core_message_kit.conditions + @property + def capsule(self) -> Capsule: + return self.message_kit.capsule + + @property + def conditions(self) -> Conditions | None: + return self.message_kit.conditions + + @property + def metadata(self) -> "EncryptedMessageMetadata": + return EncryptedMessageMetadata(RetrievalKit.from_message_kit(self.message_kit)) -# TODO: rename to something like `EncryptedMessageMetadata`? -class RetrievalKit: - @classmethod - def from_message_kit(cls, message_kit: MessageKit) -> "RetrievalKit": - return cls(CoreRetrievalKit.from_message_kit(message_kit.core_message_kit)) - def __init__(self, retrieval_kit: CoreRetrievalKit): - self.core_retrieval_kit = retrieval_kit - self.capsule = self.core_retrieval_kit.capsule - self.conditions = self.core_retrieval_kit.conditions +class EncryptedMessageMetadata: + def __init__(self, retrieval_kit: RetrievalKit): + self.retrieval_kit = retrieval_kit + @property + def capsule(self) -> Capsule: + return self.retrieval_kit.capsule -class Encryptor: - @staticmethod - def encrypt(policy: Policy, message: bytes, conditions: Conditions | None = None) -> MessageKit: - return MessageKit(policy, message, conditions=conditions) + @property + def conditions(self) -> Conditions | None: + return self.retrieval_kit.conditions @frozen @@ -148,17 +152,18 @@ class PublisherCard: class DecryptionKit: encrypted_kfrags: dict[IdentityAddress, EncryptedKeyFrag] - capsule: Capsule - - def __init__(self, message_kit: MessageKit, treasure_map: TreasureMap): - self.core_message_kit = message_kit.core_message_kit - self.core_treasure_map = treasure_map - self.capsule = self.core_message_kit.capsule + def __init__(self, encrypted_message: EncryptedMessage, treasure_map: TreasureMap): + self.message_kit = encrypted_message.message_kit + self.treasure_map = treasure_map self.encrypted_kfrags = { IdentityAddress(bytes(address)): ekfrag - for address, ekfrag in self.core_treasure_map.destinations.items() + for address, ekfrag in self.treasure_map.destinations.items() } + @property + def capsule(self) -> Capsule: + return self.message_kit.capsule + class Recipient: @classmethod @@ -187,9 +192,9 @@ def decrypt( decryption_kit: DecryptionKit, vcfrags: Iterable[VerifiedCapsuleFrag], ) -> bytes: - return decryption_kit.core_message_kit.decrypt_reencrypted( + return decryption_kit.message_kit.decrypt_reencrypted( self._decrypting_key, - decryption_kit.core_treasure_map.policy_encrypting_key, + decryption_kit.treasure_map.policy_encrypting_key, list(vcfrags), ) diff --git a/nucypher_async/client/network.py b/nucypher_async/client/network.py index 268610d..28f7cdb 100644 --- a/nucypher_async/client/network.py +++ b/nucypher_async/client/network.py @@ -8,7 +8,7 @@ import trio from .._drivers.http_client import HTTPClient -from .._drivers.time import SystemClock +from .._drivers.time import BaseClock, SystemClock from .._p2p import ( BaseStorage, Contact, @@ -19,7 +19,6 @@ VerifiedNodeInfo, ) from .._utils import producer -from ..base.time import BaseClock from ..blockchain.identity import IdentityAddress, IdentityClient from ..domain import Domain from ..logging import NULL_LOGGER, Logger @@ -33,9 +32,10 @@ def __init__( seed_contacts: Iterable[Contact] | None = None, domain: Domain = Domain.MAINNET, parent_logger: Logger = NULL_LOGGER, - clock: BaseClock = SystemClock(), + clock: BaseClock | None = None, storage: BaseStorage | None = None, ): + clock = clock or SystemClock() node_client = node_client or NodeClient(HTTPClient()) self._learner = Learner( node_client=node_client, @@ -62,7 +62,7 @@ async def _get_updated_learner(self) -> Learner: async def _ensure_seeded(self) -> None: if not self._seeded: - await self._learner.seed_round() + await self._learner.seed_round(must_succeed=True) self._seeded = True async def verification_task(self, stop_event: trio.Event) -> None: diff --git a/nucypher_async/client/pre.py b/nucypher_async/client/pre.py index 836249e..ce0e9e8 100644 --- a/nucypher_async/client/pre.py +++ b/nucypher_async/client/pre.py @@ -12,13 +12,13 @@ from ..characters.pre import ( DecryptionKit, DelegatorCard, - MessageKit, + EncryptedMessage, + EncryptedMessageMetadata, Policy, Publisher, PublisherCard, Recipient, RecipientCard, - RetrievalKit, ) from .network import NetworkClient @@ -34,7 +34,7 @@ class EnactedPolicy: @frozen class PRERetrievalOutcome: - # TODO: merge the two fields into dict[IdentityAddress, VerifiedCapsuleFrag | Exception]? + # TODO (#51): merge the two fields into dict[IdentityAddress, VerifiedCapsuleFrag | Exception]? cfrags: dict[IdentityAddress, VerifiedCapsuleFrag] errors: dict[IdentityAddress, str] @@ -44,7 +44,7 @@ class BasePREConsumerClient(ABC): async def retrieve( self, treasure_map: TreasureMap, - message_kit: MessageKit | RetrievalKit, + metadata: EncryptedMessageMetadata, delegator_card: DelegatorCard, recipient_card: RecipientCard, context: Context | None = None, @@ -54,7 +54,7 @@ async def decrypt( self, recipient: Recipient, enacted_policy: EnactedPolicy, - message_kit: MessageKit, + encrypted_message: EncryptedMessage, delegator_card: DelegatorCard, publisher_card: PublisherCard | None = None, context: Context | None = None, @@ -66,7 +66,7 @@ async def decrypt( # TODO: run mutliple rounds until completion outcome = await self.retrieve( treasure_map=treasure_map, - message_kit=message_kit, + metadata=encrypted_message.metadata, delegator_card=delegator_card, recipient_card=recipient.card(), context=context, @@ -74,17 +74,11 @@ async def decrypt( # TODO: check that we have enough vcfrags return recipient.decrypt( - decryption_kit=DecryptionKit(message_kit, treasure_map), + decryption_kit=DecryptionKit(encrypted_message, treasure_map), vcfrags=outcome.cfrags.values(), ) -# TODO: move to `characters` -def pre_encrypt(policy: Policy | EnactedPolicy, message: bytes) -> MessageKit: - policy_ = policy.policy if isinstance(policy, EnactedPolicy) else policy - return MessageKit(policy_, message, conditions=None) - - class LocalPREClient(BasePREConsumerClient): def __init__(self, network_client: NetworkClient, pre_client: PREClient): self._network_client = network_client @@ -93,7 +87,7 @@ def __init__(self, network_client: NetworkClient, pre_client: PREClient): async def retrieve( self, treasure_map: TreasureMap, - message_kit: MessageKit | RetrievalKit, + metadata: EncryptedMessageMetadata, delegator_card: DelegatorCard, recipient_card: RecipientCard, context: Context | None = None, @@ -103,12 +97,12 @@ async def retrieve( async def reencrypt(nursery: trio.Nursery, node_info: VerifiedNodeInfo) -> None: verified_cfrags = await self._network_client.node_client.reencrypt( node_info=node_info, - # TODO: support retrieving for several capsules at once - REST API allows it - capsules=[message_kit.capsule], + # TODO (#50): support retrieving for several capsules at once - REST API allows it + capsules=[metadata.capsule], treasure_map=treasure_map, delegator_card=delegator_card, recipient_card=recipient_card, - conditions=message_kit.conditions, + conditions=metadata.conditions, context=context, ) responses[node_info.staking_provider_address] = verified_cfrags[0] diff --git a/nucypher_async/logging.py b/nucypher_async/logging.py index 59e0f45..070f481 100644 --- a/nucypher_async/logging.py +++ b/nucypher_async/logging.py @@ -17,8 +17,7 @@ import trio from attr import frozen -from ._drivers.time import SystemClock -from .base.time import BaseClock +from ._drivers.time import BaseClock, SystemClock class Level(IntEnum): diff --git a/nucypher_async/node/_config.py b/nucypher_async/node/_config.py index 297b5d6..d7ec63e 100644 --- a/nucypher_async/node/_config.py +++ b/nucypher_async/node/_config.py @@ -7,7 +7,7 @@ from .._drivers.http_client import HTTPClient from .._drivers.ssl import SSLCertificate, SSLPrivateKey -from .._drivers.time import SystemClock +from .._drivers.time import BaseClock, SystemClock from .._p2p import ( BaseStorage, Contact, @@ -17,7 +17,6 @@ PeerPrivateKey, PeerPublicKey, ) -from ..base.time import BaseClock from ..blockchain.cbd import CBDClient from ..blockchain.identity import IdentityClient from ..blockchain.pre import PREClient @@ -99,7 +98,10 @@ def from_config_values( else: ssl_ca_chain = [] - # TODO: check that the SSL certificate corresponds to the given private key + if not ssl_private_key.matches(ssl_certificate): + raise ValueError( + "The given SSL certificate must be created using the given private key" + ) # TODO: check that certificates in the chain are in the correct order? # (root certificate last) diff --git a/nucypher_async/node/_server.py b/nucypher_async/node/_server.py index 72677c3..6c4256c 100644 --- a/nucypher_async/node/_server.py +++ b/nucypher_async/node/_server.py @@ -70,7 +70,7 @@ def __init__( self._storage = config.storage node_info = self._storage.get_my_node_info() - maybe_node: VerifiedNodeInfo | None = None + maybe_node_info: VerifiedNodeInfo | None = None peer_key_pair = config.peer_key_pair if peer_key_pair is not None: @@ -83,16 +83,15 @@ def __init__( self._logger.debug("Found existing metadata, verifying") try: - maybe_node = VerifiedNodeInfo.checked_local( + maybe_node_info = VerifiedNodeInfo.checked_local( clock=self._clock, node_info=node_info, operator=operator, - reencryptor=self.reencryptor, + reencryptor_card=self.reencryptor.card(), staking_provider_address=staking_provider_address, contact=config.contact, domain=config.domain, peer_public_key=peer_public_key, - peer_private_key=peer_private_key, ) except PeerVerificationError as exc: self._logger.warning( @@ -101,26 +100,24 @@ def __init__( exc_info=True, ) - if maybe_node is None: + if maybe_node_info is None: self._logger.debug("Generating new metadata") - self._node = VerifiedNodeInfo.generate( + self.info = VerifiedNodeInfo.generate( clock=self._clock, - peer_private_key=peer_private_key, + operator=self.operator, peer_public_key=peer_public_key, - signer=self.operator.signer, - operator_signature=self.operator.signature, - encrypting_key=self.reencryptor.encrypting_key, - dkg_key=self.decryptor.public_key, + reencryptor_card=self.reencryptor.card(), + dkg_node_card=self.decryptor.card(), staking_provider_address=staking_provider_address, contact=config.contact, domain=config.domain, ) - self._storage.set_my_node_info(self._node) + self._storage.set_my_node_info(self.info) else: - self._node = maybe_node + self.info = maybe_node_info self.learner = Learner( - this_node=self._node, + this_node=self.info, node_client=config.node_client, identity_client=config.identity_client, storage=config.storage, @@ -145,7 +142,7 @@ def __init__( self.started = False def secure_contact(self) -> SecureContact: - return self._node.secure_contact + return self.info.secure_contact def peer_private_key(self) -> PeerPrivateKey: return self._peer_private_key @@ -205,7 +202,7 @@ async def node_metadata(self, remote_host: str | None, request_bytes: bytes) -> node_infos = [ node_info for node_info in node_infos - if node_info.staking_provider_address != self._node.staking_provider_address + if node_info.staking_provider_address != self.info.staking_provider_address ] # TODO: this can work differently if the P2P network does not use HTTP. @@ -214,7 +211,7 @@ async def node_metadata(self, remote_host: str | None, request_bytes: bytes) -> self.learner.passive_learning(remote_host, node_infos) announce_nodes = [m.metadata for m in self.learner.get_verified_nodes()] + [ - self._node.metadata + self.info.metadata ] response_payload = MetadataResponsePayload( @@ -224,7 +221,7 @@ async def node_metadata(self, remote_host: str | None, request_bytes: bytes) -> return bytes(MetadataResponse(self.operator.signer, response_payload)) async def public_information(self) -> bytes: - return bytes(self._node.metadata) + return bytes(self.info.metadata) async def reencrypt(self, request_bytes: bytes) -> bytes: try: @@ -246,7 +243,7 @@ async def reencrypt(self, request_bytes: bytes) -> bytes: publisher_card=PublisherCard(request.publisher_verifying_key), ) - # TODO: check conditions here + # TODO (#52): check conditions here # TODO: catch reencryption errors (if any) and raise RPC error here vcfrags = self.reencryptor.reencrypt( @@ -278,7 +275,7 @@ async def decrypt(self, request_bytes: bytes) -> bytes: async with self._cbd_client.session() as session: assert await session.is_ritual_active(decryption_request.ritual_id) assert await session.is_participant( - decryption_request.ritual_id, self._node.staking_provider_address + decryption_request.ritual_id, self.info.staking_provider_address ) ciphertext_header = decryption_request.ciphertext_header @@ -299,7 +296,7 @@ async def decrypt(self, request_bytes: bytes) -> bytes: validators = {} for staking_provider_address in on_chain_ritual.providers: - if staking_provider_address == self._node.staking_provider_address: + if staking_provider_address == self.info.staking_provider_address: # Local public_key = self.decryptor.public_key else: @@ -329,10 +326,10 @@ async def decrypt(self, request_bytes: bytes) -> bytes: # should be available at the same port as the rest of the API, so it has to stay here. async def status(self) -> str: return render_status( - node=self._node, + node=self.info, logger=self._logger, clock=self._clock, snapshot=self.learner.get_snapshot(), started_at=self._started_at, - domain=self._node.domain, + domain=self.info.domain, ) diff --git a/nucypher_async/node/_status.py b/nucypher_async/node/_status.py index e523515..5eff7a4 100644 --- a/nucypher_async/node/_status.py +++ b/nucypher_async/node/_status.py @@ -7,8 +7,8 @@ from mako.template import Template from .._drivers.asgi import HTTPError +from .._drivers.time import BaseClock from .._p2p import FleetSensorSnapshot, VerifiedNodeInfo -from ..base.time import BaseClock from ..domain import Domain from ..logging import Logger from ..version import CodeInfo diff --git a/nucypher_async/proxy/_asgi_app.py b/nucypher_async/proxy/_asgi_app.py index 9149372..9e8cea1 100644 --- a/nucypher_async/proxy/_asgi_app.py +++ b/nucypher_async/proxy/_asgi_app.py @@ -49,7 +49,7 @@ async def stop(self, _nursery: trio.Nursery) -> None: await self._server.stop() async def get_ursulas(self, request: Request) -> JSONResponse: - request_body = await request.body_json() # TODO: add the query parameters here + request_body = await request.body_json() response = await self._server.get_ursulas(request.query_params, request_body) return JSONResponse(data=response) diff --git a/nucypher_async/proxy/_client.py b/nucypher_async/proxy/_client.py index 99d7f7f..8849230 100644 --- a/nucypher_async/proxy/_client.py +++ b/nucypher_async/proxy/_client.py @@ -3,12 +3,11 @@ from http import HTTPStatus from nucypher_core import Context, TreasureMap -from nucypher_core import RetrievalKit as CoreRetrievalKit from nucypher_core.umbral import PublicKey, VerifiedCapsuleFrag from .._drivers.http_client import HTTPClient from ..blockchain.identity import IdentityAddress -from ..characters.pre import DelegatorCard, MessageKit, RecipientCard, RetrievalKit +from ..characters.pre import DelegatorCard, EncryptedMessageMetadata, RecipientCard from ..client.pre import BasePREConsumerClient, PRERetrievalOutcome from . import _schema from ._schema import ClientRetrieveCFragsResponse, GetUrsulasResponse, RetrieveCFragsRequest @@ -54,14 +53,14 @@ async def get_nodes( async def retrieve_cfrags( self, treasure_map: TreasureMap, - retrieval_kits: Iterable[CoreRetrievalKit], + metadatas: Iterable[EncryptedMessageMetadata], delegator_card: DelegatorCard, recipient_card: RecipientCard, context: Context | None = None, ) -> list[dict[IdentityAddress, VerifiedCapsuleFrag]]: request = RetrieveCFragsRequest( treasure_map=treasure_map, - retrieval_kits=list(retrieval_kits), + retrieval_kits=[metadata.retrieval_kit for metadata in metadatas], alice_verifying_key=delegator_card.verifying_key, bob_encrypting_key=recipient_card.encrypting_key, bob_verifying_key=recipient_card.verifying_key, @@ -80,12 +79,12 @@ async def retrieve_cfrags( parsed_response = _schema.from_json(ClientRetrieveCFragsResponse, response.json) processed_response = [] - for rkit, result in zip( - retrieval_kits, parsed_response.result.retrieval_results, strict=True + for metadata, result in zip( + metadatas, parsed_response.result.retrieval_results, strict=True ): processed_result = { address: cfrag.verify( - rkit.capsule, + metadata.capsule, verifying_pk=delegator_card.verifying_key, delegating_pk=treasure_map.policy_encrypting_key, receiving_pk=recipient_card.encrypting_key, @@ -104,7 +103,7 @@ def __init__(self, proxy_host: str, proxy_port: int, http_client: HTTPClient | N async def retrieve( self, treasure_map: TreasureMap, - message_kit: MessageKit | RetrievalKit, + metadata: EncryptedMessageMetadata, delegator_card: DelegatorCard, recipient_card: RecipientCard, context: Context | None = None, @@ -112,15 +111,10 @@ async def retrieve( # TODO: support multi-step retrieval in Proxy # (that is, when some parts were already retrieved, # we can list those addresses in RetrievalKit) - # TODO: support retrieving multiple kits - retrieval_kits = [ - message_kit.core_retrieval_kit - if isinstance(message_kit, RetrievalKit) - else RetrievalKit.from_message_kit(message_kit).core_retrieval_kit - ] + # TODO (#50): support retrieving multiple kits cfrags = await self._proxy_client.retrieve_cfrags( - treasure_map, retrieval_kits, delegator_card, recipient_card, context + treasure_map, [metadata], delegator_card, recipient_card, context ) - # TODO: collect errors as well + # TODO (#51): collect errors as well return PRERetrievalOutcome(cfrags=cfrags[0], errors={}) diff --git a/nucypher_async/proxy/_config.py b/nucypher_async/proxy/_config.py index 49fced2..daf8754 100644 --- a/nucypher_async/proxy/_config.py +++ b/nucypher_async/proxy/_config.py @@ -5,9 +5,8 @@ from platformdirs import PlatformDirs from .._drivers.http_client import HTTPClient -from .._drivers.time import SystemClock +from .._drivers.time import BaseClock, SystemClock from .._p2p import BaseStorage, Contact, FileSystemStorage, InMemoryStorage, NodeClient -from ..base.time import BaseClock from ..blockchain.cbd import CBDClient from ..blockchain.identity import IdentityClient from ..blockchain.pre import PREClient diff --git a/nucypher_async/proxy/_schema.py b/nucypher_async/proxy/_schema.py index e08e441..f9779e6 100644 --- a/nucypher_async/proxy/_schema.py +++ b/nucypher_async/proxy/_schema.py @@ -1,27 +1,33 @@ -""" -What we need from a serializer/deserialized for JSON requests: -- deserialize from JSON into a fully typed structure with validation - (supporting Optional/List/Dict/etc types, and supporting custom types, like IdentityAddress) -- create the typed structure directly with all type info available for mypy -- serialize a typed structure to JSON - -Pydantic doesn't support serialization fully, Marshmallow requires creating a separate "schema" -type and creating a schema object is not type-checked. -Cattrs sort of works, but still requires some boilerplate (see below). - -TODO: an ideal schema library would support all that, and: -- fully declarative use with less boilerplate, where every schema class - would be derived from one base class; -- deserialization with context; -- anonymous dictionary fields with set keys, instead of creating a nested class as we do now. -""" - import base64 from collections.abc import Mapping -from typing import Any, TypeVar, cast - -import cattrs -from attrs import frozen +from dataclasses import dataclass +from types import NoneType, UnionType +from typing import Any, TypeVar, Union, cast + +from compages import ( + AsDataclassToDict, + AsDict, + AsInt, + AsList, + AsNone, + AsStr, + AsUnion, + DataclassBase, + IntoDataclassFromMapping, + IntoDict, + IntoList, + IntoNone, + IntoStr, + IntoUnion, + StructureHandler, + Structurer, + StructurerContext, + StructuringError, + UnstructureHandler, + Unstructurer, + UnstructurerContext, +) +from ethereum_rpc import Address from nucypher_core import Context, RetrievalKit, TreasureMap from nucypher_core.umbral import CapsuleFrag, PublicKey, VerifiedCapsuleFrag @@ -30,121 +36,155 @@ JSON = str | int | float | bool | None | list["JSON"] | dict[str, "JSON"] -def structure_identity_address(val: str, cls: type[IdentityAddress]) -> IdentityAddress: - return cls.from_hex(val) - - -def unstructure_identity_address(val: IdentityAddress) -> str: - return val.checksum - - -def structure_public_key(val: str, cls: type[PublicKey]) -> PublicKey: - return cls.from_compressed_bytes(bytes.fromhex(val)) - - -def unstructure_public_key(val: PublicKey) -> str: - return val.to_compressed_bytes().hex() - - -def structure_treasure_map(val: str, cls: type[TreasureMap]) -> TreasureMap: - return cls.from_bytes(base64.b64decode(val.encode())) - - -def unstructure_treasure_map(val: TreasureMap) -> str: - return base64.b64encode(bytes(val)).decode() - - -def structure_retrieval_kit(val: str, cls: type[RetrievalKit]) -> RetrievalKit: - return cls.from_bytes(base64.b64decode(val.encode())) - - -def unstructure_retrieval_kit(val: RetrievalKit) -> str: - return base64.b64encode(bytes(val)).decode() - - -def structure_cfrag(val: str, cls: type[CapsuleFrag]) -> CapsuleFrag: - return cls.from_bytes(base64.b64decode(val.encode())) - - -def unstructure_vcfrag(val: VerifiedCapsuleFrag) -> str: - return base64.b64encode(bytes(val)).decode() - - -def structure_context(val: str, cls: type[Context]) -> Context: - return cls(val) - - -def unstructure_context(val: Context) -> str: - return str(val) - - -_CONVERTER = cattrs.Converter() - -_CONVERTER.register_structure_hook(IdentityAddress, structure_identity_address) -_CONVERTER.register_unstructure_hook(IdentityAddress, unstructure_identity_address) - -_CONVERTER.register_structure_hook(PublicKey, structure_public_key) -_CONVERTER.register_unstructure_hook(PublicKey, unstructure_public_key) - -_CONVERTER.register_structure_hook(TreasureMap, structure_treasure_map) -_CONVERTER.register_unstructure_hook(TreasureMap, unstructure_treasure_map) - -_CONVERTER.register_structure_hook(RetrievalKit, structure_retrieval_kit) -_CONVERTER.register_unstructure_hook(RetrievalKit, unstructure_retrieval_kit) - -_CONVERTER.register_structure_hook(CapsuleFrag, structure_cfrag) -_CONVERTER.register_unstructure_hook(VerifiedCapsuleFrag, unstructure_vcfrag) - -_CONVERTER.register_structure_hook(Context, structure_context) -_CONVERTER.register_unstructure_hook(Context, unstructure_context) - - -class ValidationError(Exception): - pass - - -_FROM_JSON_T = TypeVar("_FROM_JSON_T") - - -def from_json(cls: type[_FROM_JSON_T], obj: JSON) -> _FROM_JSON_T: - # TODO: make validation errors more human-readable - try: - return _CONVERTER.structure(obj, cls) - except cattrs.BaseValidationError as exc: - raise ValidationError(str(exc)) from exc - - -def to_json(obj: Any) -> JSON: - # TODO: use the base class from `attrs` when the new version is released where it is public - return cast("JSON", _CONVERTER.unstructure(obj)) - +class _IntoInt(StructureHandler): + def structure(self, _context: StructurerContext, val: Any) -> int: + # Need to support both strings (coming from request parameters) + # and actual integers (coming from the JSON body) + if isinstance(val, int): + return val + if isinstance(val, str): + try: + return int(val) + except ValueError as exc: + raise StructuringError(f"Cannot parse `{val}` as integer") from exc + + raise StructuringError(f"Cannot parse a value of type `{type(val)}` as integer") + + +class _IntoPublicKey(StructureHandler): + def structure(self, _context: StructurerContext, val: Any) -> PublicKey: + if not isinstance(val, str): + raise StructuringError("Expected a string") + return PublicKey.from_compressed_bytes(bytes.fromhex(val)) + + +class _AsPublicKey(UnstructureHandler): + def unstructure(self, _context: UnstructurerContext, val: PublicKey) -> str: + return val.to_compressed_bytes().hex() + + +class _IntoObjFromBase64(StructureHandler): + def structure(self, context: StructurerContext, val: Any) -> Any: + if not isinstance(val, str): + raise StructuringError("Expected a string") + data = base64.b64decode(val.encode()) + # TODO: use a Protocol to make the expectations explicit? + return context.structure_into.from_bytes(data) # type: ignore[union-attr] + + +class _AsBase64(UnstructureHandler): + def unstructure(self, _context: UnstructurerContext, val: Any) -> str: + return base64.b64encode(bytes(val)).decode() + + +class _IntoContext(StructureHandler): + def structure(self, _context: StructurerContext, val: str) -> Context: + if not isinstance(val, str): + raise StructuringError("Expected a string") + return Context(val) + + +class _AsContext(UnstructureHandler): + def unstructure(self, _context: UnstructurerContext, val: Context) -> str: + return str(val) + + +class _IntoAddress(StructureHandler): + def structure(self, context: StructurerContext, val: str) -> Address: + structure_into = cast("type[Address]", context.structure_into) + return structure_into.from_hex(val) + + +class _AsAddress(UnstructureHandler): + def unstructure(self, _context: UnstructurerContext, val: Address) -> str: + return val.checksum + + +STRUCTURER = Structurer( + { + Address: _IntoAddress(), + PublicKey: _IntoPublicKey(), + TreasureMap: _IntoObjFromBase64(), + RetrievalKit: _IntoObjFromBase64(), + CapsuleFrag: _IntoObjFromBase64(), + Context: _IntoContext(), + int: _IntoInt(), + str: IntoStr(), + NoneType: IntoNone(), + list: IntoList(), + dict: IntoDict(), + UnionType: IntoUnion(), + Union: IntoUnion(), + DataclassBase: IntoDataclassFromMapping(), + }, +) + +UNSTRUCTURER = Unstructurer( + { + Address: _AsAddress(), + PublicKey: _AsPublicKey(), + TreasureMap: _AsBase64(), + RetrievalKit: _AsBase64(), + VerifiedCapsuleFrag: _AsBase64(), + Context: _AsContext(), + int: AsInt(), + str: AsStr(), + NoneType: AsNone(), + list: AsList(), + dict: AsDict(), + UnionType: AsUnion(), + Union: AsUnion(), + DataclassBase: AsDataclassToDict(), + }, +) + + +_T = TypeVar("_T") + + +def from_json(structure_into: type[_T], obj: JSON) -> _T: + """ + Structures incoming JSON data into the given Ethereum RPC type. + Raises :py:class:`compages.StructuringError` on failure. + """ + return STRUCTURER.structure_into(structure_into, obj) + -@frozen +def to_json(obj: Any, unstructure_as: Any = None) -> JSON: + """ + Unstructures a given Ethereum RPC entity into a JSON-serializable value. + Raises :py:class:`compages.UntructuringError` on failure. + """ + # The result is `JSON` by virtue of the hooks we defined + return cast("JSON", UNSTRUCTURER.unstructure_as(unstructure_as or type(obj), obj)) + + +@dataclass class UrsulaResult: checksum_address: IdentityAddress uri: str encrypting_key: PublicKey -@frozen +@dataclass class GetUrsulasResult: ursulas: list[UrsulaResult] -@frozen +@dataclass class GetUrsulasResponse: result: GetUrsulasResult version: str -@frozen +@dataclass class _GetUrsulasRequestAsQueryParams: quantity: int include_ursulas: str | None exclude_ursulas: str | None -@frozen +@dataclass class GetUrsulasRequest: quantity: int include_ursulas: list[IdentityAddress] | None @@ -156,7 +196,10 @@ def from_query_params(cls, params: Mapping[str, str]) -> "GetUrsulasRequest": Since `/get_ursulas` endpoint supports the request being passed through query params, and it's not exactly a fully structured JSON, we need a separate method to deserialize it. """ - typed_params = from_json(_GetUrsulasRequestAsQueryParams, cast("JSON", params)) + # TODO (https://github.com/fjarri-eth/compages/issues/18): have to convert it to dict + # since `StructureDictIntoDataclass` only applies to actual dicts, + # not to all `Mapping` implementors. + typed_params = from_json(_GetUrsulasRequestAsQueryParams, cast("JSON", dict(params))) if typed_params.include_ursulas: include_ursulas = typed_params.include_ursulas.split(",") @@ -176,7 +219,7 @@ def from_query_params(cls, params: Mapping[str, str]) -> "GetUrsulasRequest": return from_json(GetUrsulasRequest, cast("JSON", request_json)) -@frozen +@dataclass class RetrieveCFragsRequest: treasure_map: TreasureMap retrieval_kits: list[RetrievalKit] @@ -186,38 +229,39 @@ class RetrieveCFragsRequest: context: Context | None -# TODO: what would be nice to have is the support of "deserialization with context", +# TODO (https://github.com/fjarri-eth/compages/issues/19): what would be nice to have +# is the support of "deserialization with context", # allowing us e.g. to deserialize into VerifiedCapsuleFrag given all the verification keys. # for now we have to do with Client* and Server* structures. -@frozen +@dataclass class ServerRetrievalResult: cfrags: dict[IdentityAddress, VerifiedCapsuleFrag] -@frozen +@dataclass class ServerRetrieveCFragsResult: retrieval_results: list[ServerRetrievalResult] -@frozen +@dataclass class ServerRetrieveCFragsResponse: result: ServerRetrieveCFragsResult version: str -@frozen +@dataclass class ClientRetrievalResult: cfrags: dict[IdentityAddress, CapsuleFrag] -@frozen +@dataclass class ClientRetrieveCFragsResult: retrieval_results: list[ClientRetrievalResult] -@frozen +@dataclass class ClientRetrieveCFragsResponse: result: ClientRetrieveCFragsResult version: str diff --git a/nucypher_async/proxy/_server.py b/nucypher_async/proxy/_server.py index eb87ad7..e884936 100644 --- a/nucypher_async/proxy/_server.py +++ b/nucypher_async/proxy/_server.py @@ -1,15 +1,16 @@ +import dataclasses import http from collections.abc import Mapping from ipaddress import IPv4Address -import attrs import trio +from compages import StructuringError from .._drivers.asgi import HTTPError from .._drivers.http_server import HTTPServable from .._drivers.ssl import SSLCertificate, SSLPrivateKey from .._utils import BackgroundTask -from ..characters.pre import DelegatorCard, RecipientCard, RetrievalKit +from ..characters.pre import DelegatorCard, EncryptedMessageMetadata, RecipientCard from ..client.network import NetworkClient from ..client.pre import LocalPREClient from ..logging import Logger @@ -88,8 +89,7 @@ async def start(self, nursery: trio.Nursery) -> None: if self.started: raise RuntimeError("The loop is already started") - # TODO: get rid of ._learner access - await self._network_client._learner.seed_round(must_succeed=True) # noqa: SLF001 + await self._network_client._ensure_seeded() # noqa: SLF001 # TODO: make sure a proper cleanup happens if the start-up fails halfway self._verification_task.start(nursery) @@ -113,19 +113,20 @@ async def get_ursulas( ) -> JSON: try: request = GetUrsulasRequest.from_query_params(request_params) - except _schema.ValidationError as exc: + except StructuringError as exc: + self._logger.exception("Here") raise HTTPError(http.HTTPStatus.BAD_REQUEST, str(exc)) from exc if request_body is not None: try: request_from_body = _schema.from_json(GetUrsulasRequest, request_body) - except _schema.ValidationError as exc: + except StructuringError as exc: raise HTTPError(http.HTTPStatus.BAD_REQUEST, str(exc)) from exc # TODO: kind of weird. Who would use both query params and body? # Also, should GET request even support a body? # What does the reference implementation do? - request = attrs.evolve( + request = dataclasses.replace( request_from_body, quantity=request.quantity, include_ursulas=request.include_ursulas, @@ -151,7 +152,7 @@ async def get_ursulas( UrsulaResult( checksum_address=node.staking_provider_address, uri=node.contact.uri(), - encrypting_key=node.encrypting_key, + encrypting_key=node.pre_encrypting_key, ) for node in nodes ] @@ -164,15 +165,15 @@ async def get_ursulas( async def retrieve_cfrags(self, request_body: JSON) -> JSON: try: request = _schema.from_json(RetrieveCFragsRequest, request_body) - except _schema.ValidationError as exc: + except StructuringError as exc: raise HTTPError(http.HTTPStatus.BAD_REQUEST, str(exc)) from exc client = LocalPREClient(self._network_client, self._pre_client) - assert len(request.retrieval_kits) == 1 # TODO: support retrieving multiple kits + assert len(request.retrieval_kits) == 1 # TODO (#50): support retrieving multiple kits outcome = await client.retrieve( treasure_map=request.treasure_map, - message_kit=RetrievalKit(request.retrieval_kits[0]), + metadata=EncryptedMessageMetadata(request.retrieval_kits[0]), delegator_card=DelegatorCard(request.alice_verifying_key), recipient_card=RecipientCard(request.bob_encrypting_key, request.bob_verifying_key), ) diff --git a/nucypher_async/version.py b/nucypher_async/version.py index a1a61cc..1209ae5 100644 --- a/nucypher_async/version.py +++ b/nucypher_async/version.py @@ -8,7 +8,6 @@ def _run_in_project_dir(cmd: Sequence[str]) -> str: cwd = Path(__file__).parent - # TODO: use some library that queries git instead of just calling it results = subprocess.run(cmd, capture_output=True, cwd=cwd, check=True) # noqa: S603 return results.stdout.strip().decode() diff --git a/pdm.lock b/pdm.lock index 331946b..026d8c2 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "docs", "lint", "tests"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:cc787125317f4bfd8e59a4ed21fa9d3a527c0e127958bcf96e3ec668991f5528" +content_hash = "sha256:32d389f7ca7a269b3b266c8e9079ac20e8edce9e412a025cae9b63fa8d12c24c" [[metadata.targets]] requires_python = ">=3.10" @@ -72,19 +72,18 @@ files = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" requires_python = ">=3.9" summary = "High-level concurrency and networking framework on top of asyncio or Trio" groups = ["default", "tests"] dependencies = [ "exceptiongroup>=1.0.2; python_version < \"3.11\"", "idna>=2.8", - "sniffio>=1.1", "typing-extensions>=4.5; python_version < \"3.13\"", ] files = [ - {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, - {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, + {file = "anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb"}, + {file = "anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0"}, ] [[package]] @@ -130,17 +129,17 @@ files = [ [[package]] name = "beautifulsoup4" -version = "4.14.2" +version = "4.14.3" requires_python = ">=3.7.0" summary = "Screen-scraping library" groups = ["docs"] dependencies = [ - "soupsieve>1.2", + "soupsieve>=1.6.1", "typing-extensions>=4.0.0", ] files = [ - {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, - {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, + {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, + {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, ] [[package]] @@ -241,31 +240,15 @@ files = [ {file = "cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641"}, ] -[[package]] -name = "cattrs" -version = "25.3.0" -requires_python = ">=3.9" -summary = "Composable complex class support for attrs and dataclasses." -groups = ["default"] -dependencies = [ - "attrs>=25.4.0", - "exceptiongroup>=1.1.1; python_version < \"3.11\"", - "typing-extensions>=4.14.0", -] -files = [ - {file = "cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff"}, - {file = "cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a"}, -] - [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" requires_python = ">=3.7" summary = "Python package for providing Mozilla's CA Bundle." groups = ["default", "docs"] files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] @@ -274,7 +257,7 @@ version = "2.0.0" requires_python = ">=3.9" summary = "Foreign Function Interface for Python calling C code." groups = ["default", "tests"] -marker = "os_name == \"nt\" and implementation_name != \"pypy\" or platform_python_implementation != \"PyPy\"" +marker = "platform_python_implementation != \"PyPy\" and python_version >= \"3.9\" or os_name == \"nt\" and implementation_name != \"pypy\"" dependencies = [ "pycparser; implementation_name != \"PyPy\"", ] @@ -533,7 +516,7 @@ files = [ [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" requires_python = ">=3.10" summary = "Composable command line interface toolkit" groups = ["default"] @@ -541,8 +524,8 @@ dependencies = [ "colorama; platform_system == \"Windows\"", ] files = [ - {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, - {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [[package]] @@ -559,220 +542,220 @@ files = [ [[package]] name = "compages" -version = "0.3.0" +version = "0.4.0" requires_python = ">=3.10" summary = "Modular structurer/unstructurer" groups = ["default", "tests"] files = [ - {file = "compages-0.3.0-py3-none-any.whl", hash = "sha256:8fe1fc9de7ea3b75e966446be68639218a6d9f2320e879eecc65ddb5aec6edf1"}, - {file = "compages-0.3.0.tar.gz", hash = "sha256:3b02fcd9005e70fc796c83e07f6555fecfef24c4cd68194b7d371e49faca50a3"}, + {file = "compages-0.4.0-py3-none-any.whl", hash = "sha256:a6b92b128dfdafcc9d5e7b5c73ebc2a8624d76f84a632909bab2c8681f7e21f0"}, + {file = "compages-0.4.0.tar.gz", hash = "sha256:5784bb33a40ee13fc08985b4fd7972cf76670f85379a06206769ee7ccc31fd2b"}, ] [[package]] name = "coverage" -version = "7.11.2" +version = "7.13.0" requires_python = ">=3.10" summary = "Code coverage measurement for Python" groups = ["tests"] files = [ - {file = "coverage-7.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1"}, - {file = "coverage-7.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b"}, - {file = "coverage-7.11.2-cp310-cp310-win32.whl", hash = "sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785"}, - {file = "coverage-7.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493"}, - {file = "coverage-7.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f"}, - {file = "coverage-7.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6"}, - {file = "coverage-7.11.2-cp311-cp311-win32.whl", hash = "sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820"}, - {file = "coverage-7.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203"}, - {file = "coverage-7.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952"}, - {file = "coverage-7.11.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732"}, - {file = "coverage-7.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb"}, - {file = "coverage-7.11.2-cp312-cp312-win32.whl", hash = "sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527"}, - {file = "coverage-7.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0"}, - {file = "coverage-7.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc"}, - {file = "coverage-7.11.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad"}, - {file = "coverage-7.11.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148"}, - {file = "coverage-7.11.2-cp313-cp313-win32.whl", hash = "sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6"}, - {file = "coverage-7.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733"}, - {file = "coverage-7.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41"}, - {file = "coverage-7.11.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4"}, - {file = "coverage-7.11.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790"}, - {file = "coverage-7.11.2-cp313-cp313t-win32.whl", hash = "sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9"}, - {file = "coverage-7.11.2-cp313-cp313t-win_amd64.whl", hash = "sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37"}, - {file = "coverage-7.11.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5"}, - {file = "coverage-7.11.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006"}, - {file = "coverage-7.11.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1"}, - {file = "coverage-7.11.2-cp314-cp314-win32.whl", hash = "sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38"}, - {file = "coverage-7.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1"}, - {file = "coverage-7.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35"}, - {file = "coverage-7.11.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8"}, - {file = "coverage-7.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14"}, - {file = "coverage-7.11.2-cp314-cp314t-win32.whl", hash = "sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34"}, - {file = "coverage-7.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731"}, - {file = "coverage-7.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7"}, - {file = "coverage-7.11.2-py3-none-any.whl", hash = "sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c"}, - {file = "coverage-7.11.2.tar.gz", hash = "sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, + {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, + {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, + {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, + {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, + {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, + {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, + {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, + {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, + {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, + {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, + {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, + {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, + {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, + {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, + {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, + {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, + {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, + {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, + {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, + {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, + {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, + {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, ] [[package]] name = "coverage" -version = "7.11.2" +version = "7.13.0" extras = ["toml"] requires_python = ">=3.10" summary = "Code coverage measurement for Python" groups = ["tests"] dependencies = [ - "coverage==7.11.2", + "coverage==7.13.0", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ - {file = "coverage-7.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1"}, - {file = "coverage-7.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f"}, - {file = "coverage-7.11.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9"}, - {file = "coverage-7.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b"}, - {file = "coverage-7.11.2-cp310-cp310-win32.whl", hash = "sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785"}, - {file = "coverage-7.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493"}, - {file = "coverage-7.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f"}, - {file = "coverage-7.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d"}, - {file = "coverage-7.11.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b"}, - {file = "coverage-7.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6"}, - {file = "coverage-7.11.2-cp311-cp311-win32.whl", hash = "sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820"}, - {file = "coverage-7.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203"}, - {file = "coverage-7.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952"}, - {file = "coverage-7.11.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732"}, - {file = "coverage-7.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2"}, - {file = "coverage-7.11.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674"}, - {file = "coverage-7.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb"}, - {file = "coverage-7.11.2-cp312-cp312-win32.whl", hash = "sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527"}, - {file = "coverage-7.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0"}, - {file = "coverage-7.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc"}, - {file = "coverage-7.11.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad"}, - {file = "coverage-7.11.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050"}, - {file = "coverage-7.11.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c"}, - {file = "coverage-7.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148"}, - {file = "coverage-7.11.2-cp313-cp313-win32.whl", hash = "sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6"}, - {file = "coverage-7.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733"}, - {file = "coverage-7.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41"}, - {file = "coverage-7.11.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4"}, - {file = "coverage-7.11.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9"}, - {file = "coverage-7.11.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24"}, - {file = "coverage-7.11.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790"}, - {file = "coverage-7.11.2-cp313-cp313t-win32.whl", hash = "sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9"}, - {file = "coverage-7.11.2-cp313-cp313t-win_amd64.whl", hash = "sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37"}, - {file = "coverage-7.11.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5"}, - {file = "coverage-7.11.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006"}, - {file = "coverage-7.11.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599"}, - {file = "coverage-7.11.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84"}, - {file = "coverage-7.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1"}, - {file = "coverage-7.11.2-cp314-cp314-win32.whl", hash = "sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38"}, - {file = "coverage-7.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1"}, - {file = "coverage-7.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35"}, - {file = "coverage-7.11.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8"}, - {file = "coverage-7.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114"}, - {file = "coverage-7.11.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f"}, - {file = "coverage-7.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14"}, - {file = "coverage-7.11.2-cp314-cp314t-win32.whl", hash = "sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34"}, - {file = "coverage-7.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731"}, - {file = "coverage-7.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7"}, - {file = "coverage-7.11.2-py3-none-any.whl", hash = "sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c"}, - {file = "coverage-7.11.2.tar.gz", hash = "sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, + {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, + {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, + {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, + {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, + {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, + {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, + {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, + {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, + {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, + {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, + {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, + {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, + {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, + {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, + {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, + {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, + {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, + {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, + {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, + {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, + {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, + {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, ] [[package]] @@ -1022,8 +1005,8 @@ files = [ [[package]] name = "eth-abi" -version = "5.2.0" -requires_python = "<4,>=3.8" +version = "6.0.0b1" +requires_python = "<4,>=3.10" summary = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" groups = ["default", "tests"] dependencies = [ @@ -1032,8 +1015,7 @@ dependencies = [ "parsimonious<0.11.0,>=0.10.0", ] files = [ - {file = "eth_abi-5.2.0-py3-none-any.whl", hash = "sha256:17abe47560ad753f18054f5b3089fcb588f3e3a092136a416b6c1502cb7e8877"}, - {file = "eth_abi-5.2.0.tar.gz", hash = "sha256:178703fa98c07d8eecd5ae569e7e8d159e493ebb6eeb534a8fe973fbc4e40ef0"}, + {file = "eth_abi-6.0.0b1-py3-none-any.whl", hash = "sha256:b399cf9d431efa16df2ffa0be6e8615d31fe2e0b40a7c95b49dfcc1b18f41d29"}, ] [[package]] @@ -1182,22 +1164,21 @@ files = [ [[package]] name = "ethereum-rpc" -version = "0.1.1" +version = "0.2.0" requires_python = ">=3.10" summary = "Ethereum RPC types" groups = ["default", "tests"] dependencies = [ - "compages>=0.3", + "compages>=0.4", "pycryptodome>=3", ] files = [ - {file = "ethereum-rpc-0.1.1.tar.gz", hash = "sha256:b339597b9cd718dfa8a7eb0697f457a4168657d516bf531f4cc1bfa8d682e53f"}, - {file = "ethereum_rpc-0.1.1-py3-none-any.whl", hash = "sha256:47f1f6140d406c81569d1a52105b0d2d9a6aa5b8d9892b5e7eccc759458107d6"}, + {file = "ethereum_rpc-0.2.0-py3-none-any.whl", hash = "sha256:0a152ae0531ecba583f3f50029113f4ae95f9cb6396dfe552738adf71a2d784f"}, ] [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" groups = ["default", "lint", "tests"] @@ -1206,13 +1187,13 @@ dependencies = [ "typing-extensions>=4.6.0; python_version < \"3.13\"", ] files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] [[package]] name = "furo" -version = "2025.9.25" +version = "2025.12.19" requires_python = ">=3.8" summary = "A clean customisable Sphinx documentation theme." groups = ["docs"] @@ -1221,11 +1202,11 @@ dependencies = [ "beautifulsoup4", "pygments>=2.7", "sphinx-basic-ng>=1.0.0.beta2", - "sphinx<9.0,>=6.0", + "sphinx<10.0,>=7.0", ] files = [ - {file = "furo-2025.9.25-py3-none-any.whl", hash = "sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe"}, - {file = "furo-2025.9.25.tar.gz", hash = "sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98"}, + {file = "furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f"}, + {file = "furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7"}, ] [[package]] @@ -1310,13 +1291,13 @@ files = [ [[package]] name = "humanize" -version = "4.14.0" +version = "4.15.0" requires_python = ">=3.10" summary = "Python humanize utilities" groups = ["default"] files = [ - {file = "humanize-4.14.0-py3-none-any.whl", hash = "sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff"}, - {file = "humanize-4.14.0.tar.gz", hash = "sha256:2fa092705ea640d605c435b1ca82b2866a1b601cdf96f076d70b79a855eba90d"}, + {file = "humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769"}, + {file = "humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10"}, ] [[package]] @@ -1414,6 +1395,82 @@ files = [ {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] +[[package]] +name = "librt" +version = "0.7.4" +requires_python = ">=3.9" +summary = "Mypyc runtime library" +groups = ["lint"] +marker = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc300cb5a5a01947b1ee8099233156fdccd5001739e5f596ecfbc0dab07b5a3b"}, + {file = "librt-0.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee8d3323d921e0f6919918a97f9b5445a7dfe647270b2629ec1008aa676c0bc0"}, + {file = "librt-0.7.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:95cb80854a355b284c55f79674f6187cc9574df4dc362524e0cce98c89ee8331"}, + {file = "librt-0.7.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca1caedf8331d8ad6027f93b52d68ed8f8009f5c420c246a46fe9d3be06be0f"}, + {file = "librt-0.7.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a6f1236151e6fe1da289351b5b5bce49651c91554ecc7b70a947bced6fe212"}, + {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7766b57aeebaf3f1dac14fdd4a75c9a61f2ed56d8ebeefe4189db1cb9d2a3783"}, + {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1c4c89fb01157dd0a3bfe9e75cd6253b0a1678922befcd664eca0772a4c6c979"}, + {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7fa8beef580091c02b4fd26542de046b2abfe0aaefa02e8bcf68acb7618f2b3"}, + {file = "librt-0.7.4-cp310-cp310-win32.whl", hash = "sha256:543c42fa242faae0466fe72d297976f3c710a357a219b1efde3a0539a68a6997"}, + {file = "librt-0.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:25cc40d8eb63f0a7ea4c8f49f524989b9df901969cb860a2bc0e4bad4b8cb8a8"}, + {file = "librt-0.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3485b9bb7dfa66167d5500ffdafdc35415b45f0da06c75eb7df131f3357b174a"}, + {file = "librt-0.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:188b4b1a770f7f95ea035d5bbb9d7367248fc9d12321deef78a269ebf46a5729"}, + {file = "librt-0.7.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b668b1c840183e4e38ed5a99f62fac44c3a3eef16870f7f17cfdfb8b47550ed"}, + {file = "librt-0.7.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e8f864b521f6cfedb314d171630f827efee08f5c3462bcbc2244ab8e1768cd6"}, + {file = "librt-0.7.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df7c9def4fc619a9c2ab402d73a0c5b53899abe090e0100323b13ccb5a3dd82"}, + {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f79bc3595b6ed159a1bf0cdc70ed6ebec393a874565cab7088a219cca14da727"}, + {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77772a4b8b5f77d47d883846928c36d730b6e612a6388c74cba33ad9eb149c11"}, + {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:064a286e6ab0b4c900e228ab4fa9cb3811b4b83d3e0cc5cd816b2d0f548cb61c"}, + {file = "librt-0.7.4-cp311-cp311-win32.whl", hash = "sha256:42da201c47c77b6cc91fc17e0e2b330154428d35d6024f3278aa2683e7e2daf2"}, + {file = "librt-0.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:d31acb5886c16ae1711741f22504195af46edec8315fe69b77e477682a87a83e"}, + {file = "librt-0.7.4-cp311-cp311-win_arm64.whl", hash = "sha256:114722f35093da080a333b3834fff04ef43147577ed99dd4db574b03a5f7d170"}, + {file = "librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92"}, + {file = "librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108"}, + {file = "librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94"}, + {file = "librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab"}, + {file = "librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba"}, + {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9"}, + {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74"}, + {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f"}, + {file = "librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286"}, + {file = "librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20"}, + {file = "librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a"}, + {file = "librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b"}, + {file = "librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32"}, + {file = "librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67"}, + {file = "librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20"}, + {file = "librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74"}, + {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee"}, + {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681"}, + {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c"}, + {file = "librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2"}, + {file = "librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e"}, + {file = "librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788"}, + {file = "librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d"}, + {file = "librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23"}, + {file = "librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063"}, + {file = "librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848"}, + {file = "librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843"}, + {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a"}, + {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16"}, + {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce"}, + {file = "librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f"}, + {file = "librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a"}, + {file = "librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44"}, + {file = "librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105"}, + {file = "librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4"}, + {file = "librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a"}, + {file = "librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95"}, + {file = "librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906"}, + {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf"}, + {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f"}, + {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5"}, + {file = "librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb"}, + {file = "librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481"}, + {file = "librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f"}, + {file = "librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba"}, +] + [[package]] name = "lru-dict" version = "1.4.1" @@ -1622,49 +1679,50 @@ files = [ [[package]] name = "mypy" -version = "1.18.2" +version = "1.19.1" requires_python = ">=3.9" summary = "Optional static typing for Python" groups = ["lint"] dependencies = [ + "librt>=0.6.2; platform_python_implementation != \"PyPy\"", "mypy-extensions>=1.0.0", "pathspec>=0.9.0", "tomli>=1.1.0; python_version < \"3.11\"", "typing-extensions>=4.6.0", ] files = [ - {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, - {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, - {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, - {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, - {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, - {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, - {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, - {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, - {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, - {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, - {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, - {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, - {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, - {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, - {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, - {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, - {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, - {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, - {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, - {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, - {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, - {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, - {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, - {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, - {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, - {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, - {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, - {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, - {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, - {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, - {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, - {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [[package]] @@ -1747,13 +1805,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.5.1" requires_python = ">=3.10" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." groups = ["default"] files = [ - {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, - {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, + {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, + {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, ] [[package]] @@ -1874,7 +1932,7 @@ version = "2.23" requires_python = ">=3.8" summary = "C parser in Python" groups = ["default", "tests"] -marker = "os_name == \"nt\" and (implementation_name != \"pypy\" and implementation_name != \"PyPy\") or platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" +marker = "platform_python_implementation != \"PyPy\" and python_version >= \"3.9\" and implementation_name != \"PyPy\" or os_name == \"nt\" and (implementation_name != \"pypy\" and implementation_name != \"PyPy\")" files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, @@ -1919,7 +1977,7 @@ files = [ [[package]] name = "pydantic" -version = "2.12.4" +version = "2.12.5" requires_python = ">=3.9" summary = "Data validation using Python type hints" groups = ["default", "tests"] @@ -1930,8 +1988,8 @@ dependencies = [ "typing-inspection>=0.4.2", ] files = [ - {file = "pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}, - {file = "pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [[package]] @@ -2059,47 +2117,47 @@ files = [ [[package]] name = "pynacl" -version = "1.6.0" +version = "1.6.1" requires_python = ">=3.8" summary = "Python binding to the Networking and Cryptography (NaCl) library" groups = ["default"] dependencies = [ - "cffi>=1.4.1; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", - "cffi>=2.0.0; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\"", -] -files = [ - {file = "pynacl-1.6.0-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:f46386c24a65383a9081d68e9c2de909b1834ec74ff3013271f1bca9c2d233eb"}, - {file = "pynacl-1.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dea103a1afcbc333bc0e992e64233d360d393d1e63d0bc88554f572365664348"}, - {file = "pynacl-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:04f20784083014e265ad58c1b2dd562c3e35864b5394a14ab54f5d150ee9e53e"}, - {file = "pynacl-1.6.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbcc4452a1eb10cd5217318c822fde4be279c9de8567f78bad24c773c21254f8"}, - {file = "pynacl-1.6.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fed9fe1bec9e7ff9af31cd0abba179d0e984a2960c77e8e5292c7e9b7f7b5d"}, - {file = "pynacl-1.6.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:10d755cf2a455d8c0f8c767a43d68f24d163b8fe93ccfaabfa7bafd26be58d73"}, - {file = "pynacl-1.6.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:536703b8f90e911294831a7fbcd0c062b837f3ccaa923d92a6254e11178aaf42"}, - {file = "pynacl-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6b08eab48c9669d515a344fb0ef27e2cbde847721e34bba94a343baa0f33f1f4"}, - {file = "pynacl-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5789f016e08e5606803161ba24de01b5a345d24590a80323379fc4408832d290"}, - {file = "pynacl-1.6.0-cp314-cp314t-win32.whl", hash = "sha256:4853c154dc16ea12f8f3ee4b7e763331876316cc3a9f06aeedf39bcdca8f9995"}, - {file = "pynacl-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:347dcddce0b4d83ed3f32fd00379c83c425abee5a9d2cd0a2c84871334eaff64"}, - {file = "pynacl-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2d6cd56ce4998cb66a6c112fda7b1fdce5266c9f05044fa72972613bef376d15"}, - {file = "pynacl-1.6.0-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:f4b3824920e206b4f52abd7de621ea7a44fd3cb5c8daceb7c3612345dfc54f2e"}, - {file = "pynacl-1.6.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16dd347cdc8ae0b0f6187a2608c0af1c8b7ecbbe6b4a06bff8253c192f696990"}, - {file = "pynacl-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16c60daceee88d04f8d41d0a4004a7ed8d9a5126b997efd2933e08e93a3bd850"}, - {file = "pynacl-1.6.0-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25720bad35dfac34a2bcdd61d9e08d6bfc6041bebc7751d9c9f2446cf1e77d64"}, - {file = "pynacl-1.6.0-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bfaa0a28a1ab718bad6239979a5a57a8d1506d0caf2fba17e524dbb409441cf"}, - {file = "pynacl-1.6.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ef214b90556bb46a485b7da8258e59204c244b1b5b576fb71848819b468c44a7"}, - {file = "pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:49c336dd80ea54780bcff6a03ee1a476be1612423010472e60af83452aa0f442"}, - {file = "pynacl-1.6.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f3482abf0f9815e7246d461fab597aa179b7524628a4bc36f86a7dc418d2608d"}, - {file = "pynacl-1.6.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:140373378e34a1f6977e573033d1dd1de88d2a5d90ec6958c9485b2fd9f3eb90"}, - {file = "pynacl-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6b393bc5e5a0eb86bb85b533deb2d2c815666665f840a09e0aa3362bb6088736"}, - {file = "pynacl-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a25cfede801f01e54179b8ff9514bd7b5944da560b7040939732d1804d25419"}, - {file = "pynacl-1.6.0-cp38-abi3-win32.whl", hash = "sha256:dcdeb41c22ff3c66eef5e63049abf7639e0db4edee57ba70531fc1b6b133185d"}, - {file = "pynacl-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:cf831615cc16ba324240de79d925eacae8265b7691412ac6b24221db157f6bd1"}, - {file = "pynacl-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:84709cea8f888e618c21ed9a0efdb1a59cc63141c403db8bf56c469b71ad56f2"}, - {file = "pynacl-1.6.0.tar.gz", hash = "sha256:cb36deafe6e2bce3b286e5d1f3e1c246e0ccdb8808ddb4550bb2792f2df298f2"}, + "cffi>=1.4.1; platform_python_implementation != \"PyPy\" and python_version < \"3.9\"", + "cffi>=2.0.0; platform_python_implementation != \"PyPy\" and python_version >= \"3.9\"", +] +files = [ + {file = "pynacl-1.6.1-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:7d7c09749450c385301a3c20dca967a525152ae4608c0a096fe8464bfc3df93d"}, + {file = "pynacl-1.6.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc734c1696ffd49b40f7c1779c89ba908157c57345cf626be2e0719488a076d3"}, + {file = "pynacl-1.6.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3cd787ec1f5c155dc8ecf39b1333cfef41415dc96d392f1ce288b4fe970df489"}, + {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b35d93ab2df03ecb3aa506be0d3c73609a51449ae0855c2e89c7ed44abde40b"}, + {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dece79aecbb8f4640a1adbb81e4aa3bfb0e98e99834884a80eb3f33c7c30e708"}, + {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c2228054f04bf32d558fb89bb99f163a8197d5a9bf4efa13069a7fa8d4b93fc3"}, + {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:2b12f1b97346f177affcdfdc78875ff42637cb40dcf79484a97dae3448083a78"}, + {file = "pynacl-1.6.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e735c3a1bdfde3834503baf1a6d74d4a143920281cb724ba29fb84c9f49b9c48"}, + {file = "pynacl-1.6.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3384a454adf5d716a9fadcb5eb2e3e72cd49302d1374a60edc531c9957a9b014"}, + {file = "pynacl-1.6.1-cp314-cp314t-win32.whl", hash = "sha256:d8615ee34d01c8e0ab3f302dcdd7b32e2bcf698ba5f4809e7cc407c8cdea7717"}, + {file = "pynacl-1.6.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5f5b35c1a266f8a9ad22525049280a600b19edd1f785bccd01ae838437dcf935"}, + {file = "pynacl-1.6.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d984c91fe3494793b2a1fb1e91429539c6c28e9ec8209d26d25041ec599ccf63"}, + {file = "pynacl-1.6.1-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:a6f9fd6d6639b1e81115c7f8ff16b8dedba1e8098d2756275d63d208b0e32021"}, + {file = "pynacl-1.6.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e49a3f3d0da9f79c1bec2aa013261ab9fa651c7da045d376bd306cf7c1792993"}, + {file = "pynacl-1.6.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7713f8977b5d25f54a811ec9efa2738ac592e846dd6e8a4d3f7578346a841078"}, + {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a3becafc1ee2e5ea7f9abc642f56b82dcf5be69b961e782a96ea52b55d8a9fc"}, + {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ce50d19f1566c391fedc8dc2f2f5be265ae214112ebe55315e41d1f36a7f0a9"}, + {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:543f869140f67d42b9b8d47f922552d7a967e6c116aad028c9bfc5f3f3b3a7b7"}, + {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a2bb472458c7ca959aeeff8401b8efef329b0fc44a89d3775cffe8fad3398ad8"}, + {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3206fa98737fdc66d59b8782cecc3d37d30aeec4593d1c8c145825a345bba0f0"}, + {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:53543b4f3d8acb344f75fd4d49f75e6572fce139f4bfb4815a9282296ff9f4c0"}, + {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:319de653ef84c4f04e045eb250e6101d23132372b0a61a7acf91bac0fda8e58c"}, + {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:262a8de6bba4aee8a66f5edf62c214b06647461c9b6b641f8cd0cb1e3b3196fe"}, + {file = "pynacl-1.6.1-cp38-abi3-win32.whl", hash = "sha256:9fd1a4eb03caf8a2fe27b515a998d26923adb9ddb68db78e35ca2875a3830dde"}, + {file = "pynacl-1.6.1-cp38-abi3-win_amd64.whl", hash = "sha256:a569a4069a7855f963940040f35e87d8bc084cb2d6347428d5ad20550a0a1a21"}, + {file = "pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf"}, + {file = "pynacl-1.6.1.tar.gz", hash = "sha256:8d361dac0309f2b6ad33b349a56cd163c98430d409fa503b10b70b3ad66eaa1d"}, ] [[package]] name = "pytest" -version = "9.0.0" +version = "9.0.2" requires_python = ">=3.10" summary = "pytest: simple powerful testing with Python" groups = ["lint", "tests"] @@ -2113,8 +2171,8 @@ dependencies = [ "tomli>=1; python_version < \"3.11\"", ] files = [ - {file = "pytest-9.0.0-py3-none-any.whl", hash = "sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96"}, - {file = "pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e"}, + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, ] [[package]] @@ -2305,30 +2363,30 @@ files = [ [[package]] name = "ruff" -version = "0.14.4" +version = "0.14.10" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["lint"] files = [ - {file = "ruff-0.14.4-py3-none-linux_armv6l.whl", hash = "sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518"}, - {file = "ruff-0.14.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4"}, - {file = "ruff-0.14.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5"}, - {file = "ruff-0.14.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132"}, - {file = "ruff-0.14.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67"}, - {file = "ruff-0.14.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469"}, - {file = "ruff-0.14.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde"}, - {file = "ruff-0.14.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349"}, - {file = "ruff-0.14.4-py3-none-win32.whl", hash = "sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff"}, - {file = "ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c"}, - {file = "ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb"}, - {file = "ruff-0.14.4.tar.gz", hash = "sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3"}, + {file = "ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49"}, + {file = "ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f"}, + {file = "ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f"}, + {file = "ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f"}, + {file = "ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d"}, + {file = "ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405"}, + {file = "ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60"}, + {file = "ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830"}, + {file = "ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6"}, + {file = "ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154"}, + {file = "ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6"}, + {file = "ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4"}, ] [[package]] @@ -2387,13 +2445,13 @@ files = [ [[package]] name = "soupsieve" -version = "2.8" +version = "2.8.1" requires_python = ">=3.9" summary = "A modern CSS selector implementation for Beautiful Soup." groups = ["docs"] files = [ - {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, - {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, + {file = "soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434"}, + {file = "soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350"}, ] [[package]] @@ -2640,24 +2698,24 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20251108" +version = "2.9.0.20251115" requires_python = ">=3.9" summary = "Typing stubs for python-dateutil" groups = ["lint"] files = [ - {file = "types_python_dateutil-2.9.0.20251108-py3-none-any.whl", hash = "sha256:a4a537f0ea7126f8ccc2763eec9aa31ac8609e3c8e530eb2ddc5ee234b3cd764"}, - {file = "types_python_dateutil-2.9.0.20251108.tar.gz", hash = "sha256:d8a6687e197f2fa71779ce36176c666841f811368710ab8d274b876424ebfcaa"}, + {file = "types_python_dateutil-2.9.0.20251115-py3-none-any.whl", hash = "sha256:9cf9c1c582019753b8639a081deefd7e044b9fa36bd8217f565c6c4e36ee0624"}, + {file = "types_python_dateutil-2.9.0.20251115.tar.gz", hash = "sha256:8a47f2c3920f52a994056b8786309b43143faa5a64d4cbb2722d6addabdf1a58"}, ] [[package]] name = "types-setuptools" -version = "80.9.0.20250822" +version = "80.9.0.20251223" requires_python = ">=3.9" summary = "Typing stubs for setuptools" groups = ["lint"] files = [ - {file = "types_setuptools-80.9.0.20250822-py3-none-any.whl", hash = "sha256:53bf881cb9d7e46ed12c76ef76c0aaf28cfe6211d3fab12e0b83620b1a8642c3"}, - {file = "types_setuptools-80.9.0.20250822.tar.gz", hash = "sha256:070ea7716968ec67a84c7f7768d9952ff24d28b65b6594797a464f1b3066f965"}, + {file = "types_setuptools-80.9.0.20251223-py3-none-any.whl", hash = "sha256:1b36db79d724c2287d83dc052cf887b47c0da6a2fff044378be0b019545f56e6"}, + {file = "types_setuptools-80.9.0.20251223.tar.gz", hash = "sha256:d3411059ae2f5f03985217d86ac6084efea2c9e9cacd5f0869ef950f308169b2"}, ] [[package]] @@ -2687,37 +2745,37 @@ files = [ [[package]] name = "tzdata" -version = "2025.2" +version = "2025.3" requires_python = ">=2" summary = "Provider of IANA time zone data" groups = ["default"] marker = "python_version >= \"3.9\"" files = [ - {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, - {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, + {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, + {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, ] [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" requires_python = ">=3.9" summary = "HTTP library with thread-safe connection pooling, file post, and more." groups = ["docs"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"}, + {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"}, ] [[package]] name = "wsproto" -version = "1.2.0" -requires_python = ">=3.7.0" -summary = "WebSockets state-machine based protocol implementation" +version = "1.3.2" +requires_python = ">=3.10" +summary = "Pure-Python WebSocket protocol implementation" groups = ["default"] dependencies = [ - "h11<1,>=0.9.0", + "h11<1,>=0.16.0", ] files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, + {file = "wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584"}, + {file = "wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294"}, ] diff --git a/pyproject.toml b/pyproject.toml index 5d335e3..688083d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,14 +16,14 @@ dependencies = [ "mnemonic>=0.20", "eth-account>=0.6", "pons[http-provider]>=0.11", - "ethereum_rpc>=0.1.1", + "ethereum_rpc>=0.2", "platformdirs>=2", "mako>=1", "sortedcontainers>=2.4", "arrow>=1", "humanize>=4.3", "attrs>=22", - "cattrs>=22", + "compages>=0.4", "click>=8", ] requires-python = ">=3.10" diff --git a/tests/conftest.py b/tests/conftest.py index 633c013..836ceda 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import itertools import os -from collections.abc import AsyncIterator +from collections.abc import AsyncIterator, Callable from ipaddress import IPv4Address import pytest @@ -24,21 +24,35 @@ from nucypher_async.characters import MasterKey from nucypher_async.characters.cbd import Decryptor from nucypher_async.characters.pre import Reencryptor +from nucypher_async.client.network import NetworkClient +from nucypher_async.client.pre import LocalPREClient from nucypher_async.domain import Domain from nucypher_async.node import HTTPServerConfig, NodeServer, NodeServerConfig, SSLConfig -from nucypher_async.proxy import ProxyServer, ProxyServerConfig +from nucypher_async.proxy import ProxyPREClient, ProxyServer, ProxyServerConfig +from nucypher_async.proxy._client import ProxyClient + + +def pytest_addoption(parser: pytest.Parser) -> None: + parser.addoption( + "--loglevel", + action="store", + choices=["debug", "info"], + default="debug", + help="Log level used in the logging fixture", + ) @pytest.fixture(scope="session") -def logger() -> logging.Logger: - # TODO: we may add a CLI option to reduce the verbosity of test logging +def logger(pytestconfig: pytest.Config) -> logging.Logger: + loglevel = pytestconfig.getoption("--loglevel") + level = logging.DEBUG if loglevel == "debug" else logging.INFO return logging.Logger( - level=logging.DEBUG, handlers=[logging.ConsoleHandler(stderr_at=None)], clock=MockClock() + level=level, handlers=[logging.ConsoleHandler(stderr_at=None)], clock=MockClock() ) @pytest.fixture -async def mock_clock() -> MockClock: +def mock_clock() -> MockClock: return MockClock() @@ -84,7 +98,7 @@ def mock_http_network(nursery: trio.Nursery) -> MockHTTPNetwork: @pytest.fixture def mock_passive_http_client(mock_http_network: MockHTTPNetwork) -> MockHTTPClient: - return MockHTTPClient(mock_http_network, host=None) + return MockHTTPClient(mock_http_network, client_host=None) @pytest.fixture @@ -191,10 +205,10 @@ async def fully_learned_nodes( if other_server is server: continue - peer_info = other_server._node # TODO: add a proper method to NodeServer + node_info = other_server.info async with mock_identity_client.session() as session: - stake = await session.get_staked_amount(peer_info.staking_provider_address) - server.learner._test_add_verified_node(peer_info, stake) + stake = await session.get_staked_amount(node_info.staking_provider_address) + server.learner._test_add_verified_node(node_info, stake) for handle, _server in lonely_nodes: await handle.startup() @@ -253,3 +267,55 @@ async def proxy_server( await handle.startup() yield server await handle.shutdown() + + +@pytest.fixture +async def proxy_client( + mock_passive_http_client: MockHTTPClient, proxy_server: ProxyServer +) -> ProxyClient: + host, port = proxy_server.bind_pair() + return ProxyClient(str(host), port, mock_passive_http_client) + + +@pytest.fixture +async def proxy_pre_client( + mock_passive_http_client: MockHTTPClient, proxy_server: ProxyServer +) -> ProxyPREClient: + host, port = proxy_server.bind_pair() + return ProxyPREClient(str(host), port, mock_passive_http_client) + + +@pytest.fixture +async def network_client_factory( + mock_passive_node_client: MockNodeClient, + mock_identity_client: MockIdentityClient, + fully_learned_nodes: list[NodeServer], + logger: logging.Logger, + mock_clock: MockClock, +) -> Callable[[str], NetworkClient]: + # We want several instances of NetworkClient to be independent + # (since they are very much stateful), + # so we return a factory instead of an actual instance. + # This way a test can create several of those. + def factory(logger_tag: str) -> NetworkClient: + return NetworkClient( + node_client=mock_passive_node_client, + identity_client=mock_identity_client, + seed_contacts=[fully_learned_nodes[0].secure_contact().contact], + domain=Domain.MAINNET, + clock=mock_clock, + parent_logger=logger.get_child(logger_tag), + ) + + return factory + + +@pytest.fixture +async def local_pre_client_factory( + network_client_factory: Callable[[str], NetworkClient], + mock_pre_client: MockPREClient, +) -> Callable[[str], LocalPREClient]: + def factory(logger_tag: str) -> LocalPREClient: + return LocalPREClient(network_client_factory(logger_tag), mock_pre_client) + + return factory diff --git a/tests/test_characters/test_pre.py b/tests/test_characters/test_pre.py index db58fb2..9b4fbad 100644 --- a/tests/test_characters/test_pre.py +++ b/tests/test_characters/test_pre.py @@ -6,7 +6,7 @@ from nucypher_async.characters.pre import ( DecryptionKit, Delegator, - Encryptor, + EncryptedMessage, Publisher, Recipient, Reencryptor, @@ -59,12 +59,12 @@ def test_grant_and_retrieve() -> None: # Someone encrypts a message message = b"a secret message" - message_kit = Encryptor.encrypt(policy, message) + encrypted_message = EncryptedMessage(policy, message) # Bob decrypts treasure_map = bob.decrypt_treasure_map(encrypted_treasure_map, publisher.card()) - decryption_kit = DecryptionKit(message_kit, treasure_map) + decryption_kit = DecryptionKit(encrypted_message, treasure_map) vcfrags = [] for node in nodes: diff --git a/tests/test_node.py b/tests/test_node.py index 8431417..1c82e4d 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -8,16 +8,16 @@ async def test_learning( autojump_clock: trio.testing.MockClock, # noqa: ARG001 chain_seeded_nodes: list[NodeServer], ) -> None: + # Wait multiple learning cycles while True: - # Wait multiple learning cycles - # TODO: find a way to wait until the learning is done, and measure how much time has passed await trio.sleep(100) + if all(server.learner.has_no_new_contacts() for server in chain_seeded_nodes): + break - known_nodes = { - server._node.staking_provider_address: server.learner.get_verified_nodes() - for server in chain_seeded_nodes - } + known_nodes = { + server.info.staking_provider_address: server.learner.get_verified_nodes() + for server in chain_seeded_nodes + } - # Each node should know about every other node by now. - if all(len(nodes) == 9 for nodes in known_nodes.values()): - break + # Each node should know about every other node by now. + assert all(len(nodes) == 9 for nodes in known_nodes.values()) diff --git a/tests/test_pre.py b/tests/test_pre.py index c1f1aaf..c4e389a 100644 --- a/tests/test_pre.py +++ b/tests/test_pre.py @@ -1,34 +1,23 @@ +from collections.abc import Callable + import trio import trio.testing -from nucypher_async._mocks import MockClock, MockIdentityClient, MockNodeClient, MockPREClient +from nucypher_async._mocks import MockPREClient from nucypher_async.blockchain.pre import PREAccount, PREAccountSigner, PREAmount -from nucypher_async.characters.pre import Delegator, Publisher, Recipient +from nucypher_async.characters.pre import Delegator, EncryptedMessage, Publisher, Recipient from nucypher_async.client.network import NetworkClient -from nucypher_async.client.pre import LocalPREClient, pre_encrypt -from nucypher_async.domain import Domain -from nucypher_async.logging import Logger +from nucypher_async.client.pre import LocalPREClient from nucypher_async.node import NodeServer async def test_verified_nodes_iter( autojump_clock: trio.testing.MockClock, # noqa: ARG001 fully_learned_nodes: list[NodeServer], - mock_passive_node_client: MockNodeClient, - mock_identity_client: MockIdentityClient, - logger: Logger, - mock_clock: MockClock, + network_client_factory: Callable[[str], NetworkClient], ) -> None: - network_client = NetworkClient( - domain=Domain.MAINNET, - node_client=mock_passive_node_client, - identity_client=mock_identity_client, - seed_contacts=[fully_learned_nodes[0].secure_contact().contact], - parent_logger=logger, - clock=mock_clock, - ) - - addresses = [server._node.staking_provider_address for server in fully_learned_nodes[:3]] + network_client = network_client_factory("") + addresses = [server.info.staking_provider_address for server in fully_learned_nodes[:3]] nodes = [] with trio.fail_after(10): @@ -40,29 +29,15 @@ async def test_verified_nodes_iter( async def test_granting( autojump_clock: trio.testing.MockClock, # noqa: ARG001 - fully_learned_nodes: list[NodeServer], - mock_passive_node_client: MockNodeClient, - mock_identity_client: MockIdentityClient, mock_pre_client: MockPREClient, - mock_clock: MockClock, - logger: Logger, + local_pre_client_factory: Callable[[str], LocalPREClient], ) -> None: alice = Delegator.random() publisher = Publisher.random() publisher_signer = PREAccountSigner(PREAccount.random()) bob = Recipient.random() - publisher_client = LocalPREClient( - NetworkClient( - node_client=mock_passive_node_client, - identity_client=mock_identity_client, - seed_contacts=[fully_learned_nodes[0].secure_contact().contact], - domain=Domain.MAINNET, - clock=mock_clock, - parent_logger=logger.get_child("Publisher"), - ), - pre_client=mock_pre_client, - ) + publisher_client = local_pre_client_factory("Publisher") # Fund the publisher mock_pre_client.mock_set_balance(publisher_signer.address, PREAmount.ether(1)) @@ -83,25 +58,15 @@ async def test_granting( ) message = b"a secret message" - message_kit = pre_encrypt(policy, message) - - bob_client = LocalPREClient( - NetworkClient( - node_client=mock_passive_node_client, - identity_client=mock_identity_client, - seed_contacts=[fully_learned_nodes[0].secure_contact().contact], - domain=Domain.MAINNET, - clock=mock_clock, - parent_logger=logger.get_child("Recipient"), - ), - pre_client=mock_pre_client, - ) + encrypted_message = EncryptedMessage(policy, message) + + bob_client = local_pre_client_factory("Recipient") with trio.fail_after(10): decrypted = await bob_client.decrypt( recipient=bob, enacted_policy=enacted_policy, - message_kit=message_kit, + encrypted_message=encrypted_message, delegator_card=alice.card(), publisher_card=publisher.card(), ) diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 723ed22..2746bc6 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -1,36 +1,25 @@ +from collections.abc import Callable + import trio import trio.testing -from nucypher_async._mocks import ( - MockClock, - MockHTTPClient, - MockIdentityClient, - MockNodeClient, - MockPREClient, -) +from nucypher_async._mocks import MockPREClient from nucypher_async.blockchain.pre import PREAccount, PREAccountSigner, PREAmount -from nucypher_async.characters.pre import Delegator, Publisher, Recipient -from nucypher_async.client.network import NetworkClient -from nucypher_async.client.pre import LocalPREClient, pre_encrypt -from nucypher_async.domain import Domain +from nucypher_async.characters.pre import Delegator, EncryptedMessage, Publisher, Recipient +from nucypher_async.client.pre import LocalPREClient from nucypher_async.node import NodeServer -from nucypher_async.proxy import ProxyPREClient, ProxyServer +from nucypher_async.proxy import ProxyPREClient from nucypher_async.proxy._client import ProxyClient async def test_get_nodes( - mock_passive_http_client: MockHTTPClient, - fully_learned_nodes: list[NodeServer], - # TODO: make the fixture return a handle or something, so we can actually use it? - # Or `ProxyClient`? - proxy_server: ProxyServer, # noqa: ARG001 autojump_clock: trio.testing.MockClock, # noqa: ARG001 + fully_learned_nodes: list[NodeServer], + proxy_client: ProxyClient, ) -> None: - proxy_client = ProxyClient("127.0.0.1", 9000, mock_passive_http_client) - some_nodes = [ - fully_learned_nodes[3]._node.staking_provider_address, - fully_learned_nodes[7]._node.staking_provider_address, + fully_learned_nodes[3].info.staking_provider_address, + fully_learned_nodes[7].info.staking_provider_address, ] nodes = await proxy_client.get_nodes(quantity=3, include_nodes=some_nodes) assert len(nodes) == 3 @@ -42,30 +31,17 @@ async def test_get_nodes( async def test_retrieve_cfrags( - mock_passive_node_client: MockNodeClient, - mock_passive_http_client: MockHTTPClient, - mock_identity_client: MockIdentityClient, - mock_pre_client: MockPREClient, - fully_learned_nodes: list[NodeServer], - proxy_server: ProxyServer, # noqa: ARG001 autojump_clock: trio.testing.MockClock, # noqa: ARG001 - mock_clock: MockClock, + mock_pre_client: MockPREClient, + proxy_pre_client: ProxyPREClient, + local_pre_client_factory: Callable[[str], LocalPREClient], ) -> None: alice = Delegator.random() publisher = Publisher.random() publisher_signer = PREAccountSigner(PREAccount.random()) bob = Recipient.random() - publisher_client = LocalPREClient( - NetworkClient( - node_client=mock_passive_node_client, - identity_client=mock_identity_client, - seed_contacts=[fully_learned_nodes[0].secure_contact().contact], - domain=Domain.MAINNET, - clock=mock_clock, - ), - mock_pre_client, - ) + publisher_client = local_pre_client_factory("Publisher") # Fund the publisher mock_pre_client.mock_set_balance(publisher_signer.address, PREAmount.ether(1)) @@ -86,14 +62,12 @@ async def test_retrieve_cfrags( ) message = b"a secret message" - message_kit = pre_encrypt(policy, message) - - bob_client = ProxyPREClient("127.0.0.1", 9000, mock_passive_http_client) + encrypted_message = EncryptedMessage(policy, message) - decrypted = await bob_client.decrypt( + decrypted = await proxy_pre_client.decrypt( recipient=bob, enacted_policy=enacted_policy, - message_kit=message_kit, + encrypted_message=encrypted_message, delegator_card=alice.card(), publisher_card=publisher.card(), )