From 49941aa8025fe408b650c0f9b3a3f34b8e522eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Mon, 4 Nov 2024 14:18:20 +0100 Subject: [PATCH 1/9] added type hints --- pydpkg/base.py | 4 +- pydpkg/dpkg.py | 118 +++++++++++++++++++++++++---------------- pydpkg/dpkg_inspect.py | 3 +- pydpkg/dsc.py | 75 ++++++++++++++------------ pydpkg/exceptions.py | 2 + 5 files changed, 120 insertions(+), 82 deletions(-) diff --git a/pydpkg/base.py b/pydpkg/base.py index a225446..258b4d7 100644 --- a/pydpkg/base.py +++ b/pydpkg/base.py @@ -2,10 +2,12 @@ Base class to avoid pylint complaining about duplicate code """ +from __future__ import annotations + class _Dbase: # pylint: disable=too-few-public-methods - def __getitem__(self, item): + def __getitem__(self, item: str) -> str: """Overload getitem to treat the message plus our local properties as items. diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index 5bddc56..f10878f 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -1,5 +1,7 @@ """pydpkg.dpkg.Dpkg: a class to represent dpkg files""" +from __future__ import annotations + # stdlib imports import hashlib import io @@ -7,8 +9,10 @@ import lzma import os import tarfile +from typing import Literal, Any, TypedDict, TYPE_CHECKING, Union from functools import cmp_to_key from email import message_from_string +from email.message import Message from gzip import GzipFile # pypi imports @@ -26,42 +30,59 @@ ) from pydpkg.base import _Dbase +if TYPE_CHECKING: + from _typeshed import SupportsAllComparisons + + REQUIRED_HEADERS = ("package", "version", "architecture") +class FileInfo(TypedDict): + """Type definition for the fileinfo dictionary.""" + + md5: str + sha1: str + sha256: str + filesize: int + + # pylint: disable=too-many-instance-attributes,too-many-public-methods class Dpkg(_Dbase): """Class allowing import and manipulation of a debian package file.""" - def __init__(self, filename=None, ignore_missing=False, logger=None): + def __init__( + self, filename: str | None = None, ignore_missing: bool = False, logger: logging.Logger | None = None + ) -> None: """Constructor for Dpkg object :param filename: string :param ignore_missing: bool :param logger: logging.Logger """ + if not isinstance(filename, six.string_types): + raise DpkgError("filename argument must be a string") + self.filename = os.path.expanduser(filename) self.ignore_missing = ignore_missing - if not isinstance(self.filename, six.string_types): - raise DpkgError("filename argument must be a string") + if not os.path.isfile(self.filename): raise DpkgError(f"filename '{filename}' does not exist") self._log = logger or logging.getLogger(__name__) - self._fileinfo = None - self._control_str = None - self._headers = None - self._message = None - self._upstream_version = None - self._debian_revision = None - self._epoch = None - - def __repr__(self): + self._fileinfo: FileInfo | None = None + self._control_str: str | None = None + self._headers: dict[str, str] | None = None + self._message: Message[str, str] | None = None + self._upstream_version: str | None = None + self._debian_revision: str | None = None + self._epoch: int | None = None + + def __repr__(self) -> str: return repr(self.control_str) - def __str__(self): + def __str__(self) -> str: return six.text_type(self.control_str) - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> str: """Overload getattr to treat control message headers as object attributes (so long as they do not conflict with an existing attribute). @@ -76,7 +97,7 @@ def __getattr__(self, attr): raise AttributeError(f"'Dpkg' object has no attribute '{attr}'") @property - def message(self): + def message(self) -> Message[str, str]: """Return an email.Message object containing the package control structure. @@ -87,7 +108,7 @@ def message(self): return self._message @property - def control_str(self): + def control_str(self) -> str: """Return the control message as a string :returns: string @@ -97,7 +118,7 @@ def control_str(self): return self._control_str @property - def headers(self): + def headers(self) -> dict[str, str]: """Return the control message headers as a dict :returns: dict @@ -107,7 +128,7 @@ def headers(self): return self._headers @property - def fileinfo(self): + def fileinfo(self) -> FileInfo: """Return a dictionary containing md5/sha1/sha256 checksums and the size in bytes of our target file. @@ -131,7 +152,7 @@ def fileinfo(self): return self._fileinfo @property - def md5(self): + def md5(self) -> str: """Return the md5 hash of our target file :returns: string @@ -139,7 +160,7 @@ def md5(self): return self.fileinfo["md5"] @property - def sha1(self): + def sha1(self) -> str: """Return the sha1 hash of our target file :returns: string @@ -147,7 +168,7 @@ def sha1(self): return self.fileinfo["sha1"] @property - def sha256(self): + def sha256(self) -> str: """Return the sha256 hash of our target file :returns: string @@ -155,7 +176,7 @@ def sha256(self): return self.fileinfo["sha256"] @property - def filesize(self): + def filesize(self) -> int: """Return the size of our target file :returns: string @@ -163,7 +184,7 @@ def filesize(self): return self.fileinfo["filesize"] @property - def epoch(self): + def epoch(self) -> int: """Return the epoch portion of the package version string :returns: int @@ -173,7 +194,7 @@ def epoch(self): return self._epoch @property - def upstream_version(self): + def upstream_version(self) -> str: """Return the upstream portion of the package version string :returns: string @@ -183,7 +204,7 @@ def upstream_version(self): return self._upstream_version @property - def debian_revision(self): + def debian_revision(self) -> str: """Return the debian revision portion of the package version string :returns: string @@ -192,7 +213,7 @@ def debian_revision(self): self._debian_revision = self.split_full_version(self.version)[2] return self._debian_revision - def get(self, item, default=None): + def get(self, item: str, default: str | None = None) -> str | None: """Return an object property, a message header, None or the caller- provided default. @@ -205,26 +226,29 @@ def get(self, item, default=None): except KeyError: return default - def get_header(self, header): + def get_header(self, header: str) -> str | None: """Return an individual control message header :returns: string or None """ return self.message.get(header) - def compare_version_with(self, version_str): + def compare_version_with(self, version_str: str) -> Literal[-1, 0, 1]: """Compare my version to an arbitrary version""" - return Dpkg.compare_versions(self.get_header("version"), version_str) + header_version = self.get_header("version") + if header_version is None: + raise DpkgError("No version header found in control message") + return Dpkg.compare_versions(header_version, version_str) @staticmethod - def _force_encoding(obj, encoding="utf-8"): + def _force_encoding(obj: Any, encoding: str = "utf-8") -> Any: """Enforce uniform text encoding""" if isinstance(obj, six.string_types): if not isinstance(obj, six.text_type): obj = six.text_type(obj, encoding) return obj - def _extract_message(self, ctar): + def _extract_message(self, ctar: tarfile.TarFile) -> Message[str, str]: # pathname in the tar could be ./control, or just control # (there would never be two control files...right?) tar_members = [os.path.basename(x.name) for x in ctar.getmembers()] @@ -235,16 +259,18 @@ def _extract_message(self, ctar): self._log.debug("got control index: %s", control_idx) # at last! control_file = ctar.extractfile(ctar.getmembers()[control_idx]) + if control_file is None: + raise DpkgMissingControlFile("Corrupt dpkg file: control file is None") self._log.debug("got control file: %s", control_file) - message_body = control_file.read() + message_body: Union[str, bytes] = control_file.read() # py27 lacks email.message_from_bytes, so... - if isinstance(message_body, bytes): + if not isinstance(message_body, str): message_body = message_body.decode("utf-8") message = message_from_string(message_body) self._log.debug("got control message: %s", message) return message - def _process_dpkg_file(self, filename): + def _process_dpkg_file(self, filename: str) -> Message[str, str]: dpkg_archive = Archive(filename) dpkg_archive.read_all_headers() if b"control.tar.gz" in dpkg_archive.archived_files: @@ -296,7 +322,7 @@ def _process_dpkg_file(self, filename): return message @staticmethod - def get_epoch(version_str): + def get_epoch(version_str: str) -> tuple[int, str]: """Parse the epoch out of a package version string. Return (epoch, version); epoch is zero if not found.""" try: @@ -318,7 +344,7 @@ def get_epoch(version_str): return epoch, version_str[e_index + 1 :] @staticmethod - def get_upstream(version_str): + def get_upstream(version_str: str) -> tuple[str, str]: """Given a version string that could potentially contain both an upstream revision and a debian revision, return a tuple of both. If there is no debian revision, return 0 as the second tuple element.""" @@ -331,7 +357,7 @@ def get_upstream(version_str): return version_str[0:d_index], version_str[d_index + 1 :] @staticmethod - def split_full_version(version_str): + def split_full_version(version_str: str) -> tuple[int, str, str]: """Split a full version string into epoch, upstream version and debian revision. :param: version_str @@ -341,7 +367,7 @@ def split_full_version(version_str): return epoch, upstream_rev, debian_rev @staticmethod - def get_alphas(revision_str): + def get_alphas(revision_str: str) -> tuple[str, str]: """Return a tuple of the first non-digit characters of a revision (which may be empty) and the remaining characters.""" # get the index of the first digit @@ -354,7 +380,7 @@ def get_alphas(revision_str): return revision_str, "" @staticmethod - def get_digits(revision_str): + def get_digits(revision_str: str) -> tuple[int, str]: """Return a tuple of the first integer characters of a revision (which may be empty) and the remains.""" # If the string is empty, return (0,'') @@ -370,14 +396,14 @@ def get_digits(revision_str): return int(revision_str), "" @staticmethod - def listify(revision_str): + def listify(revision_str: str) -> list[str | int]: """Split a revision string into a list of alternating between strings and numbers, padded on either end to always be "str, int, str, int..." and always be of even length. This allows us to trivially implement the comparison algorithm described at section 5.6.12 in: https://www.debian.org/doc/debian-policy/ch-controlfields.html#version """ - result = [] + result: list[str | int] = [] while revision_str: rev_1, remains = Dpkg.get_alphas(revision_str) rev_2, remains = Dpkg.get_digits(remains) @@ -387,7 +413,7 @@ def listify(revision_str): # pylint: disable=invalid-name,too-many-return-statements @staticmethod - def dstringcmp(a, b): + def dstringcmp(a: str, b: str) -> Literal[-1, 0, 1]: """debian package version string section lexical sort algorithm "The lexical comparison is a comparison of ASCII values modified so @@ -430,7 +456,7 @@ def dstringcmp(a, b): return -1 @staticmethod - def compare_revision_strings(rev1, rev2): + def compare_revision_strings(rev1: str, rev2: str) -> Literal[-1, 0, 1]: """Compare two debian revision strings as described at https://www.debian.org/doc/debian-policy/ch-controlfields.html#version """ @@ -476,7 +502,7 @@ def compare_revision_strings(rev1, rev2): return -1 @staticmethod - def compare_versions(ver1, ver2): + def compare_versions(ver1: str, ver2: str) -> Literal[-1, 0, 1]: """Function to compare two Debian package version strings, suitable for passing to list.sort() and friends.""" if ver1 == ver2: @@ -509,14 +535,14 @@ def compare_versions(ver1, ver2): return 0 @staticmethod - def compare_versions_key(x): + def compare_versions_key(x: str) -> SupportsAllComparisons: """Uses functools.cmp_to_key to convert the compare_versions() function to a function suitable to passing to sorted() and friends as a key.""" return cmp_to_key(Dpkg.compare_versions)(x) @staticmethod - def dstringcmp_key(x): + def dstringcmp_key(x: str) -> SupportsAllComparisons: """Uses functools.cmp_to_key to convert the dstringcmp() function to a function suitable to passing to sorted() and friends as a key.""" diff --git a/pydpkg/dpkg_inspect.py b/pydpkg/dpkg_inspect.py index a7df34b..8fd363f 100755 --- a/pydpkg/dpkg_inspect.py +++ b/pydpkg/dpkg_inspect.py @@ -5,6 +5,7 @@ a debian package file using python-dpkg """ +from __future__ import annotations import glob import logging import os @@ -25,7 +26,7 @@ {5}""" -def indent(input_str, prefix): +def indent(input_str: str, prefix: str) -> str: """ Given a multiline string, return it with every line prefixed by "prefix" """ diff --git a/pydpkg/dsc.py b/pydpkg/dsc.py index 67240e7..cf6a4f5 100644 --- a/pydpkg/dsc.py +++ b/pydpkg/dsc.py @@ -1,12 +1,14 @@ """pydpkg.dpkg.Dpkg: a class to represent dpkg files""" +from __future__ import annotations + # stdlib imports import hashlib import logging import os from collections import defaultdict from email import message_from_file, message_from_string - +from email.message import Message import pgpy # pypi imports @@ -27,25 +29,28 @@ class Dsc(_Dbase): description (dsc) file.""" # pylint: disable=too-many-instance-attributes - def __init__(self, filename=None, logger=None): + def __init__(self, filename: str | None = None, logger: logging.Logger | None = None) -> None: + if not isinstance(filename, six.string_types): + raise TypeError("filename must be a string") + self.filename = os.path.expanduser(filename) self._dirname = os.path.dirname(self.filename) self._log = logger or logging.getLogger(__name__) - self._message = None - self._source_files = None - self._sizes = None - self._message_str = None - self._checksums = None - self._corrected_checksums = None - self._pgp_message = None - - def __repr__(self): + self._message: Message[str, str] | None = None + self._source_files: list[tuple[str, int, bool]] | None = None + self._sizes: set[tuple[str, int]] | None = None + self._message_str: str | None = None + self._checksums: dict[str, dict[str, str]] | None = None + self._corrected_checksums: dict[str, defaultdict[str, str | None]] | None = None + self._pgp_message: pgpy.PGPMessage | None = None + + def __repr__(self) -> str: return repr(self.message_str) - def __str__(self): + def __str__(self) -> str: return six.text_type(self.message_str) - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> str: """Overload getattr to treat message headers as object attributes (so long as they do not conflict with an existing attribute). @@ -64,7 +69,7 @@ def __getattr__(self, attr): return self.message[munged] raise AttributeError(f"'Dsc' object has no attribute '{attr}'") - def get(self, item, ret=None): + def get(self, item: str, ret: str | None = None) -> str | None: """Public wrapper for getitem""" try: return self[item] @@ -72,7 +77,7 @@ def get(self, item, ret=None): return ret @property - def message(self): + def message(self) -> Message[str, str]: """Return an email.Message object containing the parsed dsc file""" self._log.debug("accessing message property") if self._message is None: @@ -80,14 +85,14 @@ def message(self): return self._message @property - def headers(self): + def headers(self) -> dict[str, str]: """Return a dictionary of the message items""" if self._message is None: self._message = self._process_dsc_file() return dict(self._message.items()) @property - def pgp_message(self): + def pgp_message(self) -> pgpy.PGPMessage | None: """Return a pgpy.PGPMessage object containing the signed dsc message (or None if the message is unsigned)""" if self._message is None: @@ -95,26 +100,26 @@ def pgp_message(self): return self._pgp_message @property - def source_files(self): + def source_files(self) -> list[str]: """Return a list of source files found in the dsc file""" if self._source_files is None: self._source_files = self._process_source_files() return [x[0] for x in self._source_files] @property - def all_files_present(self): + def all_files_present(self) -> bool: """Return true if all files listed in the dsc have been found""" if self._source_files is None: self._source_files = self._process_source_files() return all(x[2] for x in self._source_files) @property - def all_checksums_correct(self): + def all_checksums_correct(self) -> bool: """Return true if all checksums are correct""" return not self.corrected_checksums @property - def corrected_checksums(self): + def corrected_checksums(self) -> dict[str, defaultdict[str, str | None]]: """Returns a dict of the CORRECT checksums in any case where the ones provided by the dsc file are incorrect.""" if self._corrected_checksums is None: @@ -122,21 +127,21 @@ def corrected_checksums(self): return self._corrected_checksums @property - def missing_files(self): + def missing_files(self) -> list[str]: """Return a list of all files from the dsc that we failed to find""" if self._source_files is None: self._source_files = self._process_source_files() return [x[0] for x in self._source_files if x[2] is False] @property - def sizes(self): + def sizes(self) -> set[tuple[str, int]]: """Return a list of source files found in the dsc file""" if self._source_files is None: self._source_files = self._process_source_files() return {(x[0], x[1]) for x in self._source_files} @property - def message_str(self): + def message_str(self) -> str: """Return the dsc message as a string :returns: string @@ -146,26 +151,28 @@ def message_str(self): return self._message_str @property - def checksums(self): + def checksums(self) -> dict[str, dict[str, str]]: """Return a dictionary of checksums for the source files found in the dsc file, keyed first by hash type and then by filename.""" if self._checksums is None: self._checksums = self._process_checksums() return self._checksums - def validate(self): + def validate(self) -> None: """Raise exceptions if files are missing or checksums are bad.""" if not self.all_files_present: + if self._source_files is None: + raise DscMissingFileError("Source files are not processed") raise DscMissingFileError([x[0] for x in self._source_files if not x[2]]) if not self.all_checksums_correct: raise DscBadChecksumsError(self.corrected_checksums) - def _process_checksums(self): + def _process_checksums(self) -> dict[str, dict[str, str]]: """Walk through the dsc message looking for any keys in the format 'Checksum-hashtype'. Return a nested dictionary in the form {hashtype: {filename: {digest}}}""" self._log.debug("process_checksums()") - sums = {} + sums: dict[str, dict[str, str]] = {} for key in self.message.keys(): if key.lower().startswith("checksums"): hashtype = key.split("-")[1].lower() @@ -183,7 +190,7 @@ def _process_checksums(self): sums[hashtype][pathname] = digest return sums - def _internalize_message(self, msg): + def _internalize_message(self, msg: Message[str, str]) -> Message[str, str]: """Ugh: the dsc message body may not include a Files or Checksums-foo entry for _itself_, which makes for hilarious misadventures up the chain. So, pfeh, we add it.""" @@ -219,7 +226,7 @@ def _internalize_message(self, msg): msg.replace_header(key, msg[key] + newline) return msg - def _process_dsc_file(self): + def _process_dsc_file(self) -> Message[str, str]: """Extract the dsc message from a file: parse the dsc body and return an email.Message object. Attempt to extract the RFC822 message from an OpenPGP message if necessary.""" @@ -246,7 +253,7 @@ def _process_dsc_file(self): msg = message_from_file(fileobj) return self._internalize_message(msg) - def _process_source_files(self): + def _process_source_files(self) -> list[tuple[str, int, bool]]: """Walk through the list of lines in the 'Files' section of the dsc message, and verify that the file exists in the same location on our filesystem as the dsc file. Return a list @@ -258,7 +265,7 @@ def _process_source_files(self): out the _files dictionary. """ self._log.debug("process_source_files()") - filenames = [] + filenames: list[tuple[str, int, bool]] = [] try: files = self.message["Files"] except KeyError: @@ -271,13 +278,13 @@ def _process_source_files(self): filenames.append((pathname, int(size), os.path.isfile(pathname))) return filenames - def _validate_checksums(self): + def _validate_checksums(self) -> dict[str, defaultdict[str, str | None]]: """Iterate over the dict of asserted checksums from the dsc file. Check each in turn. If any checksum is invalid, append the correct checksum to a similarly structured dict and return them all at the end.""" self._log.debug("validate_checksums()") - bad_hashes = defaultdict(lambda: defaultdict(None)) + bad_hashes: defaultdict[str, defaultdict[str, str | None]] = defaultdict(lambda: defaultdict(None)) for hashtype, filenames in six.iteritems(self.checksums): for filename, digest in six.iteritems(filenames): hasher = getattr(hashlib, hashtype)() diff --git a/pydpkg/exceptions.py b/pydpkg/exceptions.py index 39b9e00..d971d32 100644 --- a/pydpkg/exceptions.py +++ b/pydpkg/exceptions.py @@ -1,5 +1,7 @@ """pydpkg.exceptions: what it says on the tin""" +from __future__ import annotations + class DpkgError(Exception): """Base error class for Dpkg errors""" From 1347410d39e8f5aec3f05087b2eaf0d40ad3a78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Mon, 4 Nov 2024 14:26:08 +0100 Subject: [PATCH 2/9] removed trailing whiteline in test file, and adjusted hashes in tests --- tests/test_dsc.py | 12 ++++++------ tests/testdeb_0.0.0.dsc | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_dsc.py b/tests/test_dsc.py index d1bff6f..110f373 100644 --- a/tests/test_dsc.py +++ b/tests/test_dsc.py @@ -91,17 +91,17 @@ def test_parse_checksums(self): { "md5": { xz: "fc80e6e7f1c1a08b78a674aaee6c1548", - dsc: "893d13a2ef13f7409c9521e8ab1dbccb", + dsc: "739bc26254611231dfbb93619151d2b1", gz: "142ca7334ed1f70302b4504566e0c233", }, "sha1": { xz: "cb3474ff94053018957ebcf1d8a2b45f75dda449", - dsc: "80cd7b01014a269d445c63b037b885d6002cf533", + dsc: "19331d9d4ab75fc184ff39cebc09c36c957c5c02", gz: "f250ac0a426b31df24fc2c98050f4fab90e456cd", }, "sha256": { xz: "1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858", - dsc: "b5ad1591349eb48db65e6865be506ad7dbd21931902a71addee5b1db9ae1ac2a", + dsc: "c5146bfe11e02d532134ed0432c97f0adccd4b623e851b5ad57c02495e7fa14d", gz: "aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7", }, }, @@ -117,13 +117,13 @@ def test_message_internalization(self): self.maxDiff = None files = """142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz - 893d13a2ef13f7409c9521e8ab1dbccb 841 testdeb_0.0.0.dsc""" + 739bc26254611231dfbb93619151d2b1 840 testdeb_0.0.0.dsc""" sha_1 = """f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_0.0.0.orig.tar.gz cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz - 80cd7b01014a269d445c63b037b885d6002cf533 841 testdeb_0.0.0.dsc""" + 19331d9d4ab75fc184ff39cebc09c36c957c5c02 840 testdeb_0.0.0.dsc""" sha_256 = """aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_0.0.0.orig.tar.gz 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_0.0.0-1.debian.tar.xz - b5ad1591349eb48db65e6865be506ad7dbd21931902a71addee5b1db9ae1ac2a 841 testdeb_0.0.0.dsc""" + c5146bfe11e02d532134ed0432c97f0adccd4b623e851b5ad57c02495e7fa14d 840 testdeb_0.0.0.dsc""" self.assertEqual( self.good.message["Files"].strip(), files, diff --git a/tests/testdeb_0.0.0.dsc b/tests/testdeb_0.0.0.dsc index 239d059..d444d3c 100644 --- a/tests/testdeb_0.0.0.dsc +++ b/tests/testdeb_0.0.0.dsc @@ -18,4 +18,3 @@ Checksums-Sha256: Files: 142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz - From e3b46decdb1f260e1754726a24baa1deee019ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Mon, 4 Nov 2024 14:42:45 +0100 Subject: [PATCH 3/9] safely close archive after use --- pydpkg/dpkg.py | 71 +++++++++++++++++++++++++++++++++----------------- pydpkg/dsc.py | 8 ++++-- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index f10878f..44cced3 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -9,16 +9,17 @@ import lzma import os import tarfile -from typing import Literal, Any, TypedDict, TYPE_CHECKING, Union +from typing import Literal, Any, TypedDict, TYPE_CHECKING, Union, IO from functools import cmp_to_key from email import message_from_string from email.message import Message from gzip import GzipFile +from contextlib import contextmanager # pypi imports import six import zstandard -from arpy import Archive +from arpy import Archive, ArchiveFileData # local imports from pydpkg.exceptions import ( @@ -31,8 +32,8 @@ from pydpkg.base import _Dbase if TYPE_CHECKING: - from _typeshed import SupportsAllComparisons - + from _typeshed import SupportsAllComparisons, SupportsRead + from collections.abc import Generator REQUIRED_HEADERS = ("package", "version", "architecture") @@ -270,41 +271,63 @@ def _extract_message(self, ctar: tarfile.TarFile) -> Message[str, str]: self._log.debug("got control message: %s", message) return message - def _process_dpkg_file(self, filename: str) -> Message[str, str]: - dpkg_archive = Archive(filename) + def _read_archive(self, dpkg_archive: Archive) -> tuple[ArchiveFileData, Literal["gz", "xz", "zst"]]: dpkg_archive.read_all_headers() + if b"control.tar.gz" in dpkg_archive.archived_files: control_archive = dpkg_archive.archived_files[b"control.tar.gz"] - control_archive_type = "gz" - elif b"control.tar.xz" in dpkg_archive.archived_files: + return control_archive, "gz" + + if b"control.tar.xz" in dpkg_archive.archived_files: control_archive = dpkg_archive.archived_files[b"control.tar.xz"] - control_archive_type = "xz" - elif b"control.tar.zst" in dpkg_archive.archived_files: + return control_archive, "xz" + + if b"control.tar.zst" in dpkg_archive.archived_files: control_archive = dpkg_archive.archived_files[b"control.tar.zst"] - control_archive_type = "zst" - else: - raise DpkgMissingControlGzipFile("Corrupt dpkg file: no control.tar.gz/xz/zst file in ar archive.") - self._log.debug("found controlgz: %s", control_archive) + return control_archive, "zst" + + raise DpkgMissingControlGzipFile("Corrupt dpkg file: no control.tar.gz/xz/zst file in ar archive.") + def _extract_message_from_tar(self, fd: SupportsRead[bytes]) -> Message[str, str]: + with tarfile.open(fileobj=io.BytesIO(fd.read())) as ctar: + self._log.debug("opened tar file: %s", ctar) + message = self._extract_message(ctar) + return message + + def _extract_message_from_archive( + self, control_archive: IO[bytes], control_archive_type: Literal["gz", "xz", "zst"] + ) -> Message[str, str]: if control_archive_type == "gz": with GzipFile(fileobj=control_archive) as gzf: self._log.debug("opened gzip control archive: %s", gzf) - with tarfile.open(fileobj=io.BytesIO(gzf.read())) as ctar: - self._log.debug("opened tar file: %s", ctar) - message = self._extract_message(ctar) + message = self._extract_message_from_tar(gzf) elif control_archive_type == "xz": with lzma.open(control_archive) as xzf: self._log.debug("opened xz control archive: %s", xzf) - with tarfile.open(fileobj=io.BytesIO(xzf.read())) as ctar: - self._log.debug("opened tar file: %s", ctar) - message = self._extract_message(ctar) - else: + message = self._extract_message_from_tar(xzf) + elif control_archive_type == "zst": zst = zstandard.ZstdDecompressor() with zst.stream_reader(control_archive) as reader: self._log.debug("opened zst control archive: %s", reader) - with tarfile.open(fileobj=io.BytesIO(reader.read())) as ctar: - self._log.debug("opened tar file: %s", ctar) - message = self._extract_message(ctar) + message = self._extract_message_from_tar(reader) + else: + raise DpkgError(f"Unknown control archive type: {control_archive_type}") + return message + + def _process_dpkg_file(self, filename: str) -> Message[str, str]: + @contextmanager + def archive_context(filename: str) -> Generator[Archive]: + """Close archive after use.""" + dpkg_archive = Archive(filename) + try: + yield dpkg_archive + finally: + dpkg_archive.close() + + with archive_context(filename) as archive: + control_archive, control_archive_type = self._read_archive(archive) + self._log.debug("found controlgz: %s", control_archive) + message = self._extract_message_from_archive(control_archive, control_archive_type) for req in REQUIRED_HEADERS: if req not in list(map(str.lower, message.keys())): diff --git a/pydpkg/dsc.py b/pydpkg/dsc.py index cf6a4f5..c2943d1 100644 --- a/pydpkg/dsc.py +++ b/pydpkg/dsc.py @@ -10,6 +10,7 @@ from email import message_from_file, message_from_string from email.message import Message import pgpy +from typing import TYPE_CHECKING # pypi imports import six @@ -21,6 +22,9 @@ ) from pydpkg.base import _Dbase +if TYPE_CHECKING: + from hashlib import _Hash + REQUIRED_HEADERS = ("package", "version", "architecture") @@ -213,7 +217,7 @@ def _internalize_message(self, msg: Message[str, str]) -> Message[str, str]: if base not in files: self._log.debug("dsc file not found in %s: %s", key, base) self._log.debug("getting hasher for %s", hashtype) - hasher = getattr(hashlib, hashtype)() + hasher: _Hash = getattr(hashlib, hashtype)() self._log.debug("hashing file") with open(self.filename, "rb") as fileobj: # pylint: disable=cell-var-from-loop @@ -287,7 +291,7 @@ def _validate_checksums(self) -> dict[str, defaultdict[str, str | None]]: bad_hashes: defaultdict[str, defaultdict[str, str | None]] = defaultdict(lambda: defaultdict(None)) for hashtype, filenames in six.iteritems(self.checksums): for filename, digest in six.iteritems(filenames): - hasher = getattr(hashlib, hashtype)() + hasher: _Hash = getattr(hashlib, hashtype)() with open(filename, "rb") as fileobj: # pylint: disable=cell-var-from-loop for chunk in iter(lambda: fileobj.read(128), b""): From ce72ef1e3ec799030d9e00d543f17a70e2cfc61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Mon, 4 Nov 2024 14:55:11 +0100 Subject: [PATCH 4/9] contextmanager already implemented in arpy, using the implemented one --- pydpkg/dpkg.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index 44cced3..85591e9 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -14,7 +14,6 @@ from email import message_from_string from email.message import Message from gzip import GzipFile -from contextlib import contextmanager # pypi imports import six @@ -33,7 +32,6 @@ if TYPE_CHECKING: from _typeshed import SupportsAllComparisons, SupportsRead - from collections.abc import Generator REQUIRED_HEADERS = ("package", "version", "architecture") @@ -315,16 +313,7 @@ def _extract_message_from_archive( return message def _process_dpkg_file(self, filename: str) -> Message[str, str]: - @contextmanager - def archive_context(filename: str) -> Generator[Archive]: - """Close archive after use.""" - dpkg_archive = Archive(filename) - try: - yield dpkg_archive - finally: - dpkg_archive.close() - - with archive_context(filename) as archive: + with Archive(filename) as archive: control_archive, control_archive_type = self._read_archive(archive) self._log.debug("found controlgz: %s", control_archive) message = self._extract_message_from_archive(control_archive, control_archive_type) From 255aa3f7a7a9ac92b80aa09d0fd2dbb847a48f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Mon, 4 Nov 2024 15:02:23 +0100 Subject: [PATCH 5/9] do not repeat the logging, just pass the archive type --- pydpkg/dpkg.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index 85591e9..86cea70 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -286,7 +286,8 @@ def _read_archive(self, dpkg_archive: Archive) -> tuple[ArchiveFileData, Literal raise DpkgMissingControlGzipFile("Corrupt dpkg file: no control.tar.gz/xz/zst file in ar archive.") - def _extract_message_from_tar(self, fd: SupportsRead[bytes]) -> Message[str, str]: + def _extract_message_from_tar(self, fd: SupportsRead[bytes], archive_name: str = "undefined") -> Message[str, str]: + self._log.debug("opened %s control archive: %s", archive_name, fd) with tarfile.open(fileobj=io.BytesIO(fd.read())) as ctar: self._log.debug("opened tar file: %s", ctar) message = self._extract_message(ctar) @@ -297,20 +298,18 @@ def _extract_message_from_archive( ) -> Message[str, str]: if control_archive_type == "gz": with GzipFile(fileobj=control_archive) as gzf: - self._log.debug("opened gzip control archive: %s", gzf) - message = self._extract_message_from_tar(gzf) - elif control_archive_type == "xz": + return self._extract_message_from_tar(gzf, "gzip") + + if control_archive_type == "xz": with lzma.open(control_archive) as xzf: - self._log.debug("opened xz control archive: %s", xzf) - message = self._extract_message_from_tar(xzf) - elif control_archive_type == "zst": + return self._extract_message_from_tar(xzf, "xz") + + if control_archive_type == "zst": zst = zstandard.ZstdDecompressor() with zst.stream_reader(control_archive) as reader: - self._log.debug("opened zst control archive: %s", reader) - message = self._extract_message_from_tar(reader) - else: - raise DpkgError(f"Unknown control archive type: {control_archive_type}") - return message + return self._extract_message_from_tar(reader, "zst") + + raise DpkgError(f"Unknown control archive type: {control_archive_type}") def _process_dpkg_file(self, filename: str) -> Message[str, str]: with Archive(filename) as archive: From 35518404b714856d9eb2869ea0329a6577baf035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Mon, 4 Nov 2024 15:31:50 +0100 Subject: [PATCH 6/9] made the revision string comparison type hinted and replaced the try-except indexerror with a zip_longest plus null check --- pydpkg/dpkg.py | 70 ++++++++++++++++++++++++++++---------------------- pydpkg/dsc.py | 3 ++- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index 86cea70..55c43d4 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -14,6 +14,7 @@ from email import message_from_string from email.message import Message from gzip import GzipFile +from itertools import zip_longest # pypi imports import six @@ -480,37 +481,46 @@ def compare_revision_strings(rev1: str, rev2: str) -> Literal[-1, 0, 1]: list2 = Dpkg.listify(rev2) if list1 == list2: return 0 - try: - for i, item in enumerate(list1): - # explicitly raise IndexError if we've fallen off the edge of list2 - if i >= len(list2): - raise IndexError - # just in case - if not isinstance(item, list2[i].__class__): - raise DpkgVersionError(f"Cannot compare '{item}' to {list2[i]}, something has gone horribly awry.") - # if the items are equal, next - if item == list2[i]: - continue - # numeric comparison - if isinstance(item, int): - if item > list2[i]: - return 1 - if item < list2[i]: - return -1 - else: - # string comparison - return Dpkg.dstringcmp(item, list2[i]) - except IndexError: - # rev1 is longer than rev2 but otherwise equal, hence greater - # ...except for goddamn tildes - if list1[len(list2)][0][0] == "~": + + def _tilde_case(elem: str | int | None) -> Literal[-1, 1]: + if isinstance(elem, str): + if elem[0] == "~": + return -1 + return 1 + raise DpkgVersionError(f"Cannot compare '{elem}' to ~, something has gone horribly awry.") + + def _numeric_case(i1: int, i2: int) -> Literal[-1, 0, 1]: + if i1 > i2: + return 1 + if i1 < i2: return -1 - return 1 - # rev1 is shorter than rev2 but otherwise equal, hence lesser - # ...except for goddamn tildes - if list2[len(list1)][0][0] == "~": - return 1 - return -1 + return 0 + + for i1, i2 in zip_longest(list1, list2, fillvalue=None): + if i2 is None: + # rev1 is longer than rev2 but otherwise equal, hence greater + # ...except for goddamn tildes + return _tilde_case(i1) + + if i1 is None: + # rev1 is shorter than rev2 but otherwise equal, hence lesser + # ...except for goddamn tildes + return -1 if _tilde_case(i2) == 1 else 1 + + # if the items are equal, next + if isinstance(i1, int) and isinstance(i2, int): + cmp = _numeric_case(i1, i2) + elif isinstance(i1, str) and isinstance(i2, str): + cmp = Dpkg.dstringcmp(i1, i2) + else: + raise DpkgVersionError(f"Cannot compare '{i1}' to {i2}, something has gone horribly awry.") + + if cmp == 0: + continue + + return cmp + + raise DpkgVersionError("Should have matched equal in the beginning") @staticmethod def compare_versions(ver1: str, ver2: str) -> Literal[-1, 0, 1]: diff --git a/pydpkg/dsc.py b/pydpkg/dsc.py index c2943d1..800dfc2 100644 --- a/pydpkg/dsc.py +++ b/pydpkg/dsc.py @@ -9,11 +9,12 @@ from collections import defaultdict from email import message_from_file, message_from_string from email.message import Message -import pgpy from typing import TYPE_CHECKING # pypi imports import six +import pgpy + # local imports from pydpkg.exceptions import ( From ed8b008aaeef620866e80aa41924164fb3110107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Tue, 5 Nov 2024 11:42:41 +0100 Subject: [PATCH 7/9] adjusted dstringcmp matching to compare_revision_strings, adjusted type hints, ignoring explicit override --- pydpkg/base.py | 6 ++-- pydpkg/dpkg.py | 78 ++++++++++++++++++++++++------------------ pydpkg/dpkg_inspect.py | 2 +- pydpkg/dsc.py | 12 +++---- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/pydpkg/base.py b/pydpkg/base.py index 258b4d7..f230911 100644 --- a/pydpkg/base.py +++ b/pydpkg/base.py @@ -4,10 +4,12 @@ from __future__ import annotations +from typing import Any + class _Dbase: # pylint: disable=too-few-public-methods - def __getitem__(self, item: str) -> str: + def __getitem__(self, item: str) -> Any: """Overload getitem to treat the message plus our local properties as items. @@ -19,6 +21,6 @@ def __getitem__(self, item: str) -> str: return getattr(self, item) except AttributeError: try: - return self.__getattr__(item) + return self.__getattr__(item) # type: ignore[attr-defined] except AttributeError as ex: raise KeyError(item) from ex diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index 55c43d4..0d7b0d2 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -76,11 +76,11 @@ def __init__( self._debian_revision: str | None = None self._epoch: int | None = None - def __repr__(self) -> str: + def __repr__(self) -> str: # type: ignore[explicit-override] return repr(self.control_str) - def __str__(self) -> str: - return six.text_type(self.control_str) + def __str__(self) -> str: # type: ignore[explicit-override] + return six.text_type(self.control_str) # type: ignore[no-any-return] def __getattr__(self, attr: str) -> str: """Overload getattr to treat control message headers as object @@ -213,7 +213,7 @@ def debian_revision(self) -> str: self._debian_revision = self.split_full_version(self.version)[2] return self._debian_revision - def get(self, item: str, default: str | None = None) -> str | None: + def get(self, item: str, default: str | None = None) -> Any | None: """Return an object property, a message header, None or the caller- provided default. @@ -249,6 +249,7 @@ def _force_encoding(obj: Any, encoding: str = "utf-8") -> Any: return obj def _extract_message(self, ctar: tarfile.TarFile) -> Message[str, str]: + """Extract the control file from an opened tar archive as a Message object""" # pathname in the tar could be ./control, or just control # (there would never be two control files...right?) tar_members = [os.path.basename(x.name) for x in ctar.getmembers()] @@ -271,6 +272,7 @@ def _extract_message(self, ctar: tarfile.TarFile) -> Message[str, str]: return message def _read_archive(self, dpkg_archive: Archive) -> tuple[ArchiveFileData, Literal["gz", "xz", "zst"]]: + """Search an opened archive for a compressed control file and return it plus the compression""" dpkg_archive.read_all_headers() if b"control.tar.gz" in dpkg_archive.archived_files: @@ -288,6 +290,7 @@ def _read_archive(self, dpkg_archive: Archive) -> tuple[ArchiveFileData, Literal raise DpkgMissingControlGzipFile("Corrupt dpkg file: no control.tar.gz/xz/zst file in ar archive.") def _extract_message_from_tar(self, fd: SupportsRead[bytes], archive_name: str = "undefined") -> Message[str, str]: + """Extract the control file in a tar archive from a decompressed archive fileobj""" self._log.debug("opened %s control archive: %s", archive_name, fd) with tarfile.open(fileobj=io.BytesIO(fd.read())) as ctar: self._log.debug("opened tar file: %s", ctar) @@ -297,6 +300,7 @@ def _extract_message_from_tar(self, fd: SupportsRead[bytes], archive_name: str = def _extract_message_from_archive( self, control_archive: IO[bytes], control_archive_type: Literal["gz", "xz", "zst"] ) -> Message[str, str]: + """Extract the control file from a compressed archive fileobj""" if control_archive_type == "gz": with GzipFile(fileobj=control_archive) as gzf: return self._extract_message_from_tar(gzf, "gzip") @@ -435,37 +439,45 @@ def dstringcmp(a: str, b: str) -> Literal[-1, 0, 1]: if a == b: return 0 - try: - for i, char in enumerate(a): - if char == b[i]: - continue - # "a tilde sorts before anything, even the end of a part" - # (emptyness) - if char == "~": - return -1 - if b[i] == "~": - return 1 - # "all the letters sort earlier than all the non-letters" - if char.isalpha() and not b[i].isalpha(): - return -1 - if not char.isalpha() and b[i].isalpha(): - return 1 - # otherwise lexical sort - if ord(char) > ord(b[i]): - return 1 - if ord(char) < ord(b[i]): - return -1 - except IndexError: - # a is longer than b but otherwise equal, hence greater - # ...except for goddamn tildes - if char == "~": + + def _tilde_case(longer: str | None) -> Literal[-1, 1]: + if longer is None: + raise DpkgVersionError("Cannot compare None to ~, something has gone horribly awry.") + if longer[0] == "~": return -1 return 1 - # if we get here, a is shorter than b but otherwise equal, hence lesser - # ...except for goddamn tildes - if b[len(a)] == "~": - return 1 - return -1 + + for char_a, char_b in zip_longest(a, b, fillvalue=None): + if char_b is None: + # a is longer than b but otherwise equal, hence greater + # ...except for goddamn tildes + return _tilde_case(char_a) + + if char_a is None: + # if we get here, a is shorter than b but otherwise equal, hence lesser + # ...except for goddamn tildes + return -1 if _tilde_case(char_b) == 1 else 1 + + if char_a == char_b: + continue + + # "a tilde sorts before anything, even the end of a part" + # (emptyness) + if char_a == "~": + return -1 + if char_b == "~": + return 1 + # "all the letters sort earlier than all the non-letters" + if char_a.isalpha() and not char_b.isalpha(): + return -1 + if not char_a.isalpha() and char_b.isalpha(): + return 1 + # otherwise lexical sort + if ord(char_a) > ord(char_b): + return 1 + if ord(char_a) < ord(char_b): + return -1 + raise DpkgVersionError("Should have matched equal in the beginning") @staticmethod def compare_revision_strings(rev1: str, rev2: str) -> Literal[-1, 0, 1]: diff --git a/pydpkg/dpkg_inspect.py b/pydpkg/dpkg_inspect.py index 8fd363f..fa29609 100755 --- a/pydpkg/dpkg_inspect.py +++ b/pydpkg/dpkg_inspect.py @@ -33,7 +33,7 @@ def indent(input_str: str, prefix: str) -> str: return "\n".join([f"{prefix}{x}" for x in input_str.split("\n")]) -def main(): +def main() -> None: """pylint really wants a docstring :)""" try: file_names = sys.argv[1:] diff --git a/pydpkg/dsc.py b/pydpkg/dsc.py index 800dfc2..9ab89ba 100644 --- a/pydpkg/dsc.py +++ b/pydpkg/dsc.py @@ -9,7 +9,7 @@ from collections import defaultdict from email import message_from_file, message_from_string from email.message import Message -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any # pypi imports import six @@ -49,13 +49,13 @@ def __init__(self, filename: str | None = None, logger: logging.Logger | None = self._corrected_checksums: dict[str, defaultdict[str, str | None]] | None = None self._pgp_message: pgpy.PGPMessage | None = None - def __repr__(self) -> str: + def __repr__(self) -> str: # type: ignore[explicit-override] return repr(self.message_str) - def __str__(self) -> str: - return six.text_type(self.message_str) + def __str__(self) -> str: # type: ignore[explicit-override] + return six.text_type(self.message_str) # type: ignore[no-any-return] - def __getattr__(self, attr: str) -> str: + def __getattr__(self, attr: str) -> Any: """Overload getattr to treat message headers as object attributes (so long as they do not conflict with an existing attribute). @@ -74,7 +74,7 @@ def __getattr__(self, attr: str) -> str: return self.message[munged] raise AttributeError(f"'Dsc' object has no attribute '{attr}'") - def get(self, item: str, ret: str | None = None) -> str | None: + def get(self, item: str, ret: str | None = None) -> Any | None: """Public wrapper for getitem""" try: return self[item] From c477627d928e6063dcf0291fa9e11246199140ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Tue, 5 Nov 2024 11:43:31 +0100 Subject: [PATCH 8/9] added mypy to CI --- .github/workflows/pr.yaml | 2 + poetry.lock | 297 ++++++++++++++++++++++++-------------- pyproject.toml | 17 +++ 3 files changed, 210 insertions(+), 106 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 92e5874..c0d3860 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -30,5 +30,7 @@ jobs: POETRY_VIRTUALENVS_CREATE: false - name: Check formatting and linting run: poetry run ruff check + - name: Static type checking + run: poetry run mypy pydpkg/ - name: Test with pytest run: poetry run pytest tests/ diff --git a/poetry.lock b/poetry.lock index c449f60..ae2433c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "arpy" @@ -12,63 +12,78 @@ files = [ [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -87,43 +102,38 @@ files = [ [[package]] name = "cryptography" -version = "42.0.5" +version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] @@ -136,18 +146,18 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -164,15 +174,79 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -191,13 +265,13 @@ pyasn1 = "*" [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -206,13 +280,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pyasn1" -version = "0.6.0" +version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, ] [[package]] @@ -287,13 +361,24 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -360,4 +445,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "f97022dc0d780531b61dfc9a0206e372d11729ed134c82d7692de06bd91bc687" +content-hash = "5360ff1341ea2a9470707bd29c01c39e26621d230a98b336288c37664d2f6dcc" diff --git a/pyproject.toml b/pyproject.toml index 545acf5..0e3d720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,11 +37,28 @@ cryptography = ">=39.0.1" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" ruff = "0.3.4" +mypy = "^1.13.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" +[tool.mypy] +# Enable all error messages. +python_version = "3.8" +enable_error_code = '''type-arg,no-untyped-def,redundant-cast,redundant-self,comparison-overlap, + no-untyped-call,no-any-return,no-any-unimported,unreachable,redundant-expr, + possibly-undefined,truthy-bool,truthy-iterable,ignore-without-code, + unused-awaitable,unused-ignore,explicit-override,unimported-reveal''' +disable_error_code = 'import-untyped' +disallow_untyped_calls = true +disallow_untyped_defs = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +exclude = ["build", "docs", "tests"] + [tool.ruff] # Exclude a variety of commonly ignored directories. exclude = [ From e47ab1caadbf5247fa854625e2dbbff486bdd8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=B6rmann?= Date: Tue, 5 Nov 2024 11:49:43 +0100 Subject: [PATCH 9/9] reverted hash changes caveats: in this change a linter which removes trailing whitespace needs to ignore this file, else it will lead to test failure --- tests/test_dsc.py | 12 ++++++------ tests/testdeb_0.0.0.dsc | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_dsc.py b/tests/test_dsc.py index 110f373..d1bff6f 100644 --- a/tests/test_dsc.py +++ b/tests/test_dsc.py @@ -91,17 +91,17 @@ def test_parse_checksums(self): { "md5": { xz: "fc80e6e7f1c1a08b78a674aaee6c1548", - dsc: "739bc26254611231dfbb93619151d2b1", + dsc: "893d13a2ef13f7409c9521e8ab1dbccb", gz: "142ca7334ed1f70302b4504566e0c233", }, "sha1": { xz: "cb3474ff94053018957ebcf1d8a2b45f75dda449", - dsc: "19331d9d4ab75fc184ff39cebc09c36c957c5c02", + dsc: "80cd7b01014a269d445c63b037b885d6002cf533", gz: "f250ac0a426b31df24fc2c98050f4fab90e456cd", }, "sha256": { xz: "1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858", - dsc: "c5146bfe11e02d532134ed0432c97f0adccd4b623e851b5ad57c02495e7fa14d", + dsc: "b5ad1591349eb48db65e6865be506ad7dbd21931902a71addee5b1db9ae1ac2a", gz: "aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7", }, }, @@ -117,13 +117,13 @@ def test_message_internalization(self): self.maxDiff = None files = """142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz - 739bc26254611231dfbb93619151d2b1 840 testdeb_0.0.0.dsc""" + 893d13a2ef13f7409c9521e8ab1dbccb 841 testdeb_0.0.0.dsc""" sha_1 = """f250ac0a426b31df24fc2c98050f4fab90e456cd 280 testdeb_0.0.0.orig.tar.gz cb3474ff94053018957ebcf1d8a2b45f75dda449 232 testdeb_0.0.0-1.debian.tar.xz - 19331d9d4ab75fc184ff39cebc09c36c957c5c02 840 testdeb_0.0.0.dsc""" + 80cd7b01014a269d445c63b037b885d6002cf533 841 testdeb_0.0.0.dsc""" sha_256 = """aa57ba8f29840383f5a96c5c8f166a9e6da7a484151938643ce2618e82bfeea7 280 testdeb_0.0.0.orig.tar.gz 1ddb2a7336a99bc1d203f3ddb59f6fa2d298e90cb3e59cccbe0c84e359979858 232 testdeb_0.0.0-1.debian.tar.xz - c5146bfe11e02d532134ed0432c97f0adccd4b623e851b5ad57c02495e7fa14d 840 testdeb_0.0.0.dsc""" + b5ad1591349eb48db65e6865be506ad7dbd21931902a71addee5b1db9ae1ac2a 841 testdeb_0.0.0.dsc""" self.assertEqual( self.good.message["Files"].strip(), files, diff --git a/tests/testdeb_0.0.0.dsc b/tests/testdeb_0.0.0.dsc index d444d3c..239d059 100644 --- a/tests/testdeb_0.0.0.dsc +++ b/tests/testdeb_0.0.0.dsc @@ -18,3 +18,4 @@ Checksums-Sha256: Files: 142ca7334ed1f70302b4504566e0c233 280 testdeb_0.0.0.orig.tar.gz fc80e6e7f1c1a08b78a674aaee6c1548 232 testdeb_0.0.0-1.debian.tar.xz +