From 2b9865f84061b799610fccca9aff4a988421b15e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:57:01 +0000 Subject: [PATCH 01/41] Bump typing-extensions from 4.6.2 to 4.6.3 Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.6.2 to 4.6.3. - [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md) - [Commits](https://github.com/python/typing_extensions/compare/4.6.2...4.6.3) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 107 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index ed3fe48c..c38b2028 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -15,6 +16,7 @@ files = [ name = "apeye" version = "1.3.0" description = "Handy tools for working with URLs and APIs." +category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -36,6 +38,7 @@ limiter = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] name = "apeye-core" version = "1.1.2" description = "Core (offline) functionality for the apeye library." +category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -51,6 +54,7 @@ idna = ">=2.5" name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -69,6 +73,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "autodocsumm" version = "0.2.11" description = "Extended sphinx autodoc including automatic autosummaries" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -83,6 +88,7 @@ Sphinx = ">=2.2,<8.0" name = "babel" version = "2.12.1" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -97,6 +103,7 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" +category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -115,6 +122,7 @@ lxml = ["lxml"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -164,6 +172,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cachecontrol" version = "0.12.11" description = "httplib2 caching for requests" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -184,6 +193,7 @@ redis = ["redis (>=2.10.5)"] name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -195,6 +205,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "dev" optional = false python-versions = "*" files = [ @@ -271,6 +282,7 @@ pycparser = "*" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -282,6 +294,7 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -366,6 +379,7 @@ files = [ name = "cli-ui" version = "0.17.2" description = "Build Nice User Interfaces In The Terminal" +category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -382,6 +396,7 @@ unidecode = ">=1.0.23,<2.0.0" name = "click" version = "8.1.3" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -396,6 +411,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -407,6 +423,7 @@ files = [ name = "contextlib2" version = "21.6.0" description = "Backports and enhancements for the contextlib module" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -418,6 +435,7 @@ files = [ name = "coverage" version = "7.2.6" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -484,6 +502,7 @@ toml = ["tomli"] name = "cryptography" version = "40.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -525,6 +544,7 @@ tox = ["tox"] name = "cssutils" version = "2.6.0" description = "A CSS Cascading Style Sheets library for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -540,6 +560,7 @@ testing = ["cssselect", "flake8 (<5)", "importlib-resources", "jaraco.test (>=5. name = "dict2css" version = "0.3.0" description = "A ฮผ-library for constructing cascading style sheets from Python dictionaries." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -555,6 +576,7 @@ domdf-python-tools = ">=2.2.0" name = "distlib" version = "0.3.6" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -566,6 +588,7 @@ files = [ name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" +category = "dev" optional = false python-versions = "*" files = [ @@ -576,6 +599,7 @@ files = [ name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -587,6 +611,7 @@ files = [ name = "domdf-python-tools" version = "3.6.1" description = "Helpful functions for Pythonโ€‚๐Ÿโ€‚๐Ÿ› ๏ธ" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -607,6 +632,7 @@ dates = ["pytz (>=2019.1)"] name = "dulwich" version = "0.21.5" description = "Python Git Library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -681,6 +707,7 @@ pgp = ["gpg"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -695,6 +722,7 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.12.0" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -710,6 +738,7 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "p name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -731,6 +760,7 @@ lxml = ["lxml"] name = "identify" version = "2.5.24" description = "File identification library for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -745,6 +775,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -756,6 +787,7 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -767,6 +799,7 @@ files = [ name = "importlib-metadata" version = "6.6.0" description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -786,6 +819,7 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -797,6 +831,7 @@ files = [ name = "interrogate" version = "1.5.0" description = "Interrogate a codebase for docstring coverage." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -822,6 +857,7 @@ tests = ["pytest", "pytest-cov", "pytest-mock"] name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -839,6 +875,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -856,6 +893,7 @@ i18n = ["Babel (>=2.7)"] name = "lockfile" version = "0.12.2" description = "Platform-independent file locking module" +category = "dev" optional = false python-versions = "*" files = [ @@ -867,6 +905,7 @@ files = [ name = "loguru" version = "0.7.0" description = "Python logging made (stupidly) simple" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -885,6 +924,7 @@ dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegu name = "markupsafe" version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -944,6 +984,7 @@ files = [ name = "mock" version = "5.0.2" description = "Rolling backport of unittest.mock for all Pythons" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -960,6 +1001,7 @@ test = ["pytest", "pytest-cov"] name = "msgpack" version = "1.0.5" description = "MessagePack serializer" +category = "dev" optional = false python-versions = "*" files = [ @@ -1032,6 +1074,7 @@ files = [ name = "mypy" version = "1.3.0" description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1078,6 +1121,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1089,6 +1133,7 @@ files = [ name = "natsort" version = "8.3.1" description = "Simple yet flexible natural sorting in Python." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1104,6 +1149,7 @@ icu = ["PyICU (>=1.0.0)"] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1118,6 +1164,7 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1129,6 +1176,7 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1140,6 +1188,7 @@ files = [ name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" +category = "dev" optional = false python-versions = ">=2.6" files = [ @@ -1151,6 +1200,7 @@ files = [ name = "platformdirs" version = "3.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1166,6 +1216,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1181,6 +1232,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.3.2" description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1199,6 +1251,7 @@ virtualenv = ">=20.10.0" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1210,6 +1263,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1221,6 +1275,7 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1235,6 +1290,7 @@ plugins = ["importlib-metadata"] name = "pytest" version = "7.3.1" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1257,6 +1313,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.0" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1275,6 +1332,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1293,6 +1351,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1310,6 +1369,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" +category = "dev" optional = false python-versions = "*" files = [ @@ -1321,6 +1381,7 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1370,6 +1431,7 @@ files = [ name = "reno" version = "4.0.0" description = "RElease NOtes manager" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1391,6 +1453,7 @@ test = ["coverage (>=4.0,!=4.4)", "openstackdocstheme (>=2.2.1)", "python-subuni name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1412,6 +1475,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "ruamel-yaml" version = "0.17.28" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "dev" optional = false python-versions = ">=3" files = [ @@ -1430,6 +1494,7 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] name = "ruamel-yaml-clib" version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1440,7 +1505,8 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -1475,6 +1541,7 @@ files = [ name = "schema" version = "0.7.5" description = "Simple data validation library" +category = "dev" optional = false python-versions = "*" files = [ @@ -1489,6 +1556,7 @@ contextlib2 = ">=0.5.5" name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1505,6 +1573,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1516,6 +1585,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" files = [ @@ -1527,6 +1597,7 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1538,6 +1609,7 @@ files = [ name = "sphinx" version = "6.2.1" description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1573,6 +1645,7 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] name = "sphinx-autodoc-typehints" version = "1.23.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1592,6 +1665,7 @@ type-comment = ["typed-ast (>=1.5.4)"] name = "sphinx-jinja2-compat" version = "0.2.0" description = "Patches Jinja2 v3 to restore compatibility with earlier Sphinx versions." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1607,6 +1681,7 @@ markupsafe = ">=1" name = "sphinx-prompt" version = "1.5.0" description = "Sphinx directive to add unselectable prompt" +category = "dev" optional = false python-versions = "*" files = [ @@ -1621,6 +1696,7 @@ Sphinx = "*" name = "sphinx-tabs" version = "3.4.1" description = "Tabbed views for Sphinx" +category = "dev" optional = false python-versions = "~=3.7" files = [ @@ -1641,6 +1717,7 @@ testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "py name = "sphinx-toolbox" version = "3.4.0" description = "Box of handy tools for Sphinx ๐Ÿงฐ ๐Ÿ“”" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1675,6 +1752,7 @@ testing = ["coincidence (>=0.4.3)", "pygments (>=2.7.4,<=2.13.0)"] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1690,6 +1768,7 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1705,6 +1784,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1720,6 +1800,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1734,6 +1815,7 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1749,6 +1831,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1764,6 +1847,7 @@ test = ["pytest"] name = "tabulate" version = "0.8.10" description = "Pretty-print tabular data" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1778,6 +1862,7 @@ widechars = ["wcwidth"] name = "tbump" version = "6.10.0" description = "Bump software releases" +category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1795,6 +1880,7 @@ tomlkit = ">=0.11,<0.12" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1806,6 +1892,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1817,6 +1904,7 @@ files = [ name = "tomlkit" version = "0.11.8" description = "Style preserving TOML library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1828,6 +1916,7 @@ files = [ name = "trustme" version = "0.9.0" description = "#1 quality TLS certs while you wait, for the discerning tester" +category = "dev" optional = false python-versions = "*" files = [ @@ -1843,6 +1932,7 @@ idna = "*" name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1862,19 +1952,21 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. [[package]] name = "typing-extensions" -version = "4.6.2" +version = "4.6.3" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.2-py3-none-any.whl", hash = "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98"}, - {file = "typing_extensions-4.6.2.tar.gz", hash = "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c"}, + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, ] [[package]] name = "unidecode" version = "1.3.6" description = "ASCII transliterations of Unicode text" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1886,6 +1978,7 @@ files = [ name = "urllib3" version = "1.26.16" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1902,6 +1995,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "virtualenv" version = "20.23.0" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1922,6 +2016,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "dev" optional = false python-versions = "*" files = [ @@ -1933,6 +2028,7 @@ files = [ name = "win32-setctime" version = "1.1.0" description = "A small Python utility to set file creation time on Windows" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1947,6 +2043,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=3.7" files = [ From 88d2ec50c93a2e2fe950976532774bdef3fdfd88 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:08:09 +0000 Subject: [PATCH 02/41] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.3.0 โ†’ 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b8f040e7..c65e50c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/PyCQA/isort From d6142cd855f1b07f6e1748a6fcf594fbf5e97144 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 2 Aug 2023 16:05:57 -0400 Subject: [PATCH 03/41] Clone full git history when building ReadTheDocs so Reno can work --- readthedocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs.yml b/readthedocs.yml index 47ce20aa..ef0713dd 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -7,6 +7,8 @@ build: tools: python: "3.11" jobs: + post_checkout: + - git fetch --unshallow || true post_create_environment: - pip install poetry - poetry config virtualenvs.create false From 02c87dc600a5666897700ef1e2561b2a7bff6adb Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 2 Aug 2023 16:17:24 -0400 Subject: [PATCH 04/41] Excluding pre-commit bot from generated release notes --- .github/release-notes.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release-notes.yml b/.github/release-notes.yml index c0bcc299..ea876851 100644 --- a/.github/release-notes.yml +++ b/.github/release-notes.yml @@ -4,6 +4,7 @@ changelog: - ignore-for-release authors: - dependabot + - pre-commit-ci categories: - title: Security labels: From c2d39d66120b32e7a9efa3fd97ed6a59fde644cb Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Mon, 7 Aug 2023 21:19:06 -0400 Subject: [PATCH 05/41] Fixed reports CLI not setting user header --- aiospamc/cli.py | 5 ++++- .../405-cli-report-doesnt-set-user-11fb68eae45c4178.yaml | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/405-cli-report-doesnt-set-user-11fb68eae45c4178.yaml diff --git a/aiospamc/cli.py b/aiospamc/cli.py index 6ec6bd1e..72264ae2 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -461,9 +461,12 @@ def report( """Report a message to collaborative filtering databases as spam.""" message_data = read_message(message) + headers = Headers() + headers.user = user + headers.message_class = MessageClassOption.spam request = Request( "TELL", - headers={"Message-class": MessageClassValue(MessageClassOption.spam)}, + headers=headers, body=message_data, ) runner = CommandRunner(request, out) diff --git a/releasenotes/notes/405-cli-report-doesnt-set-user-11fb68eae45c4178.yaml b/releasenotes/notes/405-cli-report-doesnt-set-user-11fb68eae45c4178.yaml new file mode 100644 index 00000000..737df88c --- /dev/null +++ b/releasenotes/notes/405-cli-report-doesnt-set-user-11fb68eae45c4178.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Fixed `report` CLI sub-command that did not set the `User` header. :github:issue:`385` From 1b4bc8172e242c862e10517f3cb22966459ac7bf Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Mon, 7 Aug 2023 21:21:27 -0400 Subject: [PATCH 06/41] Bump to 0.10.1 --- aiospamc/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiospamc/__init__.py b/aiospamc/__init__.py index 17f9f762..7da4b6b8 100644 --- a/aiospamc/__init__.py +++ b/aiospamc/__init__.py @@ -20,7 +20,7 @@ __author__ = "Michael Caley" __copyright__ = "Copyright 2016-2023 Michael Caley" __license__ = "MIT" -__version__ = "0.10.0" +__version__ = "0.10.1" __email__ = "mjcaley@darkarctic.com" logger.disable(__package__) diff --git a/pyproject.toml b/pyproject.toml index 4ebface9..5b498701 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiospamc" -version = "0.10.0" +version = "0.10.1" description = "An asyncio-based library to communicate with SpamAssassin's SPAMD service." authors = ["Michael Caley "] license = "MIT" @@ -114,7 +114,7 @@ tbump = "^6.10" github_url = "https://github.com/mjcaley/aiospamc/" [tool.tbump.version] -current = "0.10.0" +current = "0.10.1" regex = ''' (?P\d+) \. From 50e1c1f74d7e776c93bf32f33161dee61136069d Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Thu, 17 Aug 2023 16:47:49 -0400 Subject: [PATCH 07/41] Add client certificates feature (#408) Add feature #404, client certificates Fix #394, --ssl option wrong type --- aiospamc/cli.py | 451 ++++++++--- aiospamc/client.py | 56 +- aiospamc/connections.py | 351 +++++++-- aiospamc/frontend.py | 344 +++++++-- poetry.lock | 101 +-- pyproject.toml | 8 + ...sl-option-wrong-type-a4320c34bfd4ddaa.yaml | 4 + ...d-client-certificate-24076b39e96cd311.yaml | 7 + tests/conftest.py | 277 +++++-- tests/spamd-image/Dockerfile | 9 + tests/test_cli.py | 643 +++++++++++++--- tests/test_client.py | 219 +----- tests/test_connections.py | 162 ++-- tests/test_frontend.py | 722 ++++++++++++------ tests/test_integration_example_messages.py | 8 +- tests/test_integration_ssl.py | 46 +- tests/test_integration_ssl_client.py | 147 ++++ tests/test_integration_tcp.py | 34 +- tests/test_integration_unix.py | 65 +- 19 files changed, 2552 insertions(+), 1102 deletions(-) create mode 100644 releasenotes/notes/394-ssl-option-wrong-type-a4320c34bfd4ddaa.yaml create mode 100644 releasenotes/notes/404-add-client-certificate-24076b39e96cd311.yaml create mode 100644 tests/spamd-image/Dockerfile create mode 100644 tests/test_integration_ssl_client.py diff --git a/aiospamc/cli.py b/aiospamc/cli.py index 72264ae2..0cedd2cd 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -7,13 +7,18 @@ from enum import Enum from getpass import getuser from io import BufferedReader -from typing import Any, Optional, Union +from pathlib import Path +from typing import Optional import typer from loguru import logger from typing_extensions import Annotated -from aiospamc.exceptions import AIOSpamcConnectionFailed, ParseError +from aiospamc.exceptions import ( + AIOSpamcConnectionFailed, + BadResponse, + ClientTimeoutException, +) from aiospamc.header_values import ( ActionOption, Headers, @@ -24,7 +29,7 @@ from . import __version__ from .client import Client, Request -from .connections import Timeout +from .connections import ConnectionManagerBuilder, SSLContextBuilder, Timeout from .responses import Response, ResponseException app = typer.Typer(no_args_is_help=True) @@ -43,18 +48,145 @@ def __str__(self) -> str: # Exit codes SUCCESS = NOT_SPAM = PING_SUCCESS = 0 IS_SPAM = REPORT_FAILED = REVOKE_FAILED = 1 -PARSE_ERROR = 3 +BAD_RESPONSE = 3 TIMEOUT_ERROR = 4 CONNECTION_ERROR = 5 UNEXPECTED_ERROR = 6 FILE_NOT_FOUND_ERROR = 7 +class CliClientBuilder: + """Client builder for CLI arguments.""" + + def __init__(self): + """Constructor for the ClieClientBuilder.""" + + self._connection_builder = ConnectionManagerBuilder() + self._ssl = False + self._ssl_builder = SSLContextBuilder() + + def build(self) -> Client: + """Builds the `Client`. + + :return: An instance of :class:`aiospamc.client.Client`. + """ + + if self._ssl: + ssl_context = self._ssl_builder.build() + self._connection_builder.add_ssl_context(ssl_context) + connection_manager = self._connection_builder.build() + + return Client(connection_manager) + + def with_connection( + self, + host: str = "localhost", + port: int = 783, + socket_path: Optional[Path] = None, + ) -> "CliClientBuilder": + """Sets the type of connection manager to use. + + Defaults to TCP, but if a Unix socket is provided it will be used. + + :param host: TCP hostname to use. + :param port: TCP port to use. + :param socket_path: Path to the Unix socket. + + :return: This builder instance. + """ + + if socket_path: + self._connection_builder = self._connection_builder.with_unix_socket( + socket_path + ) + else: + self._connection_builder = self._connection_builder.with_tcp(host, port) + + return self + + def set_timeout(self, timeout: Timeout) -> "CliClientBuilder": + """Sets the timeout for the connection. + + :param timeout: Timeout object. + + :return: This builder instance. + """ + + self._connection_builder.set_timeout(timeout) + + return self + + def add_verify(self, verify: bool) -> "CliClientBuilder": + """Adds an SSL context to the connection manager. + + :param verify: How to configure the SSL context. If `True`, add the default + certificate authorities. If `False`, accept any certificate. + + :return: This builder instance. + """ + + self._ssl = True + self._ssl_builder.add_default_ca() + if not verify: + self._ssl_builder.dont_verify() + + return self + + def add_ca_cert(self, ca_cert: Optional[Path]) -> "CliClientBuilder": + """Adds trusted certificate authorities. + + :param ca_cert: Path to the cerficiate file or directory. + + :return: This builder instance. + """ + + if ca_cert is None: + return self + + self._ssl = True + if ca_cert.is_dir(): + self._ssl_builder.add_ca_dir(ca_cert) + elif ca_cert.is_file(): + self._ssl_builder.add_ca_file(ca_cert) + else: + raise FileNotFoundError( + f"Unable to find CA certificate file/directory {ca_cert}" + ) + + return self + + def add_client_cert( + self, + cert: Optional[Path], + key: Optional[Path] = None, + password: Optional[str] = None, + ) -> "CliClientBuilder": + """Add a client certificate to authenticate to the server. + + :param cert: Path to the client certificate and optionally the key. + :param key: Path to the client key. + :param password: Password of the client key. + + :return: This builder instance. + """ + + if cert is None: + return self + + if self._ssl is False: + self._ssl = True + self.add_verify(True) + self._ssl_builder.add_client(cert, key, password) + + return self + + class CommandRunner: """Object to execute requests and handle exceptions.""" def __init__( self, + client: Client, request: Request, output: Output = Output.Text, ): @@ -65,50 +197,30 @@ def __init__( :param output: Output format when printing to console. """ + self.client = client self.request = request self.response: Optional[Response] = None self.output = output self.exception: Optional[Exception] = None self.exit_code = SUCCESS - self._client = Client() self._logger = logger.bind(request=request) - async def run( - self, - host: Optional[str] = None, - port: Optional[int] = None, - socket_path: Optional[str] = None, - timeout: Optional[Timeout] = None, - verify: Any = None, - ) -> Response: + async def run(self) -> Response: """Send the request, get the response and handle common exceptions. - :param host: Hostname or IP address of the SPAMD service, defaults to localhost. - :param port: Port number for the SPAMD service, defaults to 783. - :param socket_path: Path to Unix socket. - :param timeout: Timeout settings. - :param verify: - Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. - `False` will use SSL, but not verify the root certificates. Passing a string to a filename - will use the path to verify the root certificates. + :return: The response. """ - ssl_context = self._client.ssl_context_factory(verify) - connection = self._client.connection_factory( - host, port, socket_path, timeout, ssl_context - ) - parser = self._client.parser_factory() - self._logger = self._logger.bind(host=host, port=port, socket_path=socket_path) self._logger.info("Sending request") - ssl_context = self._client.ssl_context_factory(verify) - connection = self._client.connection_factory( - host, port, socket_path, timeout, ssl_context - ) - parser = self._client.parser_factory() try: - response = await self._client.request(self.request, connection, parser) + response = await self.client.request(self.request) + except BadResponse as e: + self._logger.exception(e) + self.exit_code = BAD_RESPONSE + self.exception = e + self.exit("Error parsing response", True) except ResponseException as e: self._logger = self._logger.bind(response=e.response) self._logger.exception(e) @@ -116,12 +228,7 @@ async def run( self.exit_code = int(self.response.status_code) self.exception = e self.exit(f"Response error from server: {self.response.message}", True) - except ParseError as e: - self._logger.exception(e) - self.exit_code = PARSE_ERROR - self.exception = e - self.exit("Error parsing response", True) - except asyncio.TimeoutError as e: + except (asyncio.TimeoutError, ClientTimeoutException) as e: self._logger.exception(e) self.exit_code = TIMEOUT_ERROR self.exception = e @@ -141,7 +248,7 @@ async def run( def to_json(self) -> str: """Converts the object to a JSON string. - :returns: JSON string. + :return: JSON string. """ obj = { @@ -185,31 +292,53 @@ def ping( ), ] = 783, socket_path: Annotated[ - str, + Optional[Path], typer.Option( metavar="PATH", help="Path to use when connecting using Unix sockets" ), - ] = "", - ssl: Annotated[ - Optional[bool], - typer.Option( - help="Use SSL to communicate with the daemon. Setting the environment variable to a certificate file will use that to verify the server certificates.", - envvar="AIOSPAMC_CERT_FILE", - ), ] = None, + ssl: Annotated[ + bool, typer.Option(help="Use SSL to communicate with the daemon.") + ] = False, timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, out: Annotated[Output, typer.Option(help="Output format for stdout")] = Output.Text, + ca_cert: Annotated[ + Optional[Path], + typer.Option( + help="Path to the CA certificates if overriding", + envvar="AIOSPAMC_CERT_FILE", + ), + ] = None, + client_cert: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate") + ] = None, + client_key: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate's key") + ] = None, + key_password: Annotated[ + Optional[str], typer.Option(help="Password for the client certificate key") + ] = None, ): """Pings the SpamAssassin daemon. A successful pong exits with code 0. """ + client_builder = CliClientBuilder() + client_builder.with_connection(host, port, socket_path).set_timeout( + Timeout(timeout) + ) + if ssl: + client_builder.add_verify(True).add_ca_cert(ca_cert).add_client_cert( + client_cert, client_key, key_password + ) + client = client_builder.build() + request = Request("PING") - runner = CommandRunner(request, out) - response = asyncio.run(runner.run(host, port, socket_path, Timeout(timeout), ssl)) + runner = CommandRunner(client, request, out) + response = asyncio.get_event_loop().run_until_complete(runner.run()) runner.exit(response.message) @@ -249,23 +378,35 @@ def check( ), ] = 783, socket_path: Annotated[ - str, + Optional[Path], typer.Option( metavar="PATH", help="Path to use when connecting using Unix sockets" ), - ] = "", - ssl: Annotated[ - Optional[bool], - typer.Option( - help="Use SSL to communicate with the daemon. Setting the environment variable to a certificate file will use that to verify the server certificates.", - envvar="AIOSPAMC_CERT_FILE", - ), ] = None, + ssl: Annotated[ + bool, typer.Option(help="Use SSL to communicate with the daemon.") + ] = False, user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, out: Annotated[Output, typer.Option(help="Output format for stdout")] = Output.Text, + ca_cert: Annotated[ + Optional[Path], + typer.Option( + help="Path to the CA certificates if overriding", + envvar="AIOSPAMC_CERT_FILE", + ), + ] = None, + client_cert: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate") + ] = None, + client_key: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate's key") + ] = None, + key_password: Annotated[ + Optional[str], typer.Option(help="Password for the client certificate key") + ] = None, ): """Submits a message to SpamAssassin and returns the processed message.""" @@ -273,8 +414,19 @@ def check( headers = Headers() headers.user = user request = Request("PROCESS", headers=headers, body=message_data) - runner = CommandRunner(request, out) - response = asyncio.run(runner.run(host, port, socket_path, Timeout(timeout), ssl)) + + client_builder = CliClientBuilder() + client_builder.with_connection(host, port, socket_path).set_timeout( + Timeout(timeout) + ) + if ssl: + client_builder.add_verify(True).add_ca_cert(ca_cert).add_client_cert( + client_cert, client_key, key_password + ) + client = client_builder.build() + + runner = CommandRunner(client, request, out) + response = asyncio.get_event_loop().run_until_complete(runner.run()) if spam_header := response.headers.spam: if spam_header.value: @@ -313,23 +465,35 @@ def learn( ), ] = 783, socket_path: Annotated[ - str, + Optional[Path], typer.Option( metavar="PATH", help="Path to use when connecting using Unix sockets" ), - ] = "", - ssl: Annotated[ - Optional[bool], - typer.Option( - help="Use SSL to communicate with the daemon. Setting the environment variable to a certificate file will use that to verify the server certificates.", - envvar="AIOSPAMC_CERT_FILE", - ), ] = None, + ssl: Annotated[ + bool, typer.Option(help="Use SSL to communicate with the daemon.") + ] = False, user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, out: Annotated[Output, typer.Option(help="Output format for stdout")] = Output.Text, + ca_cert: Annotated[ + Optional[Path], + typer.Option( + help="Path to the CA certificates if overriding", + envvar="AIOSPAMC_CERT_FILE", + ), + ] = None, + client_cert: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate") + ] = None, + client_key: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate's key") + ] = None, + key_password: Annotated[ + Optional[str], typer.Option(help="Password for the client certificate key") + ] = None, ): """Ask server to learn the message as spam or ham.""" @@ -343,8 +507,19 @@ def learn( headers=headers, body=message_data, ) - runner = CommandRunner(request, out) - response = asyncio.run(runner.run(host, port, socket_path, Timeout(timeout), ssl)) + + client_builder = CliClientBuilder() + client_builder.with_connection(host, port, socket_path).set_timeout( + Timeout(timeout) + ) + if ssl: + client_builder.add_verify(True).add_ca_cert(ca_cert).add_client_cert( + client_cert, client_key, key_password + ) + client = client_builder.build() + + runner = CommandRunner(client, request, out) + response = asyncio.get_event_loop().run_until_complete(runner.run()) if response.headers.did_set: runner.exit("Message successfully learned") @@ -377,23 +552,35 @@ def forget( ), ] = 783, socket_path: Annotated[ - str, + Optional[Path], typer.Option( metavar="PATH", help="Path to use when connecting using Unix sockets" ), - ] = "", - ssl: Annotated[ - Optional[bool], - typer.Option( - help="Use SSL to communicate with the daemon. Setting the environment variable to a certificate file will use that to verify the server certificates.", - envvar="AIOSPAMC_CERT_FILE", - ), ] = None, + ssl: Annotated[ + bool, typer.Option(help="Use SSL to communicate with the daemon.") + ] = False, user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, out: Annotated[Output, typer.Option(help="Output format for stdout")] = Output.Text, + ca_cert: Annotated[ + Optional[Path], + typer.Option( + help="Path to the CA certificates if overriding", + envvar="AIOSPAMC_CERT_FILE", + ), + ] = None, + client_cert: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate") + ] = None, + client_key: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate's key") + ] = None, + key_password: Annotated[ + Optional[str], typer.Option(help="Password for the client certificate key") + ] = None, ): """Forgets the classification of a message.""" @@ -406,8 +593,19 @@ def forget( headers=headers, body=message_data, ) - runner = CommandRunner(request, out) - response = asyncio.run(runner.run(host, port, socket_path, Timeout(timeout), ssl)) + + client_builder = CliClientBuilder() + client_builder.with_connection(host, port, socket_path).set_timeout( + Timeout(timeout) + ) + if ssl: + client_builder.add_verify(True).add_ca_cert(ca_cert).add_client_cert( + client_cert, client_key, key_password + ) + client = client_builder.build() + + runner = CommandRunner(client, request, out) + response = asyncio.get_event_loop().run_until_complete(runner.run()) if response.headers.did_remove: runner.exit("Message successfully forgotten") @@ -440,23 +638,35 @@ def report( ), ] = 783, socket_path: Annotated[ - str, + Optional[Path], typer.Option( metavar="PATH", help="Path to use when connecting using Unix sockets" ), - ] = "", - ssl: Annotated[ - Optional[bool], - typer.Option( - help="Use SSL to communicate with the daemon. Setting the environment variable to a certificate file will use that to verify the server certificates.", - envvar="AIOSPAMC_CERT_FILE", - ), ] = None, + ssl: Annotated[ + bool, typer.Option(help="Use SSL to communicate with the daemon.") + ] = False, user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, out: Annotated[Output, typer.Option(help="Output format for stdout")] = Output.Text, + ca_cert: Annotated[ + Optional[Path], + typer.Option( + help="Path to the CA certificates if overriding", + envvar="AIOSPAMC_CERT_FILE", + ), + ] = None, + client_cert: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate") + ] = None, + client_key: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate's key") + ] = None, + key_password: Annotated[ + Optional[str], typer.Option(help="Password for the client certificate key") + ] = None, ): """Report a message to collaborative filtering databases as spam.""" @@ -469,8 +679,19 @@ def report( headers=headers, body=message_data, ) - runner = CommandRunner(request, out) - response = asyncio.run(runner.run(host, port, socket_path, Timeout(timeout), ssl)) + + client_builder = CliClientBuilder() + client_builder.with_connection(host, port, socket_path).set_timeout( + Timeout(timeout) + ) + if ssl: + client_builder.add_verify(True).add_ca_cert(ca_cert).add_client_cert( + client_cert, client_key, key_password + ) + client = client_builder.build() + + runner = CommandRunner(client, request, out) + response = asyncio.get_event_loop().run_until_complete(runner.run()) if response.headers.did_set and response.headers.did_set.remote is True: runner.exit("Message successfully reported") @@ -504,23 +725,35 @@ def revoke( ), ] = 783, socket_path: Annotated[ - str, + Optional[Path], typer.Option( metavar="PATH", help="Path to use when connecting using Unix sockets" ), - ] = "", - ssl: Annotated[ - Optional[bool], - typer.Option( - help="Use SSL to communicate with the daemon. Setting the environment variable to a certificate file will use that to verify the server certificates.", - envvar="AIOSPAMC_CERT_FILE", - ), ] = None, + ssl: Annotated[ + bool, typer.Option(help="Use SSL to communicate with the daemon.") + ] = False, user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, out: Annotated[Output, typer.Option(help="Output format for stdout")] = Output.Text, + ca_cert: Annotated[ + Optional[Path], + typer.Option( + help="Path to the CA certificates if overriding", + envvar="AIOSPAMC_CERT_FILE", + ), + ] = None, + client_cert: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate") + ] = None, + client_key: Annotated[ + Optional[Path], typer.Option(help="Filename of the client certificate's key") + ] = None, + key_password: Annotated[ + Optional[str], typer.Option(help="Password for the client certificate key") + ] = None, ): """Revoke a message to collaborative filtering databases.""" @@ -536,8 +769,20 @@ def revoke( }, body=message_data, ) - runner = CommandRunner(request, out) - response = asyncio.run(runner.run(host, port, socket_path, Timeout(timeout), ssl)) + + client_builder = CliClientBuilder() + client_builder.with_connection(host, port, socket_path).set_timeout( + Timeout(timeout) + ) + if ssl: + client_builder.add_verify(True).add_ca_cert(ca_cert).add_client_cert( + client_cert, client_key, key_password + ) + client = client_builder.build() + + runner = CommandRunner(client, request, out) + response = asyncio.get_event_loop().run_until_complete(runner.run()) + if response.headers.did_remove and response.headers.did_remove.remote: runner.exit("Message successfully revoked") else: @@ -572,9 +817,9 @@ def main( bool, typer.Option( "--version", - is_flag=True, callback=version_callback, - help="Output format for stdout", + is_eager=True, + help="Print version of aiospamc", ), ] = False, debug: Annotated[ @@ -587,4 +832,6 @@ def main( ), ] = False, ): + """aiospamc sends messages to the SpamAssasin daemon.""" + pass diff --git a/aiospamc/client.py b/aiospamc/client.py index 735c6b9c..c988cdf7 100644 --- a/aiospamc/client.py +++ b/aiospamc/client.py @@ -1,67 +1,30 @@ """Module implementing client objects that all requests go through.""" -from ssl import SSLContext -from typing import Any, Callable, Optional, Type - from loguru import logger -from .connections import ( - ConnectionManager, - Timeout, - new_connection_manager, - new_ssl_context, -) +from .connections import ConnectionManager from .exceptions import BadResponse from .incremental_parser import ParseError, ResponseParser from .requests import Request from .responses import Response from .user_warnings import raise_warnings -ConnectionFactory = Callable[ - [ - Optional[str], - Optional[int], - Optional[str], - Optional[Timeout], - Optional[SSLContext], - ], - ConnectionManager, -] -SSLFactory = Callable[[Any], Optional[SSLContext]] - class Client: - """Client class containing factories.""" + """Client object to submit requests.""" - default_ssl_context_factory: Optional[SSLFactory] = staticmethod(new_ssl_context) - default_connection_factory: ConnectionFactory = staticmethod(new_connection_manager) - default_parser_factory: Type[ResponseParser] = ResponseParser - - def __init__( - self, ssl_context_factory=None, connection_factory=None, parser_factory=None - ): + def __init__(self, connection_manager: ConnectionManager): """Client constructor. - :param default_ssl_context_factory: SSL context factory function. - :param default_connection_factory: `ConnectionManager` factory function. - :param default_parser_factory: Response parser type. + :param connection_manager: Instance of a connection manager. """ - self.ssl_context_factory = ( - ssl_context_factory or self.default_ssl_context_factory - ) - self.connection_factory = connection_factory or self.default_connection_factory - self.parser_factory = parser_factory or self.default_parser_factory + self.connection_manager = connection_manager - @staticmethod - async def request( - req: Request, connection: ConnectionManager, parser: ResponseParser - ) -> Response: + async def request(self, req: Request): """Sends a request and returns the parsed response. :param req: The request to send. - :param connection: Instance of a `ConnectionManager`. - :param parser: Instance of `ResponseParser`. :return: The parsed response. @@ -87,16 +50,17 @@ async def request( """ context_logger = logger.bind( - connection=connection, + connection=self.connection_manager, request=req, ) - raise_warnings(req, connection) + raise_warnings(req, self.connection_manager) context_logger.info("Sending {} request", req.verb) - response = await connection.request(bytes(req)) + response = await self.connection_manager.request(bytes(req)) context_logger = context_logger.bind(response_bytes=response) try: + parser = ResponseParser() parsed_response = parser.parse(response) except ParseError as error: context_logger.exception("Error parsing response") diff --git a/aiospamc/connections.py b/aiospamc/connections.py index ee0f36bd..047f3ef8 100644 --- a/aiospamc/connections.py +++ b/aiospamc/connections.py @@ -4,8 +4,9 @@ import asyncio import ssl +from enum import Enum, auto from pathlib import Path -from typing import Any, Optional, Tuple +from typing import Any, Optional, Tuple, Union import certifi import loguru @@ -30,7 +31,7 @@ def __init__( :param response: The length of time in seconds to allow for a response from the server before timing out. """ - self.total = total + self.total = float(total) self.connection = connection self.response = response @@ -44,6 +45,99 @@ def __repr__(self): ) +class ConnectionManagerBuilder: + """Builder for connection managers.""" + + class ManagerType(Enum): + """Define connection manager type during build.""" + + Undefined = auto() + Tcp = auto() + Unix = auto() + + def __init__(self): + """ConnectionManagerBuilder constructor.""" + + self._manager_type = self.ManagerType.Undefined + self._tcp_builder = TcpConnectionManagerBuilder() + self._unix_builder = UnixConnectionManagerBuilder() + self._ssl_builder = SSLContextBuilder() + self._ssl = False + self._timeout = None + + def build(self) -> Union[UnixConnectionManager, TcpConnectionManager]: + """Builds the `ConnectionManager`. + + :return: An instance of :class:`aiospamc.connections.TcpConnectionManager` + or :class:`aiospamc.connections.UnixConnectionManager` + """ + + if self._manager_type is self.ManagerType.Undefined: + raise ValueError( + "Connection type is undefined, builder must be called with 'with_unix_socket' or 'with_tcp'" + ) + elif self._manager_type is self.ManagerType.Tcp: + ssl_context = None if not self._ssl else self._ssl_builder.build() + self._tcp_builder.set_ssl_context(ssl_context) + return self._tcp_builder.set_timeout(self._timeout).build() + else: + return self._unix_builder.set_timeout(self._timeout).build() + + def with_unix_socket(self, path: Path) -> ConnectionManagerBuilder: + """Configures the builder to use a Unix socket connection. + + :param path: Path to the Unix socket. + + :return: This builder instance. + """ + + self._manager_type = self.ManagerType.Unix + self._unix_builder.set_path(path) + self._tcp_host = self._tcp_port = None + + return self + + def with_tcp(self, host: str, port: int = 783) -> ConnectionManagerBuilder: + """Configures the builder to use a TCP connection. + + :param host: Hostname to use. + :param port: Port to use. + + :return: This builder instance. + """ + + self._manager_type = self.ManagerType.Tcp + self._tcp_builder.set_host(host).set_port(port) + self._unix_path = None + + return self + + def add_ssl_context(self, context: ssl.SSLContext) -> ConnectionManagerBuilder: + """Adds an SSL context when a TCP connection is being used. + + :param context: `ssl.SSLContext` instance. + + :return: This builder instance. + """ + + self._ssl_builder.with_context(context) + self._ssl = True + + return self + + def set_timeout(self, timeout: Timeout) -> ConnectionManagerBuilder: + """Sets the timeout for the connection. + + :param timeout: Timeout object. + + :return: This builder instance. + """ + + self._timeout = timeout + + return self + + class ConnectionManager: """Stores connection parameters and creates connections.""" @@ -79,9 +173,9 @@ async def request(self, data: bytes) -> bytes: try: response = await asyncio.wait_for(self._send(data), self.timeout.total) - except asyncio.TimeoutError as error: + except asyncio.TimeoutError: self.logger.exception("Total timeout reached") - raise ClientTimeoutException from error + raise return response @@ -155,6 +249,67 @@ def connection_string(self) -> str: return self._connection_string +class TcpConnectionManagerBuilder: + """Builder for :class:`aiospamc.connections.TcpConnectionManager`""" + + def __init__(self): + """`TcpConnectionManagerBuilder` constructor.""" + + self._args = {} + + def build(self) -> TcpConnectionManager: + """Builds the `TcpConnectionManager`. + + :return: An instance of :class:`aiospamc.connections.TcpConnectionManager`. + """ + + return TcpConnectionManager(**self._args) + + def set_host(self, host: str) -> TcpConnectionManagerBuilder: + """Sets the host to use. + + :param host: Hostname to use. + + :return: This builder instance. + """ + + self._args["host"] = host + return self + + def set_port(self, port: int) -> TcpConnectionManagerBuilder: + """Sets the port to use. + + :param port: Port to use. + + :return: This builder instance. + """ + + self._args["port"] = port + return self + + def set_ssl_context(self, context: ssl.SSLContext) -> TcpConnectionManagerBuilder: + """Set an SSL context. + + :param context: An instance of `ssl.SSLContext`. + + :return: This builder instance. + """ + + self._args["ssl_context"] = context + return self + + def set_timeout(self, timeout: Timeout) -> TcpConnectionManagerBuilder: + """Sets the timeout for the connection. + + :param timeout: Timeout object. + + :return: This builder instance. + """ + + self._args["timeout"] = timeout + return self + + class TcpConnectionManager(ConnectionManager): """Connection manager for TCP connections.""" @@ -197,17 +352,56 @@ async def open(self) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: return reader, writer +class UnixConnectionManagerBuilder: + """Builder for `UnixConnectionManager`.""" + + def __init__(self): + """`UnixConnectionManagerBuilder` constructor.""" + + self._args = {} + + def build(self) -> UnixConnectionManager: + """Builds a `UnixConnectionManager`. + + :return: An instance of :class:`aiospamc.connections.UnixConnectionManager`. + """ + + return UnixConnectionManager(**self._args) + + def set_path(self, path: Path) -> UnixConnectionManagerBuilder: + """Sets the unix socket path. + + :param path: Path to the Unix socket. + + :return: This builder instance. + """ + + self._args["path"] = path + return self + + def set_timeout(self, timeout: Timeout) -> UnixConnectionManagerBuilder: + """Sets the timeout for the connection. + + :param timeout: Timeout object. + + :return: This builder instance. + """ + + self._args["timeout"] = timeout + return self + + class UnixConnectionManager(ConnectionManager): """Connection manager for Unix pipes.""" - def __init__(self, path: str, timeout: Optional[Timeout] = None): + def __init__(self, path: Path, timeout: Optional[Timeout] = None): """UnixConnectionManager constructor. :param path: Unix socket path. :param timeout: Timeout configuration """ - super().__init__(path, timeout) + super().__init__(str(path), timeout) self.path = path async def open(self) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: @@ -227,52 +421,101 @@ async def open(self) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: return reader, writer -def new_ssl_context(verify: Optional[Any]) -> Optional[ssl.SSLContext]: - """Creates an SSL context based on the supplied parameter. - - :param verify: Use SSL for the connection. If True, will use root certificates. - If False, will not verify the certificate. If a string to a path or a Path - object, the connection will use the certificates found there. - """ - - if verify is None: - return None - elif verify is True: - return ssl.create_default_context(cafile=certifi.where()) - elif verify is False: - context = ssl.create_default_context(cafile=certifi.where()) - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - return context - else: - cert_path = Path(verify).absolute() - if cert_path.is_dir(): - return ssl.create_default_context(capath=str(cert_path)) - elif cert_path.is_file(): - return ssl.create_default_context(cafile=str(cert_path)) +class SSLContextBuilder: + """SSL context builder.""" + + def __init__(self): + """Builder contstructor. Sets up a default SSL context.""" + + self._context = ssl.create_default_context() + + def build(self) -> ssl.SSLContext: + """Builds the SSL context. + + :return: An instance of `ssl.SSLContext`. + """ + + return self._context + + def with_context(self, context: ssl.SSLContext) -> SSLContextBuilder: + """Use the SSL context. + + :param context: Provided SSL context. + + :return: The builder instance. + """ + + self._context = context + + return self + + def add_ca_file(self, file: Path) -> SSLContextBuilder: + """Add certificate authority from a file. + + :param file: File of concatenated certificates. + + :return: The builder instance. + """ + + self._context.load_verify_locations(cafile=file) + + return self + + def add_ca_dir(self, dir: Path) -> SSLContextBuilder: + """Add certificate authority from a directory. + + :param dir: Directory of certificates. + + :return: The builder instance. + """ + + self._context.load_verify_locations(capath=dir) + + return self + + def add_ca(self, path: Path) -> SSLContextBuilder: + """Add a certificate authority. + + :param path: Directory or file of certificates. + + :return: The builder instance. + """ + + if path.is_dir(): + return self.add_ca_dir(path) + elif path.is_file(): + return self.add_ca_file(path) else: - raise FileNotFoundError(f"Certificate path does not exist at {verify}") - - -def new_connection_manager( - host: Optional[str] = None, - port: Optional[int] = None, - socket_path: Optional[str] = None, - timeout: Optional[Timeout] = None, - context: Optional[ssl.SSLContext] = None, -) -> ConnectionManager: - """Create a new connection manager. - - :param host: TCP hostname. - :param port: TCP port number. - :param socket_path: Unix socket path. - :param timeout: Timeout configuration. - :param context: SSL context configuration. - """ - - if socket_path: - return UnixConnectionManager(socket_path, timeout=timeout) - elif host and port: - return TcpConnectionManager(host, port, context, timeout) - else: - raise ValueError('Either "host" and "port" or "socket_path" must be specified.') + raise FileNotFoundError(path) + + def add_default_ca(self) -> SSLContextBuilder: + """Add default certificate authorities. + + :return: The builder instance. + """ + + self._context.load_verify_locations(cafile=certifi.where()) + + return self + + def add_client( + self, file: Path, key: Optional[Path] = None, password: Optional[str] = None + ) -> SSLContextBuilder: + """Add client certificate. + + :param file: Path to the client certificate. + :param key: Path to the key. + :param password: Password of the key. + """ + + self._context.load_cert_chain(file, key, password) + + return self + + def dont_verify(self) -> SSLContextBuilder: + """Set the context to not verify certificates.""" + + self._context.check_hostname = False + self._context.verify_mode = ssl.CERT_NONE + + return self diff --git a/aiospamc/frontend.py b/aiospamc/frontend.py index 384501cd..a2e934ce 100644 --- a/aiospamc/frontend.py +++ b/aiospamc/frontend.py @@ -1,23 +1,152 @@ """Frontend functions for the package.""" -from typing import Any, Dict, Optional, SupportsBytes, Tuple, Union +from __future__ import annotations + +import ssl +from pathlib import Path +from typing import Any, Dict, Optional, SupportsBytes, Tuple, Union, cast from loguru import logger from .client import Client -from .connections import Timeout -from .header_values import ( - ActionOption, - CompressValue, - MessageClassOption, - MessageClassValue, - UserValue, -) +from .connections import ConnectionManagerBuilder, SSLContextBuilder, Timeout +from .header_values import ActionOption, MessageClassOption, MessageClassValue from .incremental_parser import parse_set_remove_value from .requests import Request from .responses import Response +class FrontendClientBuilder: + """Builds the :class:`aiospamc.client.Client` based off of frontend arguments.""" + + def __init__(self): + """Constructor for FrontendClientBuilder.""" + + self._connection_builder = ConnectionManagerBuilder() + self._ssl = False + self._ssl_builder = SSLContextBuilder() + + def build(self) -> Client: + """Builds the `Client`. + + :return: An instance of :class:`aiospamc.client.Client`. + """ + + if self._ssl: + self._connection_builder.add_ssl_context(self._ssl_builder.build()) + connection_manager = self._connection_builder.build() + + return Client(connection_manager) + + def with_connection( + self, + host: str = "localhost", + port: int = 783, + socket_path: Optional[Path] = None, + ) -> FrontendClientBuilder: + """Sets the type of connection manager to use. + + Defaults to TCP, but if a Unix socket is provided it will be used. + + :param host: TCP hostname to use. + :param port: TCP port to use. + :param socket_path: Path to the Unix socket. + + :return: This builder instance. + """ + + if socket_path: + self._connection_builder = self._connection_builder.with_unix_socket( + socket_path + ) + else: + self._connection_builder = self._connection_builder.with_tcp(host, port) + + return self + + def add_verify( + self, verify: Union[bool, Path, ssl.SSLContext, None] = None + ) -> FrontendClientBuilder: + """Adds an SSL context to the connection manager. + + :param verify: How to configure the SSL context. If `True`, add the default + certificate authorities. If `False`, accept any certificate. If a `Path`, + add the certificates from it. If an `ssl.SSLContext`, then use it. + + :return: This builder instance. + """ + + if verify is None: + return self + + self._ssl = True + + if verify is True: + self._ssl_builder.add_default_ca() + elif verify is False: + self._ssl_builder.add_default_ca().dont_verify() + elif isinstance(verify, ssl.SSLContext): + self._ssl_builder.with_context(verify) + else: + self._ssl_builder.add_ca(verify) + + return self + + def add_client_cert( + self, + cert: Optional[ + Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + ] + ], + ) -> FrontendClientBuilder: + """Add a client certificate to authenticate to the server. + + :param cert: Client certificate. Takes up to three a three tuple value. + 1. Path to the certificate and key. + 2. Path to the certificate and path to the key. + 3. Path to the certificate, path to the key, and password of the key. + + :return: This builder instance. + """ + + if cert is None: + return self + + if not self._ssl: + self.add_verify(True) + + if isinstance(cert, Path): + self._ssl_builder.add_client(cert) + elif isinstance(cert, tuple) and len(cert) == 2: + client, key = cast(Tuple[Path, Optional[Path]], cert) + self._ssl_builder.add_client(client, key) + elif isinstance(cert, tuple) and len(cert) == 3: + client, key, password = cast( + Tuple[Path, Optional[Path], Optional[str]], cert + ) + self._ssl_builder.add_client(client, key, password) + else: + raise TypeError("Unexepected value") + + return self + + def set_timeout(self, timeout: Optional[Timeout] = None) -> FrontendClientBuilder: + """Sets the timeout for the connection. + + :param timeout: Timeout object. + + :return: This builder instance. + """ + + if timeout: + self._connection_builder.set_timeout(timeout) + + return self + + def _add_compress_header(request: Request, compress: bool): """Adds a compress header to the request if specified. @@ -45,12 +174,18 @@ async def check( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Optional[ + Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + ] + ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return a response with a score header. @@ -65,6 +200,9 @@ async def check( will use the path to verify the root certificates. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. + :param client_cert: Client certificate file to use. + :param client_key: Key file to use for the client certificate. + :param key_password: Password to use for the client key if needed. :return: A successful response with a "Spam" header showing if the message is @@ -103,14 +241,16 @@ async def check( ) context_logger.info("Sending CHECK request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling check function") raise @@ -127,9 +267,15 @@ async def headers( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, user: Optional[str] = None, compress: bool = False, **kwargs, @@ -186,14 +332,16 @@ async def headers( ) context_logger.info("Sending HEADERS request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling headers function") raise @@ -209,9 +357,15 @@ async def ping( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Optional[Any] = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, **kwargs, ) -> Response: """Sends a ping to the SPAMD service. @@ -257,14 +411,16 @@ async def ping( ) context_logger.info("Sending PING request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling ping function") raise @@ -281,9 +437,15 @@ async def process( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, user: Optional[str] = None, compress: bool = False, **kwargs, @@ -340,14 +502,16 @@ async def process( ) context_logger.info("Sending PROCESS request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling process function") raise @@ -364,9 +528,15 @@ async def report( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, user: Optional[str] = None, compress: bool = False, **kwargs, @@ -422,14 +592,16 @@ async def report( ) context_logger.info("Sending REPORT request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling report function") raise @@ -446,9 +618,15 @@ async def report_if_spam( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, user: Optional[str] = None, compress: bool = False, **kwargs, @@ -505,14 +683,16 @@ async def report_if_spam( ) context_logger.info("Sending REPORT_IFSPAM request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling report_if_spam function") raise @@ -529,9 +709,15 @@ async def symbols( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Union[bool, Path, ssl.SSLContext, None] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, user: Optional[str] = None, compress: bool = False, **kwargs, @@ -588,14 +774,16 @@ async def symbols( ) context_logger.info("Sending SYMBOLS request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling symbols function") raise @@ -615,9 +803,15 @@ async def tell( *, host: str = "localhost", port: int = 783, - socket_path: Optional[str] = None, + socket_path: Optional[Path] = None, timeout: Optional[Timeout] = None, - verify: Any = None, + verify: Optional[Union[bool, Path, ssl.SSLContext]] = None, + cert: Union[ + Path, + Tuple[Path, Optional[Path]], + Tuple[Path, Optional[Path], Optional[str]], + None, + ] = None, user: Optional[str] = None, compress: bool = False, **kwargs, @@ -683,14 +877,16 @@ async def tell( ) context_logger.info("Sending TELL request") - client = Client() - ssl_context = client.ssl_context_factory(verify) - connection = client.connection_factory( - host, port, socket_path, timeout, ssl_context + client_builder = FrontendClientBuilder() + client = ( + client_builder.with_connection(host, port, socket_path) + .add_verify(verify) + .add_client_cert(cert) + .set_timeout(timeout) + .build() ) - parser = client.parser_factory() try: - response = await client.request(req, connection, parser) + response = await client.request(req) except Exception: context_logger.exception("Exception when calling tell function") raise diff --git a/poetry.lock b/poetry.lock index c38b2028..2e3e4550 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -16,7 +15,6 @@ files = [ name = "apeye" version = "1.3.0" description = "Handy tools for working with URLs and APIs." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -38,7 +36,6 @@ limiter = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] name = "apeye-core" version = "1.1.2" description = "Core (offline) functionality for the apeye library." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -54,7 +51,6 @@ idna = ">=2.5" name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -73,7 +69,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "autodocsumm" version = "0.2.11" description = "Extended sphinx autodoc including automatic autosummaries" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -88,7 +83,6 @@ Sphinx = ">=2.2,<8.0" name = "babel" version = "2.12.1" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -103,7 +97,6 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -122,7 +115,6 @@ lxml = ["lxml"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -172,7 +164,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cachecontrol" version = "0.12.11" description = "httplib2 caching for requests" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -193,7 +184,6 @@ redis = ["redis (>=2.10.5)"] name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -205,7 +195,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "dev" optional = false python-versions = "*" files = [ @@ -282,7 +271,6 @@ pycparser = "*" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -294,7 +282,6 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -379,7 +366,6 @@ files = [ name = "cli-ui" version = "0.17.2" description = "Build Nice User Interfaces In The Terminal" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -396,7 +382,6 @@ unidecode = ">=1.0.23,<2.0.0" name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -411,7 +396,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -423,7 +407,6 @@ files = [ name = "contextlib2" version = "21.6.0" description = "Backports and enhancements for the contextlib module" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -435,7 +418,6 @@ files = [ name = "coverage" version = "7.2.6" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -502,7 +484,6 @@ toml = ["tomli"] name = "cryptography" version = "40.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -544,7 +525,6 @@ tox = ["tox"] name = "cssutils" version = "2.6.0" description = "A CSS Cascading Style Sheets library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -560,7 +540,6 @@ testing = ["cssselect", "flake8 (<5)", "importlib-resources", "jaraco.test (>=5. name = "dict2css" version = "0.3.0" description = "A ฮผ-library for constructing cascading style sheets from Python dictionaries." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -576,7 +555,6 @@ domdf-python-tools = ">=2.2.0" name = "distlib" version = "0.3.6" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -588,7 +566,6 @@ files = [ name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "dev" optional = false python-versions = "*" files = [ @@ -599,7 +576,6 @@ files = [ name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -611,7 +587,6 @@ files = [ name = "domdf-python-tools" version = "3.6.1" description = "Helpful functions for Pythonโ€‚๐Ÿโ€‚๐Ÿ› ๏ธ" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -632,7 +607,6 @@ dates = ["pytz (>=2019.1)"] name = "dulwich" version = "0.21.5" description = "Python Git Library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -707,7 +681,6 @@ pgp = ["gpg"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -722,7 +695,6 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.12.0" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -738,7 +710,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "p name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -760,7 +731,6 @@ lxml = ["lxml"] name = "identify" version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -775,7 +745,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -787,7 +756,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -799,7 +767,6 @@ files = [ name = "importlib-metadata" version = "6.6.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -819,7 +786,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -831,7 +797,6 @@ files = [ name = "interrogate" version = "1.5.0" description = "Interrogate a codebase for docstring coverage." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -857,7 +822,6 @@ tests = ["pytest", "pytest-cov", "pytest-mock"] name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -875,7 +839,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -893,7 +856,6 @@ i18n = ["Babel (>=2.7)"] name = "lockfile" version = "0.12.2" description = "Platform-independent file locking module" -category = "dev" optional = false python-versions = "*" files = [ @@ -905,7 +867,6 @@ files = [ name = "loguru" version = "0.7.0" description = "Python logging made (stupidly) simple" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -924,7 +885,6 @@ dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegu name = "markupsafe" version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -984,7 +944,6 @@ files = [ name = "mock" version = "5.0.2" description = "Rolling backport of unittest.mock for all Pythons" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1001,7 +960,6 @@ test = ["pytest", "pytest-cov"] name = "msgpack" version = "1.0.5" description = "MessagePack serializer" -category = "dev" optional = false python-versions = "*" files = [ @@ -1074,7 +1032,6 @@ files = [ name = "mypy" version = "1.3.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1121,7 +1078,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1133,7 +1089,6 @@ files = [ name = "natsort" version = "8.3.1" description = "Simple yet flexible natural sorting in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1149,7 +1104,6 @@ icu = ["PyICU (>=1.0.0)"] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1164,7 +1118,6 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1176,7 +1129,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1188,7 +1140,6 @@ files = [ name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" -category = "dev" optional = false python-versions = ">=2.6" files = [ @@ -1200,7 +1151,6 @@ files = [ name = "platformdirs" version = "3.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1216,7 +1166,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1232,7 +1181,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.3.2" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1251,7 +1199,6 @@ virtualenv = ">=20.10.0" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1263,7 +1210,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1275,7 +1221,6 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1290,7 +1235,6 @@ plugins = ["importlib-metadata"] name = "pytest" version = "7.3.1" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1313,7 +1257,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.0" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1332,7 +1275,6 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1351,7 +1293,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1369,7 +1310,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "dev" optional = false python-versions = "*" files = [ @@ -1381,7 +1321,6 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1431,7 +1370,6 @@ files = [ name = "reno" version = "4.0.0" description = "RElease NOtes manager" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1453,7 +1391,6 @@ test = ["coverage (>=4.0,!=4.4)", "openstackdocstheme (>=2.2.1)", "python-subuni name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1475,7 +1412,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "ruamel-yaml" version = "0.17.28" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -category = "dev" optional = false python-versions = ">=3" files = [ @@ -1494,7 +1430,6 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] name = "ruamel-yaml-clib" version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1505,8 +1440,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -1541,7 +1475,6 @@ files = [ name = "schema" version = "0.7.5" description = "Simple data validation library" -category = "dev" optional = false python-versions = "*" files = [ @@ -1556,7 +1489,6 @@ contextlib2 = ">=0.5.5" name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1573,7 +1505,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1585,7 +1516,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -1597,7 +1527,6 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1609,7 +1538,6 @@ files = [ name = "sphinx" version = "6.2.1" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1645,7 +1573,6 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] name = "sphinx-autodoc-typehints" version = "1.23.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1665,7 +1592,6 @@ type-comment = ["typed-ast (>=1.5.4)"] name = "sphinx-jinja2-compat" version = "0.2.0" description = "Patches Jinja2 v3 to restore compatibility with earlier Sphinx versions." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1681,7 +1607,6 @@ markupsafe = ">=1" name = "sphinx-prompt" version = "1.5.0" description = "Sphinx directive to add unselectable prompt" -category = "dev" optional = false python-versions = "*" files = [ @@ -1696,7 +1621,6 @@ Sphinx = "*" name = "sphinx-tabs" version = "3.4.1" description = "Tabbed views for Sphinx" -category = "dev" optional = false python-versions = "~=3.7" files = [ @@ -1717,7 +1641,6 @@ testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "py name = "sphinx-toolbox" version = "3.4.0" description = "Box of handy tools for Sphinx ๐Ÿงฐ ๐Ÿ“”" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1752,7 +1675,6 @@ testing = ["coincidence (>=0.4.3)", "pygments (>=2.7.4,<=2.13.0)"] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1768,7 +1690,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1784,7 +1705,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1800,7 +1720,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1815,7 +1734,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1831,7 +1749,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1847,7 +1764,6 @@ test = ["pytest"] name = "tabulate" version = "0.8.10" description = "Pretty-print tabular data" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1862,7 +1778,6 @@ widechars = ["wcwidth"] name = "tbump" version = "6.10.0" description = "Bump software releases" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1880,7 +1795,6 @@ tomlkit = ">=0.11,<0.12" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1892,7 +1806,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1904,7 +1817,6 @@ files = [ name = "tomlkit" version = "0.11.8" description = "Style preserving TOML library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1916,7 +1828,6 @@ files = [ name = "trustme" version = "0.9.0" description = "#1 quality TLS certs while you wait, for the discerning tester" -category = "dev" optional = false python-versions = "*" files = [ @@ -1932,7 +1843,6 @@ idna = "*" name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1954,7 +1864,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "typing-extensions" version = "4.6.3" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1966,7 +1875,6 @@ files = [ name = "unidecode" version = "1.3.6" description = "ASCII transliterations of Unicode text" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1978,7 +1886,6 @@ files = [ name = "urllib3" version = "1.26.16" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1995,7 +1902,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "virtualenv" version = "20.23.0" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2016,7 +1922,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "dev" optional = false python-versions = "*" files = [ @@ -2028,7 +1933,6 @@ files = [ name = "win32-setctime" version = "1.1.0" description = "A small Python utility to set file creation time on Windows" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2043,7 +1947,6 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" files = [ diff --git a/pyproject.toml b/pyproject.toml index 5b498701..4a2122aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,12 +68,16 @@ fail_under = 95.0 + + [tool.poetry.group.workaround.dependencies] urllib3 = "<2" + + [tool.poetry.group.docs.dependencies] sphinx = "^6.2" sphinx-toolbox = "^3.4" @@ -82,6 +86,8 @@ reno = "^4.0" + + [tool.poetry.group.quality.dependencies] mypy = "^1.2" black = "^23" @@ -107,6 +113,8 @@ profile = "black" src_paths = ["aiospamc", "tests"] + + [tool.poetry.group.mgmt.dependencies] tbump = "^6.10" diff --git a/releasenotes/notes/394-ssl-option-wrong-type-a4320c34bfd4ddaa.yaml b/releasenotes/notes/394-ssl-option-wrong-type-a4320c34bfd4ddaa.yaml new file mode 100644 index 00000000..adc6760d --- /dev/null +++ b/releasenotes/notes/394-ssl-option-wrong-type-a4320c34bfd4ddaa.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Changed `--ssl` CLI option so it accepts a boolean instead of an optional boolean. :github:issue:`394` diff --git a/releasenotes/notes/404-add-client-certificate-24076b39e96cd311.yaml b/releasenotes/notes/404-add-client-certificate-24076b39e96cd311.yaml new file mode 100644 index 00000000..9145b15d --- /dev/null +++ b/releasenotes/notes/404-add-client-certificate-24076b39e96cd311.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adding support for using client certificates. :github:issue:`404` +fixes: + - | + CLI `--ssl` option type changed from optional boolean to boolean. :github:issue:`394` diff --git a/tests/conftest.py b/tests/conftest.py index a6023be1..efce60b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,19 @@ +import asyncio import datetime +import ssl import sys +from asyncio import StreamReader, StreamWriter +from dataclasses import dataclass, field from pathlib import Path from shutil import which from socket import gethostbyname from subprocess import PIPE, STDOUT, Popen, TimeoutExpired +from typing import Any, Optional import pytest import trustme from pytest_mock import MockerFixture -from aiospamc.client import Client from aiospamc.header_values import ContentLengthValue from aiospamc.requests import Request @@ -283,82 +287,161 @@ def unix_socket(tmp_path_factory): return str(tmp_path_factory.mktemp("sockets") / "spamd.sock") -@pytest.fixture -def mock_client(mocker: MockerFixture, response_ok): - connection_factory = mocker.Mock() - connection_factory.return_value.request = mocker.AsyncMock(return_value=response_ok) +@pytest.fixture(scope="session") +def ca(): + yield trustme.CA() - mock_connection_factory = mocker.patch.object( - Client, "default_connection_factory", connection_factory - ) - return mock_connection_factory +@pytest.fixture(scope="session") +def server_cert(ca, hostname, ip_address): + yield ca.issue_cert(hostname, ip_address) -@pytest.fixture -def mock_client_response(mock_client): - def inner(response): - mock_client.return_value.request.return_value = response - return mock_client - - return inner +@pytest.fixture(scope="session") +def ca_cert_path(ca, tmp_path_factory: pytest.TempdirFactory): + tmp_path = tmp_path_factory.mktemp("ca_certs") + cert_file = tmp_path / "ca_cert.pem" + ca.cert_pem.write_to_path(cert_file) + yield cert_file -@pytest.fixture -def mock_client_raises(mock_client): - def inner(side_effect): - mock_client.return_value.request.side_effect = side_effect - return mock_client - return inner +@pytest.fixture(scope="session") +def server_cert_and_key(server_cert, tmp_path_factory: pytest.TempdirFactory): + tmp_path = tmp_path_factory.mktemp("server_certs") + cert_file = tmp_path / "server.cert" + key_file = tmp_path / "server.key" + cert_file.write_bytes( + b"".join([blob.bytes() for blob in server_cert.cert_chain_pems]) + ) + server_cert.private_key_pem.write_to_path(key_file) -# Integration fixtures + yield cert_file, key_file @pytest.fixture(scope="session") -def create_certificate(tmp_path_factory, hostname, ip_address): - certs_tmp_path = tmp_path_factory.mktemp("localhost_certs") - ca_path = certs_tmp_path / "ca.pem" - cert_path = certs_tmp_path / "cert.pem" +def client_cert_and_key( + ca, hostname, ip_address, tmp_path_factory: pytest.TempdirFactory +): + tmp_path = tmp_path_factory.mktemp("client_certs") + cert_file = tmp_path / "client.cert" + key_file = tmp_path / "client.key" + cert_key_file = tmp_path / "client_cert_key.pem" + + cert: trustme.LeafCert = ca.issue_cert(hostname, ip_address) - ca = trustme.CA() - cert = ca.issue_cert(hostname, ip_address) + cert.private_key_and_cert_chain_pem.write_to_path(cert_key_file) + cert_file.write_bytes(b"".join([blob.bytes() for blob in cert.cert_chain_pems])) + cert.private_key_pem.write_to_path(key_file) + + yield cert_file, key_file, cert_key_file + + +@pytest.fixture(scope="session") +def server_cert_path(server_cert_and_key): + yield server_cert_and_key[0] - ca.cert_pem.write_to_path(ca_path) - cert.private_key_and_cert_chain_pem.write_to_path(cert_path) - yield ca_path, cert_path +@pytest.fixture(scope="session") +def server_key_path(server_cert_and_key): + yield server_cert_and_key[1] @pytest.fixture(scope="session") -def certificate_authority(create_certificate): - yield create_certificate[0] +def client_cert_and_key_path(client_cert_and_key): + yield client_cert_and_key[2] @pytest.fixture(scope="session") -def certificate(create_certificate): - yield create_certificate[1] +def client_cert_path(client_cert_and_key): + yield client_cert_and_key[0] @pytest.fixture(scope="session") -def spamd(ip_address, tcp_port, ssl_port, unix_socket, certificate, request): - # Configure options - options = [ - # f"--syslog={str(log_file)}", - "--local", - "--allow-tell", - f"--listen={ip_address}:{tcp_port}", - f"--listen=ssl:{ip_address}:{ssl_port}", - "--server-key", - f"{certificate}", - "--server-cert", - f"{certificate}", - ] +def client_key_path(client_cert_and_key): + yield client_cert_and_key[1] + + +@dataclass +class ServerResponse: + response: bytes = b"" + + +def fake_server(resp: ServerResponse): + buffer_size = 1024 + + async def inner(reader: StreamReader, writer: StreamWriter): + data = b"" + while len(chunk := await reader.read(buffer_size)) == buffer_size: + data += chunk + writer.write(resp.response) + if writer.can_write_eof(): + writer.write_eof() + await writer.drain() + writer.close() + await writer.wait_closed() + + return inner + + +@pytest.fixture +async def fake_tcp_server(unused_tcp_port, response_ok): + response = ServerResponse(response_ok) + server = await asyncio.start_server( + fake_server(response), "localhost", unused_tcp_port + ) + yield response, "localhost", unused_tcp_port + server.close() + + +@pytest.fixture +async def fake_tcp_ssl_server(unused_tcp_port, response_ok, server_cert): + response = ServerResponse(response_ok) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + server_cert.configure_cert(context) + server = await asyncio.start_server( + fake_server(response), "localhost", unused_tcp_port, ssl=context + ) + yield response, "localhost", unused_tcp_port + server.close() + + +@pytest.fixture +async def fake_tcp_ssl_client(unused_tcp_port, response_ok, ca, server_cert): + response = ServerResponse(response_ok) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.verify_mode = ssl.CERT_REQUIRED + server_cert.configure_cert(context) + ca.configure_trust(context) + server = await asyncio.start_server( + fake_server(response), "localhost", unused_tcp_port, ssl=context + ) + yield response, "localhost", unused_tcp_port + server.close() + + +@pytest.fixture +def mock_reader_writer(mocker: MockerFixture): + mock_reader = mocker.MagicMock() + mock_reader.read = mocker.AsyncMock() + mock_writer = mocker.MagicMock() + mock_writer.drain = mocker.AsyncMock() + mock_writer.write = mocker.MagicMock() + + mocker.patch("asyncio.open_connection", return_value=(mock_reader, mock_writer)) if sys.platform != "win32": - options += [f"--socketpath={unix_socket}"] + mocker.patch( + "asyncio.open_unix_connection", return_value=(mock_reader, mock_writer) + ) + + yield mock_reader, mock_writer + + +# Integration fixtures + - # Spawn spamd +def spawn_spamd(options, timeout): spamd_exe = Path(which("spamd")) process = Popen( [spamd_exe, *options], @@ -368,10 +451,7 @@ def spamd(ip_address, tcp_port, ssl_port, unix_socket, certificate, request): universal_newlines=True, ) - # Check the log to see if spamd is running - timeout = datetime.datetime.utcnow() + datetime.timedelta( - seconds=request.config.getoption("--spamd-process-timeout") - ) + timeout = datetime.datetime.utcnow() + datetime.timedelta(seconds=timeout) running = False spamd_start = "info: spamd: server started on" @@ -387,12 +467,95 @@ def spamd(ip_address, tcp_port, ssl_port, unix_socket, certificate, request): if not running: raise ChildProcessError - yield + return process - # Stop spamd + +def shutdown_spamd(process): process.terminate() try: process.wait(timeout=5) except TimeoutExpired: process.kill() process.wait(timeout=5) + + +@pytest.fixture(scope="session") +def spamd_timeout(request): + yield request.config.getoption("--spamd-process-timeout") + + +@pytest.fixture(scope="session") +def spamd_common_options(): + yield ["--local", "--allow-tell"] + + +@pytest.fixture(scope="session") +def spamd_tcp(spamd_common_options, unused_tcp_port_factory, spamd_timeout): + port = unused_tcp_port_factory() + process = spawn_spamd( + spamd_common_options + [f"--listen=localhost:{port}"], spamd_timeout + ) + yield "localhost", port + shutdown_spamd(process) + + +@pytest.fixture(scope="session") +def spamd_ssl( + spamd_common_options, + unused_tcp_port_factory, + server_cert_path, + server_key_path, + spamd_timeout, +): + port = unused_tcp_port_factory() + process = spawn_spamd( + spamd_common_options + + [ + f"--listen=ssl:localhost:{port}", + "--server-cert", + f"{server_cert_path}", + "--server-key", + f"{server_key_path}", + ], + spamd_timeout, + ) + yield "localhost", port + shutdown_spamd(process) + + +@pytest.fixture(scope="session") +def spamd_ssl_client( + spamd_common_options, + unused_tcp_port_factory, + server_cert_path, + server_key_path, + ca_cert_path, + spamd_timeout, +): + port = unused_tcp_port_factory() + process = spawn_spamd( + spamd_common_options + + [ + f"--listen=ssl:localhost:{port}", + "--server-cert", + f"{server_cert_path}", + "--server-key", + f"{server_key_path}", + "--ssl-ca-file", + f"{ca_cert_path}", + "--ssl-verify", + ], + spamd_timeout, + ) + yield "localhost", port + shutdown_spamd(process) + + +@pytest.fixture(scope="session") +def spamd_unix(spamd_common_options, tmp_path_factory, spamd_timeout): + unix_socket = tmp_path_factory.mktemp("spamd") / "spamd.sock" + process = spawn_spamd( + spamd_common_options + [f"--socketpath={unix_socket}"], spamd_timeout + ) + yield unix_socket + shutdown_spamd(process) diff --git a/tests/spamd-image/Dockerfile b/tests/spamd-image/Dockerfile new file mode 100644 index 00000000..c2bc7461 --- /dev/null +++ b/tests/spamd-image/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine:latest + +RUN apk update && \ + apk add spamassassin && \ + sa-update + +EXPOSE 783 + +CMD ["spamd", "--ip-address", "--allowed-ips=0.0.0.0/0", "--allow-tell"] diff --git a/tests/test_cli.py b/tests/test_cli.py index e06dfcbd..8605e0c1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,6 @@ import asyncio import json +from pathlib import Path from ssl import SSLError import pytest @@ -9,23 +10,25 @@ import aiospamc from aiospamc.cli import ( + BAD_RESPONSE, CONNECTION_ERROR, IS_SPAM, NOT_SPAM, - PARSE_ERROR, PING_SUCCESS, REPORT_FAILED, REVOKE_FAILED, SUCCESS, TIMEOUT_ERROR, UNEXPECTED_ERROR, + CliClientBuilder, CommandRunner, Output, app, read_message, ) from aiospamc.client import Client -from aiospamc.exceptions import AIOSpamcConnectionFailed, ParseError +from aiospamc.connections import ConnectionManagerBuilder, Timeout +from aiospamc.exceptions import AIOSpamcConnectionFailed from aiospamc.incremental_parser import ResponseParser from aiospamc.requests import Request from aiospamc.responses import Response @@ -39,9 +42,78 @@ def gtube(spam, tmp_path): return message -def test_cli_runner_init_defaults(): +def test_cli_builder_exception_when_not_defined(): + with pytest.raises(ValueError): + CliClientBuilder().build() + + +def test_cli_builder_with_tcp_connection(): + c = CliClientBuilder().with_connection("localhost", 1783).build() + + assert "localhost" == c.connection_manager.host + assert 1783 == c.connection_manager.port + + +def test_cli_builder_with_unix_connection(): + c = CliClientBuilder().with_connection(socket_path="test").build() + + assert "test" == c.connection_manager.path + + +def test_cli_builder_sets_timeout(): + t = Timeout() + c = CliClientBuilder().with_connection().set_timeout(t).build() + + assert t is c.connection_manager.timeout + + +@pytest.mark.parametrize("test_input", [True, False]) +def test_cli_builder_add_verify(test_input): + c = CliClientBuilder().with_connection().add_verify(test_input).build() + + assert hasattr(c.connection_manager, "ssl_context") + + +def test_cli_builder_add_ca_cert_none(): + c = CliClientBuilder().with_connection().add_ca_cert(None).build() + + assert hasattr(c.connection_manager, "ssl_context") + + +def test_cli_builder_add_ca_cert_file(server_cert_path): + c = CliClientBuilder().with_connection().add_ca_cert(server_cert_path).build() + + assert hasattr(c.connection_manager, "ssl_context") + + +def test_cli_builder_add_ca_cert_dir(tmp_path): + c = CliClientBuilder().with_connection().add_ca_cert(tmp_path).build() + + assert hasattr(c.connection_manager, "ssl_context") + + +def test_cli_builder_add_ca_cert_not_found(): + with pytest.raises(FileNotFoundError): + CliClientBuilder().with_connection().add_ca_cert(Path("doesnt_exist")).build() + + +def test_cli_builder_add_ca_client(client_cert_path, client_key_path): + c = ( + CliClientBuilder() + .with_connection() + .add_client_cert(client_cert_path, client_key_path, "password") + .build() + ) + + assert hasattr(c.connection_manager, "ssl_context") + + +def test_cli_runner_init_defaults(fake_tcp_server): + _, host, port = fake_tcp_server request = Request("PING") - c = CommandRunner(request) + c = CommandRunner( + Client(ConnectionManagerBuilder().with_tcp(host, port).build()), request + ) assert request == c.request assert None is c.response @@ -50,19 +122,23 @@ def test_cli_runner_init_defaults(): assert SUCCESS == c.exit_code -async def test_cli_runner_run_success(mock_client_response, response_pong, ip_address): - mock_client_response(response_pong) +async def test_cli_runner_run_success(fake_tcp_server, response_pong): + resp, host, port = fake_tcp_server + resp.response = response_pong expected = Response(**ResponseParser().parse(response_pong)) request = Request("PING") - c = CommandRunner(request) - result = await c.run(ip_address) + c = CommandRunner( + Client(ConnectionManagerBuilder().with_tcp(host, port).build()), request + ) + result = await c.run() assert expected == result assert expected == c.response -def test_cli_runner_to_json(): +def test_cli_runner_to_json(fake_tcp_server): + _, host, port = fake_tcp_server request = Request("PING") expected = { "request": request.to_json(), @@ -70,18 +146,24 @@ def test_cli_runner_to_json(): "exit_code": SUCCESS, } - c = CommandRunner(request) + c = CommandRunner( + Client(ConnectionManagerBuilder().with_tcp(host, port).build()), request + ) result = c.to_json() assert json.dumps(expected, indent=4) == result -def test_ping_json(mocker, mock_client): +def test_ping_json(mocker, fake_tcp_server, response_pong): + resp, host, port = fake_tcp_server + resp.response = response_pong request_spy = mocker.spy(Client, "request") runner = CliRunner() - result = runner.invoke(app, ["ping", "--out", "json"]) + result = runner.invoke( + app, ["ping", "--host", host, "--port", port, "--out", "json"] + ) expected = { - "request": request_spy.call_args.args[0].to_json(), + "request": request_spy.call_args.args[1].to_json(), "response": request_spy.spy_return.to_json(), "exit_code": 0, } @@ -96,12 +178,15 @@ def test_ping_json(mocker, mock_client): ["learn", "--message-class", "spam"], ], ) -def test_command_with_message_json(mocker, mock_client, gtube, args): +def test_command_with_message_json(mocker, fake_tcp_server, gtube, args): + _, host, port = fake_tcp_server request_spy = mocker.spy(Client, "request") runner = CliRunner() - result = runner.invoke(app, args + [str(gtube), "--out", "json"]) + result = runner.invoke( + app, args + [str(gtube), "--host", host, "--port", port, "--out", "json"] + ) expected = { - "request": request_spy.call_args.args[0].to_json(), + "request": request_spy.call_args.args[1].to_json(), "response": request_spy.spy_return.to_json(), "exit_code": 0, } @@ -109,15 +194,16 @@ def test_command_with_message_json(mocker, mock_client, gtube, args): assert f"{json.dumps(expected, indent=4)}\n" == result.stdout -def test_check_json( - mocker: MockerFixture, mock_client_response, response_not_spam, gtube -): +def test_check_json(mocker: MockerFixture, fake_tcp_server, response_not_spam, gtube): + resp, host, port = fake_tcp_server request_spy = mocker.spy(Client, "request") - mock_client_response(response_not_spam) + resp.response = response_not_spam runner = CliRunner() - result = runner.invoke(app, ["check", str(gtube), "--out", "json"]) + result = runner.invoke( + app, ["check", str(gtube), "--host", host, "--port", port, "--out", "json"] + ) expected = { - "request": request_spy.call_args.args[0].to_json(), + "request": request_spy.call_args.args[1].to_json(), "response": request_spy.spy_return.to_json(), "exit_code": 0, } @@ -125,13 +211,16 @@ def test_check_json( assert f"{json.dumps(expected, indent=4)}\n" == result.stdout -def test_report_json(mocker, mock_client_response, response_reported, gtube): - mock_client_response(response_reported) +def test_report_json(mocker, fake_tcp_server, response_reported, gtube): + resp, host, port = fake_tcp_server + resp.response = response_reported request_spy = mocker.spy(Client, "request") runner = CliRunner() - result = runner.invoke(app, ["report", str(gtube), "--out", "json"]) + result = runner.invoke( + app, ["report", str(gtube), "--host", host, "--port", port, "--out", "json"] + ) expected = { - "request": request_spy.call_args.args[0].to_json(), + "request": request_spy.call_args.args[1].to_json(), "response": request_spy.spy_return.to_json(), "exit_code": 0, } @@ -139,13 +228,16 @@ def test_report_json(mocker, mock_client_response, response_reported, gtube): assert f"{json.dumps(expected, indent=4)}\n" == result.stdout -def test_revoke_json(mocker, mock_client_response, response_revoked, gtube): - mock_client_response(response_revoked) +def test_revoke_json(mocker, fake_tcp_server, response_revoked, gtube): + resp, host, port = fake_tcp_server + resp.response = response_revoked request_spy = mocker.spy(Client, "request") runner = CliRunner() - result = runner.invoke(app, ["revoke", str(gtube), "--out", "json"]) + result = runner.invoke( + app, ["revoke", str(gtube), "--host", host, "--port", port, "--out", "json"] + ) expected = { - "request": request_spy.call_args.args[0].to_json(), + "request": request_spy.call_args.args[1].to_json(), "response": request_spy.spy_return.to_json(), "exit_code": 0, } @@ -153,10 +245,11 @@ def test_revoke_json(mocker, mock_client_response, response_revoked, gtube): assert f"{json.dumps(expected, indent=4)}\n" == result.stdout -def test_command_without_message_response_exception(mock_client_response, ex_usage): +def test_command_without_message_response_exception(fake_tcp_server, ex_usage): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(ex_usage) - result = runner.invoke(app, ["ping"]) + resp.response = ex_usage + result = runner.invoke(app, ["ping", "--host", host, "--port", port]) assert 64 == result.exit_code assert "Response error from server: EX_USAGE\n" == result.stdout @@ -173,22 +266,24 @@ def test_command_without_message_response_exception(mock_client_response, ex_usa ], ) def test_command_with_message_response_exception( - mock_client_response, ex_usage, gtube, args + fake_tcp_server, ex_usage, gtube, args ): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(ex_usage) - result = runner.invoke(app, args + [str(gtube)]) + resp.response = ex_usage + result = runner.invoke(app, args + [str(gtube), "--host", host, "--port", port]) assert 64 == result.exit_code assert "Response error from server: EX_USAGE\n" == result.stdout -def test_command_without_message_parser_exception(mock_client_raises): +def test_command_without_message_parser_exception(fake_tcp_server, response_invalid): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_raises(ParseError()) - result = runner.invoke(app, ["ping"]) + resp.response = response_invalid + result = runner.invoke(app, ["ping", "--host", host, "--port", port]) - assert PARSE_ERROR == result.exit_code + assert BAD_RESPONSE == result.exit_code assert "Error parsing response\n" == result.stdout @@ -202,19 +297,25 @@ def test_command_without_message_parser_exception(mock_client_raises): ["revoke"], ], ) -def test_command_with_message_parser_exception(mock_client_raises, gtube, args): +def test_command_with_message_parser_exception( + fake_tcp_server, gtube, args, response_invalid +): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_raises(ParseError()) - result = runner.invoke(app, args + [str(gtube)]) + resp.response = response_invalid + result = runner.invoke(app, args + [str(gtube), "--host", host, "--port", port]) - assert PARSE_ERROR == result.exit_code + assert BAD_RESPONSE == result.exit_code assert "Error parsing response\n" == result.stdout -def test_command_without_message_timeout_exception(mock_client_raises): +def test_command_without_message_timeout_exception(fake_tcp_server): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_raises(asyncio.TimeoutError()) - result = runner.invoke(app, ["ping"]) + resp.sleep = 10 + result = runner.invoke( + app, ["ping", "--timeout", 0, "--host", host, "--port", port] + ) assert TIMEOUT_ERROR == result.exit_code assert "Error: timeout\n" == result.stdout @@ -230,9 +331,11 @@ def test_command_without_message_timeout_exception(mock_client_raises): ["revoke"], ], ) -def test_command_with_message_timeout_exception(mock_client_raises, gtube, args): +def test_command_with_message_timeout_exception(mock_reader_writer, gtube, args): + reader, _ = mock_reader_writer + reader.read.side_effect = asyncio.TimeoutError() + runner = CliRunner() - mock_client_raises(asyncio.TimeoutError()) result = runner.invoke(app, args + [str(gtube)]) assert TIMEOUT_ERROR == result.exit_code @@ -242,9 +345,11 @@ def test_command_with_message_timeout_exception(mock_client_raises, gtube, args) @pytest.mark.parametrize( "raises", [AIOSpamcConnectionFailed(), OSError(), ConnectionError(), SSLError()] ) -def test_command_without_message_connection_exception(mock_client_raises, raises): +def test_command_without_message_connection_exception(mock_reader_writer, raises): + reader, _ = mock_reader_writer + reader.read.side_effect = raises + runner = CliRunner() - mock_client_raises(raises) result = runner.invoke(app, ["ping"]) assert CONNECTION_ERROR == result.exit_code @@ -269,124 +374,484 @@ def test_command_without_message_connection_exception(mock_client_raises, raises ], ) def test_command_with_message_connection_exception( - mock_client_raises, raises, gtube, args + mock_reader_writer, raises, gtube, args ): + reader, _ = mock_reader_writer + reader.read.side_effect = raises + runner = CliRunner() - mock_client_raises(raises) result = runner.invoke(app, args + [str(gtube)]) assert CONNECTION_ERROR == result.exit_code assert "Error: Connection error\n" == result.stdout -def test_ping(mock_client_response, response_pong): +def test_ping(fake_tcp_server, response_pong): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_pong) - result = runner.invoke(app, ["ping"]) + resp.response = response_pong + result = runner.invoke(app, ["ping", "--host", host, "--port", port]) + + assert PING_SUCCESS == result.exit_code + assert "PONG\n" == result.stdout + + +def test_ping_server_ssl_ca(fake_tcp_ssl_server, response_pong, ca_cert_path): + resp, host, port = fake_tcp_ssl_server + resp.response = response_pong + runner = CliRunner() + result = runner.invoke( + app, + ["ping", "--host", host, "--port", port, "--ssl", "--ca-cert", ca_cert_path], + ) + + assert PING_SUCCESS == result.exit_code + assert "PONG\n" == result.stdout + + +def test_ping_server_ssl_client( + fake_tcp_ssl_server, response_pong, ca_cert_path, client_cert_path, client_key_path +): + resp, host, port = fake_tcp_ssl_server + resp.response = response_pong + runner = CliRunner() + result = runner.invoke( + app, + [ + "ping", + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + "--client-cert", + client_cert_path, + "--client-key", + client_key_path, + ], + ) assert PING_SUCCESS == result.exit_code assert "PONG\n" == result.stdout -def test_check_spam(mock_client_response, response_spam_header, gtube): +def test_check_spam(fake_tcp_server, response_spam_header, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_spam_header) - result = runner.invoke(app, ["check", str(gtube)]) + resp.response = response_spam_header + result = runner.invoke(app, ["check", str(gtube), "--host", host, "--port", port]) assert IS_SPAM == result.exit_code assert "1000.0/1.0\n" == result.stdout -def test_check_ham(mock_client_response, response_not_spam, gtube): +def test_check_ham(fake_tcp_server, response_not_spam, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_not_spam) - result = runner.invoke(app, ["check", str(gtube)]) + resp.response = response_not_spam + result = runner.invoke(app, ["check", str(gtube), "--host", host, "--port", port]) assert NOT_SPAM == result.exit_code assert "0.0/1.0\n" == result.stdout -def test_check_no_spam_header(mock_client_response, response_with_body, gtube): +def test_check_server_ssl_ca( + gtube, response_spam_header, fake_tcp_ssl_server, ca_cert_path +): + resp, host, port = fake_tcp_ssl_server + resp.response = response_spam_header + runner = CliRunner() + result = runner.invoke( + app, + [ + "check", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + ], + ) + + assert IS_SPAM == result.exit_code + assert "1000.0/1.0\n" == result.stdout + + +def test_check_server_ssl_client( + gtube, + response_spam_header, + fake_tcp_ssl_client, + ca_cert_path, + client_cert_path, + client_key_path, +): + resp, host, port = fake_tcp_ssl_client + resp.response = response_spam_header + runner = CliRunner() + result = runner.invoke( + app, + [ + "check", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + "--client-cert", + client_cert_path, + "--client-key", + client_key_path, + ], + ) + + assert IS_SPAM == result.exit_code + assert "1000.0/1.0\n" == result.stdout + + +def test_check_no_spam_header(fake_tcp_server, response_with_body, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_with_body) - result = runner.invoke(app, ["check", str(gtube)]) + resp.response = response_with_body + result = runner.invoke( + app, + [ + "check", + str(gtube), + "--host", + host, + "--port", + port, + ], + ) assert UNEXPECTED_ERROR == result.exit_code assert "Could not find 'Spam' header\n" == result.stdout -def test_learn_success(mock_client_response, response_learned, gtube): +def test_learn_success(fake_tcp_server, response_learned, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_learned) - result = runner.invoke(app, ["learn", str(gtube)]) + resp.response = response_learned + result = runner.invoke(app, ["learn", str(gtube), "--host", host, "--port", port]) assert SUCCESS == result.exit_code assert "Message successfully learned\n" == result.stdout -def test_learn_already_learned(mock_client_response, response_tell, gtube): +def test_learn_already_learned(fake_tcp_server, response_tell, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_tell) - result = runner.invoke(app, ["learn", str(gtube)]) + resp.response = response_tell + result = runner.invoke(app, ["learn", str(gtube), "--host", host, "--port", port]) assert SUCCESS == result.exit_code assert "Message was already learned\n" == result.stdout -def test_forget_success(mock_client_response, response_forgotten, gtube): +def test_learn_ssl_ca(fake_tcp_ssl_server, response_learned, gtube, ca_cert_path): + resp, host, port = fake_tcp_ssl_server + resp.response = response_learned runner = CliRunner() - mock_client_response(response_forgotten) - result = runner.invoke(app, ["forget", str(gtube)]) + result = runner.invoke( + app, + [ + "learn", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully learned\n" == result.stdout + + +def test_learn_ssl_client( + fake_tcp_ssl_client, + response_learned, + gtube, + ca_cert_path, + client_cert_path, + client_key_path, +): + resp, host, port = fake_tcp_ssl_client + resp.response = response_learned + runner = CliRunner() + result = runner.invoke( + app, + [ + "learn", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + "--client-cert", + client_cert_path, + "--client-key", + client_key_path, + "--timeout", + 30000, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully learned\n" == result.stdout + + +def test_forget_success(fake_tcp_server, response_forgotten, gtube): + resp, host, port = fake_tcp_server + runner = CliRunner() + resp.response = response_forgotten + result = runner.invoke(app, ["forget", str(gtube), "--host", host, "--port", port]) assert SUCCESS == result.exit_code assert "Message successfully forgotten\n" == result.stdout -def test_learn_already_forgotten(mock_client_response, response_tell, gtube): +def test_forget_ssl_ca(fake_tcp_ssl_server, response_forgotten, gtube, ca_cert_path): + resp, host, port = fake_tcp_ssl_server + resp.response = response_forgotten runner = CliRunner() - mock_client_response(response_tell) - result = runner.invoke(app, ["forget", str(gtube)]) + result = runner.invoke( + app, + [ + "forget", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully forgotten\n" == result.stdout + + +def test_forget_ssl_client( + fake_tcp_ssl_client, + response_forgotten, + gtube, + ca_cert_path, + client_cert_path, + client_key_path, +): + resp, host, port = fake_tcp_ssl_client + resp.response = response_forgotten + runner = CliRunner() + result = runner.invoke( + app, + [ + "forget", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + "--client-cert", + client_cert_path, + "--client-key", + client_key_path, + "--timeout", + 30000, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully forgotten\n" == result.stdout + + +def test_learn_already_forgotten(fake_tcp_server, response_tell, gtube): + resp, host, port = fake_tcp_server + runner = CliRunner() + resp.response = response_tell + result = runner.invoke(app, ["forget", str(gtube), "--host", host, "--port", port]) assert SUCCESS == result.exit_code assert "Message was already forgotten\n" == result.stdout -def test_report_success(mock_client_response, response_reported, gtube): +def test_report_success(fake_tcp_server, response_reported, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_reported) - result = runner.invoke(app, ["report", str(gtube)]) + resp.response = response_reported + result = runner.invoke(app, ["report", str(gtube), "--host", host, "--port", port]) assert SUCCESS == result.exit_code assert "Message successfully reported\n" == result.stdout -def test_report_failed(mock_client_response, response_tell, gtube): +def test_report_failed(fake_tcp_server, response_tell, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_tell) - result = runner.invoke(app, ["report", str(gtube)]) + resp.response = response_tell + result = runner.invoke(app, ["report", str(gtube), "--host", host, "--port", port]) assert REPORT_FAILED == result.exit_code assert "Unable to report message\n" == result.stdout -def test_revoke_success(mock_client_response, response_revoked, gtube): +def test_report_ssl_ca(fake_tcp_ssl_server, response_reported, gtube, ca_cert_path): + resp, host, port = fake_tcp_ssl_server + resp.response = response_reported runner = CliRunner() - mock_client_response(response_revoked) - result = runner.invoke(app, ["revoke", str(gtube)]) + result = runner.invoke( + app, + [ + "report", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully reported\n" == result.stdout + + +def test_report_ssl_client( + fake_tcp_ssl_client, + response_reported, + gtube, + ca_cert_path, + client_cert_path, + client_key_path, +): + resp, host, port = fake_tcp_ssl_client + resp.response = response_reported + runner = CliRunner() + result = runner.invoke( + app, + [ + "report", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + "--client-cert", + client_cert_path, + "--client-key", + client_key_path, + "--timeout", + 30000, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully reported\n" == result.stdout + + +def test_revoke_success(fake_tcp_server, response_revoked, gtube): + resp, host, port = fake_tcp_server + runner = CliRunner() + resp.response = response_revoked + result = runner.invoke(app, ["revoke", str(gtube), "--host", host, "--port", port]) assert SUCCESS == result.exit_code assert "Message successfully revoked\n" == result.stdout -def test_revoke_failed(mock_client_response, response_tell, gtube): +def test_revoke_failed(fake_tcp_server, response_tell, gtube): + resp, host, port = fake_tcp_server runner = CliRunner() - mock_client_response(response_tell) - result = runner.invoke(app, ["revoke", str(gtube)]) + resp.response = response_tell + result = runner.invoke(app, ["revoke", str(gtube), "--host", host, "--port", port]) assert REVOKE_FAILED == result.exit_code assert "Unable to revoke message\n" == result.stdout +def test_revoke_ssl_ca(fake_tcp_ssl_server, response_revoked, gtube, ca_cert_path): + resp, host, port = fake_tcp_ssl_server + resp.response = response_revoked + runner = CliRunner() + result = runner.invoke( + app, + [ + "revoke", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully revoked\n" == result.stdout + + +def test_revoke_ssl_client( + fake_tcp_ssl_client, + response_revoked, + gtube, + ca_cert_path, + client_cert_path, + client_key_path, +): + resp, host, port = fake_tcp_ssl_client + resp.response = response_revoked + runner = CliRunner() + result = runner.invoke( + app, + [ + "revoke", + str(gtube), + "--host", + host, + "--port", + port, + "--ssl", + "--ca-cert", + ca_cert_path, + "--client-cert", + client_cert_path, + "--client-key", + client_key_path, + "--timeout", + 30000, + ], + ) + + assert SUCCESS == result.exit_code + assert "Message successfully revoked\n" == result.stdout + + def test_version(): runner = CliRunner() result = runner.invoke(app, ["--version"]) diff --git a/tests/test_client.py b/tests/test_client.py index c378d95b..ae34080a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,10 @@ import pytest +from pytest_mock import MockerFixture from aiospamc.client import Client +from aiospamc.connections import ConnectionManagerBuilder +from aiospamc.exceptions import BadResponse +from aiospamc.requests import Request from aiospamc.responses import ( CantCreateException, ConfigException, @@ -23,207 +27,28 @@ ) -async def test_request_sent_to_connection(mock_client, mocker): - mock_req = mocker.MagicMock() - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - await client.request(mock_req, connection, parser) +async def test_successful_response(fake_tcp_server): + _, host, port = fake_tcp_server + c = Client(ConnectionManagerBuilder().with_tcp(host, port).build()) + response = await c.request(Request("PING")) - assert bytes(mock_req) == client.connection_factory().request.await_args[0][0] + assert isinstance(response, Response) -async def test_request_response_sent_to_parser(mock_client, mocker): - mock_req = mocker.MagicMock() - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - mocker.spy(parser, "parse") - await client.request(mock_req, connection, parser) +async def test_successful_parse_error(fake_tcp_server, response_invalid): + resp, host, port = fake_tcp_server + resp.response = response_invalid + c = Client(ConnectionManagerBuilder().with_tcp(host, port).build()) - response = connection.request.return_value - assert response == parser.parse.call_args[0][0] + with pytest.raises(BadResponse): + await c.request(Request("PING")) -async def test_request_returns_response(mock_client, mocker): - mock_req = mocker.MagicMock() - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - parse_spy = mocker.spy(parser, "parse") - result = await client.request(mock_req, connection, parser) - expected = Response(**parse_spy.spy_return) +async def test_raise_for_status_called(fake_tcp_server, mocker: MockerFixture): + raise_spy = mocker.spy(Response, "raise_for_status") + _, host, port = fake_tcp_server + c = Client(ConnectionManagerBuilder().with_tcp(host, port).build()) + response = await c.request(Request("PING")) - assert expected == result - - -async def test_request_raises_usage(mock_client_response, mocker, ex_usage): - mock_client_response(ex_usage) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(UsageException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_data_err(mock_client_response, mocker, ex_data_err): - mock_client_response(ex_data_err) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(DataErrorException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_no_input(mock_client_response, mocker, ex_no_input): - mock_client_response(ex_no_input) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(NoInputException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_no_user(mock_client_response, mocker, ex_no_user): - mock_client_response(ex_no_user) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(NoUserException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_no_host(mock_client_response, mocker, ex_no_host): - mock_client_response(ex_no_host) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(NoHostException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_unavailable(mock_client_response, mocker, ex_unavailable): - mock_client_response(ex_unavailable) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(UnavailableException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_software(mock_client_response, mocker, ex_software): - mock_client_response(ex_software) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(InternalSoftwareException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_os_error(mock_client_response, mocker, ex_os_err): - mock_client_response(ex_os_err) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(OSErrorException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_os_file(mock_client_response, mocker, ex_os_file): - mock_client_response(ex_os_file) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(OSFileException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_cant_create(mock_client_response, mocker, ex_cant_create): - mock_client_response(ex_cant_create) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(CantCreateException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_io_error(mock_client_response, mocker, ex_io_err): - mock_client_response(ex_io_err) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(IOErrorException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_temporary_failure( - mock_client_response, mocker, ex_temp_fail -): - mock_client_response(ex_temp_fail) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(TemporaryFailureException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_protocol(mock_client_response, mocker, ex_protocol): - mock_client_response(ex_protocol) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(ProtocolException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_no_permission(mock_client_response, mocker, ex_no_perm): - mock_client_response(ex_no_perm) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(NoPermissionException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_config(mock_client_response, mocker, ex_config): - mock_client_response(ex_config) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(ConfigException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_timeout(mock_client_response, mocker, ex_timeout): - mock_client_response(ex_timeout) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(ServerTimeoutException): - await client.request(mocker.MagicMock(), connection, parser) - - -async def test_request_raises_undefined(mock_client_response, mocker, ex_undefined): - mock_client_response(ex_undefined) - client = Client() - connection = client.connection_factory() - parser = client.parser_factory() - - with pytest.raises(ResponseException): - await client.request(mocker.MagicMock(), connection, parser) + assert isinstance(response, Response) + assert raise_spy.called diff --git a/tests/test_connections.py b/tests/test_connections.py index dadf3a0d..1e68a744 100644 --- a/tests/test_connections.py +++ b/tests/test_connections.py @@ -5,28 +5,19 @@ import certifi import pytest +from pytest_mock import MockerFixture from aiospamc.connections import ( ConnectionManager, + ConnectionManagerBuilder, + SSLContextBuilder, TcpConnectionManager, Timeout, UnixConnectionManager, - new_connection_manager, - new_ssl_context, ) from aiospamc.exceptions import AIOSpamcConnectionFailed, ClientTimeoutException -@pytest.fixture -def mock_open_connection(mocker): - reader, writer = mocker.AsyncMock(), mocker.AsyncMock() - mocker.patch( - "asyncio.open_connection", mocker.AsyncMock(return_value=(reader, writer)) - ) - - yield reader, writer - - @pytest.fixture def mock_open_connection_refused(mocker): mocker.patch("asyncio.open_connection", side_effect=ConnectionRefusedError()) @@ -119,7 +110,7 @@ async def sleep(): c = ConnectionManager("connection", timeout=Timeout(total=0)) c.open = mocker.AsyncMock(side_effect=sleep) - with pytest.raises(ClientTimeoutException): + with pytest.raises(asyncio.TimeoutError): await c.request(b"data") @@ -171,12 +162,12 @@ def test_tcp_connection_manager_init(mocker, hostname, tcp_port): assert mock_ssl_context is t.ssl_context -async def test_tcp_connection_manager_open(mock_open_connection, hostname, tcp_port): +async def test_tcp_connection_manager_open(mock_reader_writer, hostname, tcp_port): t = TcpConnectionManager(hostname, tcp_port) reader, writer = await t.open() - assert mock_open_connection[0] is reader - assert mock_open_connection[1] is writer + assert mock_reader_writer[0] is reader + assert mock_reader_writer[1] is writer async def test_tcp_connection_manager_open_refused( @@ -229,74 +220,125 @@ def test_unix_connection_manager_connection_string(unix_socket): assert unix_socket == u.connection_string -def test_ssl_context_from_none(): - result = new_ssl_context(None) +def test_connection_manager_builder_builds_unix(unix_socket): + timeout = Timeout() + b = ( + ConnectionManagerBuilder() + .with_unix_socket(unix_socket) + .set_timeout(timeout) + .build() + ) - assert result is None + assert isinstance(b, UnixConnectionManager) + assert unix_socket == b.path + assert timeout == b.timeout -def test_ssl_context_from_true(mocker): - s = mocker.spy(ssl, "create_default_context") - new_ssl_context(True) +def test_connection_manager_builder_builds_tcp(hostname, tcp_port): + timeout = Timeout() + b = ( + ConnectionManagerBuilder() + .with_tcp(hostname, tcp_port) + .set_timeout(timeout) + .build() + ) - assert s.call_args.kwargs["cafile"] == certifi.where() + assert isinstance(b, TcpConnectionManager) + assert hostname == b.host + assert tcp_port == b.port + assert timeout == b.timeout + assert None is b.ssl_context -def test_ssl_context_from_false(mocker): - mocker.spy(ssl, "create_default_context") - s = new_ssl_context(False) +def test_connection_manager_builder_builds_tcp_with_ssl(hostname, tcp_port, mocker): + ssl_context = mocker.Mock() + b = ( + ConnectionManagerBuilder() + .with_tcp(hostname, tcp_port) + .add_ssl_context(ssl_context) + .build() + ) - assert ssl.create_default_context.call_args.kwargs["cafile"] == certifi.where() - assert s.check_hostname is False - assert s.verify_mode == ssl.CERT_NONE + assert isinstance(b, TcpConnectionManager) + assert ssl_context == b.ssl_context -def test_ssl_context_from_dir(mocker, tmp_path): - mocker.spy(ssl, "create_default_context") - temp = Path(str(tmp_path)) - s = new_ssl_context(temp) +def test_ssl_context_builder_default(mocker: MockerFixture): + default_spy = mocker.spy(ssl, "create_default_context") + s = SSLContextBuilder().build() - assert ssl.create_default_context.call_args.kwargs["capath"] == str(temp) + assert isinstance(s, ssl.SSLContext) + assert True is default_spy.called -def test_ssl_context_from_file(mocker, certificate_authority): - mocker.spy(ssl, "create_default_context") - # file = tmp_path / "certs.pem" - # with open(str(file), "wb") as dest: - # with open(certifi.where(), "rb") as source: - # dest.writelines(source.readlines()) - s = new_ssl_context(certificate_authority) +def test_ssl_context_builder_existing_context(): + context = ssl.create_default_context() + s = SSLContextBuilder().with_context(context).build() - assert ssl.create_default_context.call_args.kwargs["cafile"] == str( - certificate_authority - ) + assert context is s -def test_ssl_context_file_not_found(tmp_path): - file = tmp_path / "nonexistent.pem" +def test_ssl_context_builder_dont_verify(): + s = SSLContextBuilder().dont_verify().build() - with pytest.raises(FileNotFoundError): - new_ssl_context(str(file)) + assert False is s.check_hostname + assert ssl.CERT_NONE is s.verify_mode -def test_new_connection_returns_unix_manager(unix_socket): - result = new_connection_manager(socket_path=unix_socket) +def test_ssl_context_builder_add_certifi(mocker: MockerFixture): + s = SSLContextBuilder() + certs_spy = mocker.spy(s._context, "load_verify_locations") + s.add_default_ca().build() - assert isinstance(result, UnixConnectionManager) + assert {"cafile": certifi.where()} == certs_spy.call_args.kwargs -def test_new_connection_returns_tcp_manager(hostname, tcp_port): - result = new_connection_manager(host=hostname, port=tcp_port) +def test_ssl_context_builder_add_cafile(mocker: MockerFixture, server_cert_path): + s = SSLContextBuilder() + certs_spy = mocker.spy(s._context, "load_verify_locations") + s.add_ca_file(server_cert_path).build() - assert isinstance(result, TcpConnectionManager) + assert {"cafile": server_cert_path} == certs_spy.call_args.kwargs -def test_new_connection_returns_tcp_manager_with_ssl(mocker, hostname, tcp_port): - result = new_connection_manager(host=hostname, port=tcp_port, context=mocker.Mock()) +def test_ssl_context_builder_add_cadir(mocker: MockerFixture, server_cert_path): + s = SSLContextBuilder() + certs_spy = mocker.spy(s._context, "load_verify_locations") + s.add_ca_dir(server_cert_path.parent).build() - assert isinstance(result, TcpConnectionManager) + assert {"capath": server_cert_path.parent} == certs_spy.call_args.kwargs -def test_new_connection_raises_on_missing_parameters(): - with pytest.raises(ValueError): - new_connection_manager() +def test_ssl_context_builder_add_ca_path_of_file( + mocker: MockerFixture, server_cert_path +): + s = SSLContextBuilder() + certs_spy = mocker.spy(s._context, "load_verify_locations") + s.add_ca(server_cert_path).build() + + assert {"cafile": server_cert_path} == certs_spy.call_args.kwargs + + +def test_ssl_context_builder_add_ca_path_of_dir( + mocker: MockerFixture, server_cert_path +): + s = SSLContextBuilder() + certs_spy = mocker.spy(s._context, "load_verify_locations") + s.add_ca(server_cert_path.parent).build() + + assert {"capath": server_cert_path.parent} == certs_spy.call_args.kwargs + + +def test_ssl_context_builder_add_ca_path_not_found(): + with pytest.raises(FileNotFoundError): + SSLContextBuilder().add_ca(Path("fake")).build() + + +def test_ssl_context_builder_add_client_cert( + mocker: MockerFixture, client_cert_path, client_key_path +): + builder = SSLContextBuilder() + certs_spy = mocker.spy(builder._context, "load_cert_chain") + s = builder.add_client(client_cert_path, client_key_path, "password").build() + + assert (client_cert_path, client_key_path, "password") == certs_spy.call_args.args diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 0f7655d0..94865151 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -1,8 +1,18 @@ +import ssl +from pathlib import Path + import pytest from aiospamc.client import Client +from aiospamc.connections import ( + ConnectionManager, + TcpConnectionManager, + Timeout, + UnixConnectionManager, +) from aiospamc.exceptions import BadResponse from aiospamc.frontend import ( + FrontendClientBuilder, check, headers, ping, @@ -35,6 +45,133 @@ ) +def test_frontend_builder_raises_without_connection(): + with pytest.raises(ValueError): + FrontendClientBuilder().build() + + +def test_frontend_builder_with_tcp_connection(): + f = FrontendClientBuilder().with_connection().build() + + assert isinstance(f, Client) + assert isinstance(f.connection_manager, TcpConnectionManager) + + +def test_frontend_builder_with_unix_connection(): + f = FrontendClientBuilder().with_connection(socket_path=Path("test")).build() + + assert isinstance(f, Client) + assert isinstance(f.connection_manager, UnixConnectionManager) + + +def test_frontend_builder_add_verify_none(): + f = FrontendClientBuilder().with_connection().add_verify().build() + + assert isinstance(f, Client) + assert None is f.connection_manager.ssl_context + + +@pytest.mark.parametrize("test_input", [True, False]) +def test_frontend_builder_add_verify_bool(test_input): + f = FrontendClientBuilder().with_connection().add_verify(test_input).build() + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_verify_context(): + context = ssl.create_default_context() + f = FrontendClientBuilder().with_connection().add_verify(context).build() + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_verify_path(ca_cert_path): + f = FrontendClientBuilder().with_connection().add_verify(ca_cert_path).build() + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_client_cert_none(): + f = FrontendClientBuilder().with_connection().add_client_cert(None).build() + + assert isinstance(f, Client) + assert None is f.connection_manager.ssl_context + + +def test_frontend_builder_add_client_cert_cert_arg(client_cert_and_key_path): + f = ( + FrontendClientBuilder() + .with_connection() + .add_client_cert(client_cert_and_key_path) + .build() + ) + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_client_cert_cert_arg_verify_added( + client_cert_and_key_path, +): + f = ( + FrontendClientBuilder() + .with_connection() + .add_verify(True) + .add_client_cert(client_cert_and_key_path) + .build() + ) + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_client_cert_and_key(client_cert_path, client_key_path): + f = ( + FrontendClientBuilder() + .with_connection() + .add_client_cert((client_cert_path, client_key_path)) + .build() + ) + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_client_cert_key_and_password( + client_cert_path, client_key_path +): + f = ( + FrontendClientBuilder() + .with_connection() + .add_client_cert((client_cert_path, client_key_path, "password")) + .build() + ) + + assert isinstance(f, Client) + assert None is not f.connection_manager.ssl_context + + +def test_frontend_builder_add_client_cert_typeerror(mocker): + with pytest.raises(TypeError): + FrontendClientBuilder().with_connection().add_client_cert(mocker.Mock()).build() + + +def test_frontend_build_set_timeout_none(): + f = FrontendClientBuilder().with_connection().set_timeout().build() + + assert isinstance(f, Client) + + +def test_frontend_build_set_timeout(): + timeout = Timeout() + f = FrontendClientBuilder().with_connection().set_timeout(timeout).build() + + assert timeout == f.connection_manager.timeout + + @pytest.mark.parametrize( "func,expected_verb", [ @@ -47,11 +184,12 @@ ], ) async def test_functions_with_default_parameters( - func, expected_verb, mock_client, spam, mocker + func, expected_verb, fake_tcp_server, spam, mocker ): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") - await func(spam) - req = req_spy.await_args[0][0] + await func(spam, host=host, port=port) + req = req_spy.await_args[0][1] assert expected_verb == req.verb assert "User" not in req.headers @@ -71,11 +209,12 @@ async def test_functions_with_default_parameters( ], ) async def test_functions_with_optional_parameters( - func, expected_verb, mock_client, spam, mocker + func, expected_verb, fake_tcp_server, spam, mocker ): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") - await func(spam, user="testuser", compress=True) - req = req_spy.await_args[0][0] + await func(spam, user="testuser", compress=True, host=host, port=port) + req = req_spy.await_args[0][1] assert expected_verb == req.verb assert "testuser" == req.headers["User"].name @@ -94,33 +233,96 @@ async def test_functions_with_optional_parameters( symbols, ], ) -async def test_functions_returns_response(func, mock_client, spam, mocker): - req_spy = mocker.spy(Client, "request") - result = await func(spam) +async def test_functions_returns_response(func, fake_tcp_server, spam): + _, host, port = fake_tcp_server + result = await func(spam, host=host, port=port) - assert req_spy.spy_return is result + assert isinstance(result, Response) + + +@pytest.mark.parametrize( + "func", + [ + check, + headers, + process, + report, + report_if_spam, + symbols, + ], +) +async def test_functions_returns_response_ssl( + func, fake_tcp_ssl_server, spam, ca_cert_path +): + _, host, port = fake_tcp_ssl_server + result = await func(spam, host=host, port=port, verify=ca_cert_path) + + assert isinstance(result, Response) + + +@pytest.mark.parametrize( + "func", + [ + check, + headers, + process, + report, + report_if_spam, + symbols, + ], +) +async def test_functions_returns_response_ssl_client( + func, fake_tcp_ssl_client, spam, ca_cert_path, client_cert_and_key_path +): + _, host, port = fake_tcp_ssl_client + result = await func( + spam, host=host, port=port, verify=ca_cert_path, cert=client_cert_and_key_path + ) + + assert isinstance(result, Response) -async def test_ping_request_with_parameters(mock_client, mocker): +async def test_ping_request_with_parameters(fake_tcp_server, mocker): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") - await ping() - req = req_spy.await_args[0][0] + await ping(host=host, port=port) + req = req_spy.await_args[0][1] assert "PING" == req.verb assert "User" not in req.headers -async def test_ping_returns_response(mock_client, mocker): +async def test_ping_returns_response(fake_tcp_server, mocker): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") - result = await ping() + result = await ping(host=host, port=port) assert req_spy.spy_return is result -async def test_tell_request_with_default_parameters(mock_client, spam, mocker): +async def test_ping_returns_response_ssl(fake_tcp_ssl_server, spam, ca_cert_path): + _, host, port = fake_tcp_ssl_server + result = await ping(host=host, port=port, verify=ca_cert_path) + + assert isinstance(result, Response) + + +async def test_ping_returns_response_ssl_client( + fake_tcp_ssl_client, spam, ca_cert_path, client_cert_and_key_path +): + _, host, port = fake_tcp_ssl_client + result = await ping( + host=host, port=port, verify=ca_cert_path, cert=client_cert_and_key_path + ) + + assert isinstance(result, Response) + + +async def test_tell_request_with_default_parameters(fake_tcp_server, spam, mocker): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") - await tell(spam, MessageClassOption.spam) - req = req_spy.await_args[0][0] + await tell(spam, MessageClassOption.spam, host=host, port=port) + req = req_spy.await_args[0][1] assert "TELL" == req.verb assert "User" not in req.headers @@ -129,7 +331,8 @@ async def test_tell_request_with_default_parameters(mock_client, spam, mocker): assert spam == req.body -async def test_tell_request_with_optional_parameters(mock_client, spam, mocker): +async def test_tell_request_with_optional_parameters(fake_tcp_server, spam, mocker): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") await tell( spam, @@ -138,8 +341,10 @@ async def test_tell_request_with_optional_parameters(mock_client, spam, mocker): remove_action=ActionOption(local=True, remote=True), user="testuser", compress=True, + host=host, + port=port, ) - req = req_spy.await_args[0][0] + req = req_spy.await_args[0][1] assert "TELL" == req.verb assert "testuser" == req.headers["User"].name @@ -150,9 +355,10 @@ async def test_tell_request_with_optional_parameters(mock_client, spam, mocker): assert spam == req.body -async def test_tell_returns_response(mock_client, spam, mocker): +async def test_tell_returns_response(fake_tcp_server, spam, mocker): + _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") - result = await tell(spam, MessageClassOption.spam) + result = await tell(spam, MessageClassOption.spam, host=host, port=port) assert req_spy.spy_return is result @@ -160,507 +366,553 @@ async def test_tell_returns_response(mock_client, spam, mocker): @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_bad_response( - func, mock_client_response, response_invalid, mocker -): - mock_client_response(response_invalid) +async def test_raises_bad_response(func, fake_tcp_server, response_invalid): + resp, host, port = fake_tcp_server + resp.response = response_invalid with pytest.raises(BadResponse): - await func(mocker.MagicMock()) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_usage(func, mock_client_response, mocker, ex_usage): - mock_client = mock_client_response(ex_usage) +async def test_raises_usage(func, fake_tcp_server, ex_usage): + resp, host, port = fake_tcp_server + resp.response = ex_usage with pytest.raises(UsageException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_data_err(func, mock_client_response, mocker, ex_data_err): - mock_client = mock_client_response(ex_data_err) +async def test_raises_data_err(func, fake_tcp_server, ex_data_err): + resp, host, port = fake_tcp_server + resp.response = ex_data_err with pytest.raises(DataErrorException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_no_input(func, mock_client_response, mocker, ex_no_input): - mock_client = mock_client_response(ex_no_input) +async def test_raises_no_input(func, fake_tcp_server, ex_no_input): + resp, host, port = fake_tcp_server + resp.response = ex_no_input with pytest.raises(NoInputException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_no_user(func, mock_client_response, mocker, ex_no_user): - mock_client = mock_client_response(ex_no_user) +async def test_raises_no_user(func, fake_tcp_server, ex_no_user): + resp, host, port = fake_tcp_server + resp.response = ex_no_user with pytest.raises(NoUserException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_no_host(func, mock_client_response, mocker, ex_no_host): - mock_client = mock_client_response(ex_no_host) +async def test_raises_no_host(func, fake_tcp_server, ex_no_host): + resp, host, port = fake_tcp_server + resp.response = ex_no_host with pytest.raises(NoHostException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_unavailable(func, mock_client_response, mocker, ex_unavailable): - mock_client = mock_client_response(ex_unavailable) +async def test_raises_unavailable(func, fake_tcp_server, ex_unavailable): + resp, host, port = fake_tcp_server + resp.response = ex_unavailable with pytest.raises(UnavailableException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_software(func, mock_client_response, mocker, ex_software): - mock_client = mock_client_response(ex_software) +async def test_raises_software(func, fake_tcp_server, ex_software): + resp, host, port = fake_tcp_server + resp.response = ex_software with pytest.raises(InternalSoftwareException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_os_error(func, mock_client_response, mocker, ex_os_err): - mock_client = mock_client_response(ex_os_err) +async def test_raises_os_error(func, fake_tcp_server, ex_os_err): + resp, host, port = fake_tcp_server + resp.response = ex_os_err with pytest.raises(OSErrorException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_os_file(func, mock_client_response, mocker, ex_os_file): - mock_client = mock_client_response(ex_os_file) +async def test_raises_os_file(func, fake_tcp_server, ex_os_file): + resp, host, port = fake_tcp_server + resp.response = ex_os_file with pytest.raises(OSFileException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_cant_create(func, mock_client_response, mocker, ex_cant_create): - mock_client = mock_client_response(ex_cant_create) +async def test_raises_cant_create(func, fake_tcp_server, ex_cant_create): + resp, host, port = fake_tcp_server + resp.response = ex_cant_create with pytest.raises(CantCreateException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_io_error(func, mock_client_response, mocker, ex_io_err): - mock_client = mock_client_response(ex_io_err) +async def test_raises_io_error(func, fake_tcp_server, ex_io_err): + resp, host, port = fake_tcp_server + resp.response = ex_io_err with pytest.raises(IOErrorException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_temporary_failure( - func, mock_client_response, mocker, ex_temp_fail -): - mock_client = mock_client_response(ex_temp_fail) +async def test_raises_temporary_failure(func, fake_tcp_server, ex_temp_fail): + resp, host, port = fake_tcp_server + resp.response = ex_temp_fail with pytest.raises(TemporaryFailureException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_protocol(func, mock_client_response, mocker, ex_protocol): - mock_client = mock_client_response(ex_protocol) +async def test_raises_protocol(func, fake_tcp_server, ex_protocol): + resp, host, port = fake_tcp_server + resp.response = ex_protocol with pytest.raises(ProtocolException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_no_permission(func, mock_client_response, mocker, ex_no_perm): - mock_client = mock_client_response(ex_no_perm) +async def test_raises_no_permission(func, fake_tcp_server, ex_no_perm): + resp, host, port = fake_tcp_server + resp.response = ex_no_perm with pytest.raises(NoPermissionException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_config(func, mock_client_response, mocker, ex_config): - mock_client = mock_client_response(ex_config) +async def test_raises_config(func, fake_tcp_server, ex_config): + resp, host, port = fake_tcp_server + resp.response = ex_config with pytest.raises(ConfigException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_timeout(func, mock_client_response, mocker, ex_timeout): - mock_client = mock_client_response(ex_timeout) +async def test_raises_timeout(func, fake_tcp_server, ex_timeout): + resp, host, port = fake_tcp_server + resp.response = ex_timeout with pytest.raises(ServerTimeoutException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) @pytest.mark.parametrize( "func", [check, headers, process, report, report_if_spam, symbols] ) -async def test_raises_undefined(func, mock_client_response, mocker, ex_undefined): - mock_client = mock_client_response(ex_undefined) +async def test_raises_undefined(func, fake_tcp_server, ex_undefined): + resp, host, port = fake_tcp_server + resp.response = ex_undefined with pytest.raises(ResponseException): - await func( - mocker.MagicMock(), - ) + await func(b"test", host=host, port=port) -async def test_ping_raises_usage(mock_client_response, ex_usage): - mock_client = mock_client_response(ex_usage) +async def test_ping_raises_usage(fake_tcp_server, ex_usage): + resp, host, port = fake_tcp_server + resp.response = ex_usage with pytest.raises(UsageException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_data_err(mock_client_response, ex_data_err): - mock_client = mock_client_response(ex_data_err) +async def test_ping_raises_data_err(fake_tcp_server, ex_data_err): + resp, host, port = fake_tcp_server + resp.response = ex_data_err with pytest.raises(DataErrorException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_no_input(mock_client_response, ex_no_input): - mock_client = mock_client_response(ex_no_input) +async def test_ping_raises_no_input(fake_tcp_server, ex_no_input): + resp, host, port = fake_tcp_server + resp.response = ex_no_input with pytest.raises(NoInputException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_no_user(mock_client_response, ex_no_user): - mock_client = mock_client_response(ex_no_user) +async def test_ping_raises_no_user(fake_tcp_server, ex_no_user): + resp, host, port = fake_tcp_server + resp.response = ex_no_user with pytest.raises(NoUserException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_no_host(mock_client_response, ex_no_host): - mock_client = mock_client_response(ex_no_host) +async def test_ping_raises_no_host(fake_tcp_server, ex_no_host): + resp, host, port = fake_tcp_server + resp.response = ex_no_host with pytest.raises(NoHostException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_unavailable(mock_client_response, ex_unavailable): - mock_client = mock_client_response(ex_unavailable) +async def test_ping_raises_unavailable(fake_tcp_server, ex_unavailable): + resp, host, port = fake_tcp_server + resp.response = ex_unavailable with pytest.raises(UnavailableException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_software(mock_client_response, ex_software): - mock_client = mock_client_response(ex_software) +async def test_ping_raises_software(fake_tcp_server, ex_software): + resp, host, port = fake_tcp_server + resp.response = ex_software with pytest.raises(InternalSoftwareException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_os_error(mock_client_response, ex_os_err): - mock_client = mock_client_response(ex_os_err) +async def test_ping_raises_os_error(fake_tcp_server, ex_os_err): + resp, host, port = fake_tcp_server + resp.response = ex_os_err with pytest.raises(OSErrorException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_os_file(mock_client_response, ex_os_file): - mock_client = mock_client_response(ex_os_file) +async def test_ping_raises_os_file(fake_tcp_server, ex_os_file): + resp, host, port = fake_tcp_server + resp.response = ex_os_file with pytest.raises(OSFileException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_cant_create(mock_client_response, ex_cant_create): - mock_client = mock_client_response(ex_cant_create) +async def test_ping_raises_cant_create(fake_tcp_server, ex_cant_create): + resp, host, port = fake_tcp_server + resp.response = ex_cant_create with pytest.raises(CantCreateException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_io_error(mock_client_response, ex_io_err): - mock_client = mock_client_response(ex_io_err) +async def test_ping_raises_io_error(fake_tcp_server, ex_io_err): + resp, host, port = fake_tcp_server + resp.response = ex_io_err with pytest.raises(IOErrorException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_temporary_failure(mock_client_response, ex_temp_fail): - mock_client = mock_client_response(ex_temp_fail) +async def test_ping_raises_temporary_failure(fake_tcp_server, ex_temp_fail): + resp, host, port = fake_tcp_server + resp.response = ex_temp_fail with pytest.raises(TemporaryFailureException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_protocol(mock_client_response, ex_protocol): - mock_client = mock_client_response(ex_protocol) +async def test_ping_raises_protocol(fake_tcp_server, ex_protocol): + resp, host, port = fake_tcp_server + resp.response = ex_protocol with pytest.raises(ProtocolException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_no_permission(mock_client_response, ex_no_perm): - mock_client = mock_client_response(ex_no_perm) +async def test_ping_raises_no_permission(fake_tcp_server, ex_no_perm): + resp, host, port = fake_tcp_server + resp.response = ex_no_perm with pytest.raises(NoPermissionException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_config(mock_client_response, ex_config): - mock_client = mock_client_response(ex_config) +async def test_ping_raises_config(fake_tcp_server, ex_config): + resp, host, port = fake_tcp_server + resp.response = ex_config with pytest.raises(ConfigException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_timeout(mock_client_response, ex_timeout): - mock_client = mock_client_response(ex_timeout) +async def test_ping_raises_timeout(fake_tcp_server, ex_timeout): + resp, host, port = fake_tcp_server + resp.response = ex_timeout with pytest.raises(ServerTimeoutException): - await ping() + await ping(host=host, port=port) -async def test_ping_raises_undefined(mock_client_response, ex_undefined): - mock_client = mock_client_response(ex_undefined) +async def test_ping_raises_undefined(fake_tcp_server, ex_undefined): + resp, host, port = fake_tcp_server + resp.response = ex_undefined with pytest.raises(ResponseException): - await ping() + await ping(host=host, port=port) -async def test_tell_raises_usage(mock_client_response, mocker, ex_usage): - mock_client = mock_client_response(ex_usage) +async def test_tell_raises_usage(fake_tcp_server, ex_usage): + resp, host, port = fake_tcp_server + resp.response = ex_usage with pytest.raises(UsageException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_data_err(mock_client_response, mocker, ex_data_err): - mock_client = mock_client_response(ex_data_err) +async def test_tell_raises_data_err(fake_tcp_server, ex_data_err): + resp, host, port = fake_tcp_server + resp.response = ex_data_err with pytest.raises(DataErrorException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_no_input(mock_client_response, mocker, ex_no_input): - mock_client = mock_client_response(ex_no_input) +async def test_tell_raises_no_input(fake_tcp_server, ex_no_input): + resp, host, port = fake_tcp_server + resp.response = ex_no_input with pytest.raises(NoInputException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_no_user(mock_client_response, mocker, ex_no_user): - mock_client = mock_client_response(ex_no_user) +async def test_tell_raises_no_user(fake_tcp_server, ex_no_user): + resp, host, port = fake_tcp_server + resp.response = ex_no_user with pytest.raises(NoUserException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_no_host(mock_client_response, mocker, ex_no_host): - mock_client = mock_client_response(ex_no_host) +async def test_tell_raises_no_host(fake_tcp_server, ex_no_host): + resp, host, port = fake_tcp_server + resp.response = ex_no_host with pytest.raises(NoHostException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_unavailable(mock_client_response, mocker, ex_unavailable): - mock_client = mock_client_response(ex_unavailable) +async def test_tell_raises_unavailable(fake_tcp_server, ex_unavailable): + resp, host, port = fake_tcp_server + resp.response = ex_unavailable with pytest.raises(UnavailableException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_software(mock_client_response, mocker, ex_software): - mock_client = mock_client_response(ex_software) +async def test_tell_raises_software(fake_tcp_server, ex_software): + resp, host, port = fake_tcp_server + resp.response = ex_software with pytest.raises(InternalSoftwareException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_os_error(mock_client_response, mocker, ex_os_err): - mock_client = mock_client_response(ex_os_err) +async def test_tell_raises_os_error(fake_tcp_server, ex_os_err): + resp, host, port = fake_tcp_server + resp.response = ex_os_err with pytest.raises(OSErrorException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_os_file(mock_client_response, mocker, ex_os_file): - mock_client = mock_client_response(ex_os_file) +async def test_tell_raises_os_file(fake_tcp_server, ex_os_file): + resp, host, port = fake_tcp_server + resp.response = ex_os_file with pytest.raises(OSFileException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_cant_create(mock_client_response, mocker, ex_cant_create): - mock_client = mock_client_response(ex_cant_create) +async def test_tell_raises_cant_create(fake_tcp_server, ex_cant_create): + resp, host, port = fake_tcp_server + resp.response = ex_cant_create with pytest.raises(CantCreateException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_io_error(mock_client_response, mocker, ex_io_err): - mock_client = mock_client_response(ex_io_err) +async def test_tell_raises_io_error(fake_tcp_server, ex_io_err): + resp, host, port = fake_tcp_server + resp.response = ex_io_err with pytest.raises(IOErrorException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_temporary_failure( - mock_client_response, mocker, ex_temp_fail -): - mock_client = mock_client_response(ex_temp_fail) +async def test_tell_raises_temporary_failure(fake_tcp_server, ex_temp_fail): + resp, host, port = fake_tcp_server + resp.response = ex_temp_fail with pytest.raises(TemporaryFailureException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_protocol(mock_client_response, mocker, ex_protocol): - mock_client = mock_client_response(ex_protocol) +async def test_tell_raises_protocol(fake_tcp_server, ex_protocol): + resp, host, port = fake_tcp_server + resp.response = ex_protocol with pytest.raises(ProtocolException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_no_permission(mock_client_response, mocker, ex_no_perm): - mock_client = mock_client_response(ex_no_perm) +async def test_tell_raises_no_permission(fake_tcp_server, ex_no_perm): + resp, host, port = fake_tcp_server + resp.response = ex_no_perm with pytest.raises(NoPermissionException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_config(mock_client_response, mocker, ex_config): - mock_client = mock_client_response(ex_config) +async def test_tell_raises_config(fake_tcp_server, ex_config): + resp, host, port = fake_tcp_server + resp.response = ex_config with pytest.raises(ConfigException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_timeout(mock_client_response, mocker, ex_timeout): - mock_client = mock_client_response(ex_timeout) +async def test_tell_raises_timeout(fake_tcp_server, ex_timeout): + resp, host, port = fake_tcp_server + resp.response = ex_timeout with pytest.raises(ServerTimeoutException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) -async def test_tell_raises_undefined(mock_client_response, mocker, ex_undefined): - mock_client = mock_client_response(ex_undefined) +async def test_tell_raises_undefined(fake_tcp_server, ex_undefined): + resp, host, port = fake_tcp_server + resp.response = ex_undefined with pytest.raises(ResponseException): await tell( - mocker.MagicMock(), - MessageClassOption.spam, + b"test", + host=host, + port=port, + message_class=MessageClassOption.spam, ) diff --git a/tests/test_integration_example_messages.py b/tests/test_integration_example_messages.py index 23954ac1..cafefb51 100644 --- a/tests/test_integration_example_messages.py +++ b/tests/test_integration_example_messages.py @@ -6,15 +6,15 @@ @pytest.mark.integration -async def test_spam(spamd, hostname, tcp_port, spam): - result = await aiospamc.check(spam, host=hostname, port=tcp_port) +async def test_spam(spamd_tcp, spam): + result = await aiospamc.check(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code assert True is result.headers["Spam"].value @pytest.mark.integration -async def test_gtk_encoding(spamd, hostname, tcp_port): +async def test_gtk_encoding(spamd_tcp): message = EmailMessage() message.add_header("From", "wevsty ") message.add_header("Subject", "=?UTF-8?B?5Lit5paH5rWL6K+V?=") @@ -25,6 +25,6 @@ async def test_gtk_encoding(spamd, hostname, tcp_port): message.set_param("charset", "gbk") message.set_content("่ฟ™ๆ˜ฏUnicodeๆ–‡ๅญ—." "This is Unicode characters.") - result = await aiospamc.check(message, host=hostname, port=tcp_port) + result = await aiospamc.check(message, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code diff --git a/tests/test_integration_ssl.py b/tests/test_integration_ssl.py index 917b06ae..b8700a0a 100644 --- a/tests/test_integration_ssl.py +++ b/tests/test_integration_ssl.py @@ -4,87 +4,85 @@ @pytest.mark.integration -async def test_verify_false(spamd, hostname, ssl_port, certificate_authority): - result = await aiospamc.ping( - host=hostname, port=ssl_port, verify=certificate_authority - ) +async def test_verify_false(spamd_ssl): + result = await aiospamc.ping(host=spamd_ssl[0], port=spamd_ssl[1], verify=False) assert 0 == result.status_code @pytest.mark.integration -async def test_check(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_check(spamd_ssl, ca_cert_path, spam): result = await aiospamc.check( - spam, host=hostname, port=ssl_port, verify=certificate_authority + spam, host=spamd_ssl[0], port=spamd_ssl[1], verify=ca_cert_path ) assert 0 == result.status_code @pytest.mark.integration -async def test_headers(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_headers(spamd_ssl, ca_cert_path, spam): result = await aiospamc.headers( - spam, host=hostname, port=ssl_port, verify=certificate_authority + spam, host=spamd_ssl[0], port=spamd_ssl[1], verify=ca_cert_path ) assert 0 == result.status_code @pytest.mark.integration -async def test_ping(spamd, hostname, ssl_port, certificate_authority): +async def test_ping(spamd_ssl, ca_cert_path): result = await aiospamc.ping( - host=hostname, - port=ssl_port, - verify=certificate_authority, + host=spamd_ssl[0], + port=spamd_ssl[1], + verify=ca_cert_path, ) assert 0 == result.status_code @pytest.mark.integration -async def test_process(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_process(spamd_ssl, ca_cert_path, spam): result = await aiospamc.process( - spam, host=hostname, port=ssl_port, verify=certificate_authority + spam, host=spamd_ssl[0], port=spamd_ssl[1], verify=ca_cert_path ) assert 0 == result.status_code @pytest.mark.integration -async def test_report(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_report(spamd_ssl, ca_cert_path, spam): result = await aiospamc.report( - spam, host=hostname, port=ssl_port, verify=certificate_authority + spam, host=spamd_ssl[0], port=spamd_ssl[1], verify=ca_cert_path ) assert 0 == result.status_code @pytest.mark.integration -async def test_report_if_spam(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_report_if_spam(spamd_ssl, ca_cert_path, spam): result = await aiospamc.report_if_spam( - spam, host=hostname, port=ssl_port, verify=certificate_authority + spam, host=spamd_ssl[0], port=spamd_ssl[1], verify=ca_cert_path ) assert 0 == result.status_code @pytest.mark.integration -async def test_symbols(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_symbols(spamd_ssl, ca_cert_path, spam): result = await aiospamc.symbols( - spam, host=hostname, port=ssl_port, verify=certificate_authority + spam, host=spamd_ssl[0], port=spamd_ssl[1], verify=ca_cert_path ) assert 0 == result.status_code @pytest.mark.integration -async def test_tell(spamd, hostname, ssl_port, spam, certificate_authority): +async def test_tell(spamd_ssl, ca_cert_path, spam): result = await aiospamc.tell( message=spam, message_class="spam", - host=hostname, - port=ssl_port, - verify=certificate_authority, + host=spamd_ssl[0], + port=spamd_ssl[1], + verify=ca_cert_path, ) assert 0 == result.status_code diff --git a/tests/test_integration_ssl_client.py b/tests/test_integration_ssl_client.py new file mode 100644 index 00000000..464baf66 --- /dev/null +++ b/tests/test_integration_ssl_client.py @@ -0,0 +1,147 @@ +from shutil import which +from subprocess import PIPE, Popen + +import pytest + +import aiospamc + + +def spamd_lt_4(): + import re + + spamd_exe = which("spamd") + if not spamd_exe: + return True + + process = Popen([spamd_exe, "--version"], stdout=PIPE) + process.wait(5) + version = re.match(rb".*?(\d+)\.\d+\.\d+\n", process.stdout.read()) + parsed = [int(i) for i in version.groups()] + + return parsed[0] < 4 + + +pytestmark = pytest.mark.skipif( + spamd_lt_4(), + reason="Only SpamAssassin 4+ supports client certificate authentication", +) + + +@pytest.mark.integration +async def test_check_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.check( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_headers_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.headers( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_ping_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path +): + result = await aiospamc.ping( + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_process_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.process( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_report_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.report( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_report_if_spam_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.report_if_spam( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_symbols_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.symbols( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_tell_client_auth( + spamd_ssl_client, ca_cert_path, client_cert_path, client_key_path, spam +): + result = await aiospamc.tell( + message=spam, + message_class="spam", + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_key_path), + ) + + assert 0 == result.status_code diff --git a/tests/test_integration_tcp.py b/tests/test_integration_tcp.py index 7ea8b331..be32918f 100644 --- a/tests/test_integration_tcp.py +++ b/tests/test_integration_tcp.py @@ -4,61 +4,61 @@ @pytest.mark.integration -async def test_check(spamd, hostname, tcp_port, spam): - result = await aiospamc.check(spam, host=hostname, port=tcp_port) +async def test_check(spamd_tcp, spam): + result = await aiospamc.check(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_headers(spamd, hostname, tcp_port, spam): - result = await aiospamc.headers(spam, host=hostname, port=tcp_port) +async def test_headers(spamd_tcp, spam): + result = await aiospamc.headers(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_ping(spamd, hostname, tcp_port): - result = await aiospamc.ping(host=hostname, port=tcp_port) +async def test_ping(spamd_tcp): + result = await aiospamc.ping(host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_process(spamd, hostname, tcp_port, spam): - result = await aiospamc.process(spam, host=hostname, port=tcp_port) +async def test_process(spamd_tcp, spam): + result = await aiospamc.process(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_report(spamd, hostname, tcp_port, spam): - result = await aiospamc.report(spam, host=hostname, port=tcp_port) +async def test_report(spamd_tcp, spam): + result = await aiospamc.report(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_report_if_spam(spamd, hostname, tcp_port, spam): - result = await aiospamc.report_if_spam(spam, host=hostname, port=tcp_port) +async def test_report_if_spam(spamd_tcp, spam): + result = await aiospamc.report_if_spam(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_symbols(spamd, hostname, tcp_port, spam): - result = await aiospamc.symbols(spam, host=hostname, port=tcp_port) +async def test_symbols(spamd_tcp, spam): + result = await aiospamc.symbols(spam, host=spamd_tcp[0], port=spamd_tcp[1]) assert 0 == result.status_code @pytest.mark.integration -async def test_tell(spamd, hostname, tcp_port, spam): +async def test_tell(spamd_tcp, spam): result = await aiospamc.tell( message=spam, message_class="spam", - host=hostname, - port=tcp_port, + host=spamd_tcp[0], + port=spamd_tcp[1], ) assert 0 == result.status_code diff --git a/tests/test_integration_unix.py b/tests/test_integration_unix.py index cefefb36..2733e306 100644 --- a/tests/test_integration_unix.py +++ b/tests/test_integration_unix.py @@ -4,94 +4,71 @@ import aiospamc - -@pytest.mark.skipif( +pytestmark = pytest.mark.skipif( sys.platform == "win32", reason="Unix sockets not supported on Windows" ) + + @pytest.mark.integration -async def test_check(spamd, unix_socket, spam): - result = await aiospamc.check(spam, socket_path=unix_socket) +async def test_check(spamd_unix, spam): + result = await aiospamc.check(spam, socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_headers(spamd, unix_socket, spam): - result = await aiospamc.headers(spam, socket_path=unix_socket) +async def test_headers(spamd_unix, spam): + result = await aiospamc.headers(spam, socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_ping(spamd, unix_socket): - result = await aiospamc.ping(socket_path=unix_socket) +async def test_ping(spamd_unix): + result = await aiospamc.ping(socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_process(spamd, unix_socket, spam): - result = await aiospamc.process(spam, socket_path=unix_socket) +async def test_process(spamd_unix, spam): + result = await aiospamc.process(spam, socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_report(spamd, unix_socket, spam): - result = await aiospamc.report(spam, socket_path=unix_socket) +async def test_report(spamd_unix, spam): + result = await aiospamc.report(spam, socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_report_if_spam(spamd, unix_socket, spam): - result = await aiospamc.report_if_spam(spam, socket_path=unix_socket) +async def test_report_if_spam(spamd_unix, spam): + result = await aiospamc.report_if_spam(spam, socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_symbols(spamd, unix_socket, spam): - result = await aiospamc.symbols(spam, socket_path=unix_socket) +async def test_symbols(spamd_unix, spam): + result = await aiospamc.symbols(spam, socket_path=spamd_unix) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_tell(spamd, unix_socket, spam): +async def test_tell(spamd_unix, spam): result = await aiospamc.tell( - message=spam, message_class="spam", socket_path=unix_socket + message=spam, message_class="spam", socket_path=spamd_unix ) assert 0 == result.status_code -@pytest.mark.skipif( - sys.platform == "win32", reason="Unix sockets not supported on Windows" -) @pytest.mark.integration -async def test_message_without_newline(spamd, unix_socket): - result = await aiospamc.check(message=b"acb", socket_path=unix_socket) +async def test_message_without_newline(spamd_unix): + result = await aiospamc.check(message=b"acb", socket_path=spamd_unix) assert 0 == result.status_code From dafd4ba762b22cbdcb521395c82f5e802baea0bb Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Fri, 18 Aug 2023 12:52:57 -0400 Subject: [PATCH 08/41] Marking package as stable #320 --- pyproject.toml | 2 +- .../notes/320-stable-classifier-81d3b6676e1a4a39.yaml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/320-stable-classifier-81d3b6676e1a4a39.yaml diff --git a/pyproject.toml b/pyproject.toml index 4a2122aa..94feea49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ homepage = "https://github.com/mjcaley/aiospamc" documentation = "https://aiospamc.readthedocs.io" classifiers = [ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', diff --git a/releasenotes/notes/320-stable-classifier-81d3b6676e1a4a39.yaml b/releasenotes/notes/320-stable-classifier-81d3b6676e1a4a39.yaml new file mode 100644 index 00000000..fbdd3a71 --- /dev/null +++ b/releasenotes/notes/320-stable-classifier-81d3b6676e1a4a39.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Marked package as stable :github:issue:`320` From 57644c037d9b171e8b713ec0578e6a2ded6edea8 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Fri, 18 Aug 2023 15:45:11 -0400 Subject: [PATCH 09/41] Update CodeQL workflow --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9e4a7f7f..8bab9db5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,7 +53,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # โ„น๏ธ Command-line programs to run using the OS shell. # ๐Ÿ“š https://git.io/JvXDl @@ -67,4 +67,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From 7b2136ad4f5d24b4ada521ebdc4ee34986cf9f45 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Fri, 18 Aug 2023 20:34:30 -0400 Subject: [PATCH 10/41] Update dependencies --- poetry.lock | 848 ++++++++++++++++++++++++++-------------------------- 1 file changed, 420 insertions(+), 428 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e3e4550..5076aa5d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "apeye" -version = "1.3.0" +version = "1.4.1" description = "Handy tools for working with URLs and APIs." optional = false python-versions = ">=3.6.1" files = [ - {file = "apeye-1.3.0-py3-none-any.whl", hash = "sha256:8ce991a5c7d1ccedd46f067fcd53eccfa4b0b62a50d8802340b427941603b839"}, - {file = "apeye-1.3.0.tar.gz", hash = "sha256:30ac4dcc6eafc28ae2cc6911caef7396c2692a3842e2d77aafe03cdb979eeb3c"}, + {file = "apeye-1.4.1-py3-none-any.whl", hash = "sha256:44e58a9104ec189bf42e76b3a7fe91e2b2879d96d48e9a77e5e32ff699c9204e"}, + {file = "apeye-1.4.1.tar.gz", hash = "sha256:14ea542fad689e3bfdbda2189a354a4908e90aee4bf84c15ab75d68453d76a36"}, ] [package.dependencies] @@ -34,13 +34,13 @@ limiter = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] [[package]] name = "apeye-core" -version = "1.1.2" +version = "1.1.4" description = "Core (offline) functionality for the apeye library." optional = false python-versions = ">=3.6.1" files = [ - {file = "apeye_core-1.1.2-py3-none-any.whl", hash = "sha256:7c38930ecc9a9bf002187a7d59fedd7442f995013fa1cd9501e4564d600886f6"}, - {file = "apeye_core-1.1.2.tar.gz", hash = "sha256:06f9c147b12b24ceafefe6a48f52ac628e3b94d2df375f1b88c0aa41d6b24276"}, + {file = "apeye_core-1.1.4-py3-none-any.whl", hash = "sha256:084bc696448d3ac428fece41c1f2eb08fa9d9ce1d1b2f4d43187e3def4528a60"}, + {file = "apeye_core-1.1.4.tar.gz", hash = "sha256:72bb89fed3baa647cb81aa28e1d851787edcbf9573853b5d2b5f87c02f50eaf5"}, ] [package.dependencies] @@ -113,36 +113,33 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.3.0" +version = "23.7.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, ] [package.dependencies] @@ -162,33 +159,34 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cachecontrol" -version = "0.12.11" +version = "0.13.1" description = "httplib2 caching for requests" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"}, - {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"}, + {file = "cachecontrol-0.13.1-py3-none-any.whl", hash = "sha256:95dedbec849f46dda3137866dc28b9d133fc9af55f5b805ab1291833e4457aa4"}, + {file = "cachecontrol-0.13.1.tar.gz", hash = "sha256:f012366b79d2243a6118309ce73151bf52a38d4a5dac8ea57f09bd29087e506b"}, ] [package.dependencies] -lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} msgpack = ">=0.5.2" -requests = "*" +requests = ">=2.16.0" [package.extras] -filecache = ["lockfile (>=0.9)"] +dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "mypy", "pytest", "pytest-cov", "sphinx", "tox", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] redis = ["redis (>=2.10.5)"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -269,97 +267,97 @@ pycparser = "*" [[package]] name = "cfgv" -version = "3.3.1" +version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8" files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] [[package]] @@ -380,13 +378,13 @@ unidecode = ">=1.0.23,<2.0.0" [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -416,62 +414,63 @@ files = [ [[package]] name = "coverage" -version = "7.2.6" +version = "7.3.0" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "coverage-7.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:496b86f1fc9c81a1cd53d8842ef712e950a4611bba0c42d33366a7b91ba969ec"}, - {file = "coverage-7.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbe6e8c0a9a7193ba10ee52977d4d5e7652957c1f56ccefed0701db8801a2a3b"}, - {file = "coverage-7.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d06b721c2550c01a60e5d3093f417168658fb454e5dfd9a23570e9bffe39a1"}, - {file = "coverage-7.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77a04b84d01f0e12c66f16e69e92616442dc675bbe51b90bfb074b1e5d1c7fbd"}, - {file = "coverage-7.2.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35db06450272473eab4449e9c2ad9bc6a0a68dab8e81a0eae6b50d9c2838767e"}, - {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6727a0d929ff0028b1ed8b3e7f8701670b1d7032f219110b55476bb60c390bfb"}, - {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aac1d5fdc5378f6bac2c0c7ebe7635a6809f5b4376f6cf5d43243c1917a67087"}, - {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c9e4a5eb1bbc3675ee57bc31f8eea4cd7fb0cbcbe4912cf1cb2bf3b754f4a80"}, - {file = "coverage-7.2.6-cp310-cp310-win32.whl", hash = "sha256:71f739f97f5f80627f1fee2331e63261355fd1e9a9cce0016394b6707ac3f4ec"}, - {file = "coverage-7.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:fde5c7a9d9864d3e07992f66767a9817f24324f354caa3d8129735a3dc74f126"}, - {file = "coverage-7.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc7b667f8654376e9353dd93e55e12ce2a59fb6d8e29fce40de682273425e044"}, - {file = "coverage-7.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:697f4742aa3f26c107ddcb2b1784a74fe40180014edbd9adaa574eac0529914c"}, - {file = "coverage-7.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:541280dde49ce74a4262c5e395b48ea1207e78454788887118c421cb4ffbfcac"}, - {file = "coverage-7.2.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7f1a8328eeec34c54f1d5968a708b50fc38d31e62ca8b0560e84a968fbf9a9"}, - {file = "coverage-7.2.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbd58eb5a2371bf160590f4262109f66b6043b0b991930693134cb617bc0169"}, - {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae82c5f168d2a39a5d69a12a69d4dc23837a43cf2ca99be60dfe59996ea6b113"}, - {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f5440cdaf3099e7ab17a5a7065aed59aff8c8b079597b61c1f8be6f32fe60636"}, - {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6f03f87fea579d55e0b690d28f5042ec1368650466520fbc400e7aeaf09e995"}, - {file = "coverage-7.2.6-cp311-cp311-win32.whl", hash = "sha256:dc4d5187ef4d53e0d4c8eaf530233685667844c5fb0b855fea71ae659017854b"}, - {file = "coverage-7.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:c93d52c3dc7b9c65e39473704988602300e3cc1bad08b5ab5b03ca98bbbc68c1"}, - {file = "coverage-7.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42c692b55a647a832025a4c048007034fe77b162b566ad537ce65ad824b12a84"}, - {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7786b2fa7809bf835f830779ad285215a04da76293164bb6745796873f0942d"}, - {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25bad4196104761bc26b1dae9b57383826542ec689ff0042f7f4f4dd7a815cba"}, - {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2692306d3d4cb32d2cceed1e47cebd6b1d2565c993d6d2eda8e6e6adf53301e6"}, - {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:392154d09bd4473b9d11351ab5d63391f3d5d24d752f27b3be7498b0ee2b5226"}, - {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fa079995432037b5e2ef5ddbb270bcd2ded9f52b8e191a5de11fe59a00ea30d8"}, - {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d712cefff15c712329113b01088ba71bbcef0f7ea58478ca0bbec63a824844cb"}, - {file = "coverage-7.2.6-cp37-cp37m-win32.whl", hash = "sha256:004948e296149644d208964300cb3d98affc5211e9e490e9979af4030b0d6473"}, - {file = "coverage-7.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:c1d7a31603c3483ac49c1726723b0934f88f2c011c660e6471e7bd735c2fa110"}, - {file = "coverage-7.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3436927d1794fa6763b89b60c896f9e3bd53212001026ebc9080d23f0c2733c1"}, - {file = "coverage-7.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44c9b9f1a245f3d0d202b1a8fa666a80b5ecbe4ad5d0859c0fb16a52d9763224"}, - {file = "coverage-7.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e3783a286d5a93a2921396d50ce45a909aa8f13eee964465012f110f0cbb611"}, - {file = "coverage-7.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cff6980fe7100242170092bb40d2b1cdad79502cd532fd26b12a2b8a5f9aee0"}, - {file = "coverage-7.2.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c534431153caffc7c495c3eddf7e6a6033e7f81d78385b4e41611b51e8870446"}, - {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3062fd5c62df988cea9f2972c593f77fed1182bfddc5a3b12b1e606cb7aba99e"}, - {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6284a2005e4f8061c58c814b1600ad0074ccb0289fe61ea709655c5969877b70"}, - {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:97729e6828643f168a2a3f07848e1b1b94a366b13a9f5aba5484c2215724edc8"}, - {file = "coverage-7.2.6-cp38-cp38-win32.whl", hash = "sha256:dc11b42fa61ff1e788dd095726a0aed6aad9c03d5c5984b54cb9e1e67b276aa5"}, - {file = "coverage-7.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:cbcc874f454ee51f158afd604a315f30c0e31dff1d5d5bf499fc529229d964dd"}, - {file = "coverage-7.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d3cacc6a665221108ecdf90517a8028d07a2783df3417d12dcfef1c517e67478"}, - {file = "coverage-7.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:272ab31228a9df857ab5df5d67936d8861464dc89c5d3fab35132626e9369379"}, - {file = "coverage-7.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a8723ccec4e564d4b9a79923246f7b9a8de4ec55fa03ec4ec804459dade3c4f"}, - {file = "coverage-7.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5906f6a84b47f995cd1bf0aca1c72d591c55ee955f98074e93660d64dfc66eb9"}, - {file = "coverage-7.2.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c139b7ab3f0b15f9aad0a3fedef5a1f8c0b2bdc291d88639ca2c97d3682416"}, - {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a5ffd45c6b93c23a8507e2f436983015c6457aa832496b6a095505ca2f63e8f1"}, - {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4f3c7c19581d471af0e9cb49d928172cd8492cd78a2b7a4e82345d33662929bb"}, - {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e8c0e79820cdd67978e1120983786422d279e07a381dbf89d03bbb23ec670a6"}, - {file = "coverage-7.2.6-cp39-cp39-win32.whl", hash = "sha256:13cde6bb0e58fb67d09e2f373de3899d1d1e866c5a9ff05d93615f2f54fbd2bb"}, - {file = "coverage-7.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:6b9f64526286255735847aed0221b189486e0b9ed943446936e41b7e44b08783"}, - {file = "coverage-7.2.6-pp37.pp38.pp39-none-any.whl", hash = "sha256:6babcbf1e66e46052442f10833cfc4a0d3554d8276aa37af8531a83ed3c1a01d"}, - {file = "coverage-7.2.6.tar.gz", hash = "sha256:2025f913f2edb0272ef15d00b1f335ff8908c921c8eb2013536fcaf61f5a683d"}, + {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, + {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, + {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, + {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, + {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, + {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, + {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, + {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, + {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, + {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, + {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, + {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, + {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, + {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, + {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, + {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, + {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, + {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, + {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, + {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, + {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, + {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, ] [package.dependencies] @@ -482,30 +481,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "40.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b"}, - {file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b"}, - {file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"}, - {file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c"}, - {file = "cryptography-40.0.2-cp36-abi3-win32.whl", hash = "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9"}, - {file = "cryptography-40.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404"}, - {file = "cryptography-40.0.2.tar.gz", hash = "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] @@ -514,27 +517,27 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "check-manifest", "mypy", "ruff"] -sdist = ["setuptools-rust (>=0.11.4)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] -tox = ["tox"] [[package]] name = "cssutils" -version = "2.6.0" +version = "2.7.1" description = "A CSS Cascading Style Sheets library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "cssutils-2.6.0-py3-none-any.whl", hash = "sha256:30c72f3a5c5951a11151640600aae7b3bf10e4c0d5c87f5bc505c2cd4a26e0c2"}, - {file = "cssutils-2.6.0.tar.gz", hash = "sha256:f7dcd23c1cec909fdf3630de346e1413b7b2555936dec14ba2ebb9913bf0818e"}, + {file = "cssutils-2.7.1-py3-none-any.whl", hash = "sha256:1e92e0d9dab2ec8af9f38d715393964ba533dc3beacab9b072511dfc241db775"}, + {file = "cssutils-2.7.1.tar.gz", hash = "sha256:340ecfd9835d21df8f98500f0dfcea0aee41cb4e19ecbc2cf94f0a6d36d7cb6c"}, ] [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["cssselect", "flake8 (<5)", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "dict2css" @@ -553,13 +556,13 @@ domdf-python-tools = ">=2.2.0" [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] @@ -679,13 +682,13 @@ pgp = ["gpg"] [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] @@ -693,18 +696,18 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.0" +version = "3.12.2" description = "A platform independent file lock." optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, - {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, ] [package.extras] -docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "html5lib" @@ -729,13 +732,13 @@ lxml = ["lxml"] [[package]] name = "identify" -version = "2.5.24" +version = "2.5.26" description = "File identification library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, + {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, + {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, ] [package.extras] @@ -765,13 +768,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.6.0" +version = "6.8.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, - {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] @@ -780,7 +783,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" @@ -852,17 +855,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "lockfile" -version = "0.12.2" -description = "Platform-independent file locking module" -optional = false -python-versions = "*" -files = [ - {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, - {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, -] - [[package]] name = "loguru" version = "0.7.0" @@ -883,72 +875,72 @@ dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegu [[package]] name = "markupsafe" -version = "2.1.2" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "mock" -version = "5.0.2" +version = "5.1.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" files = [ - {file = "mock-5.0.2-py3-none-any.whl", hash = "sha256:0e0bc5ba78b8db3667ad636d964eb963dc97a59f04c6f6214c5f0e4a8f726c56"}, - {file = "mock-5.0.2.tar.gz", hash = "sha256:06f18d7d65b44428202b145a9a36e99c2ee00d1eb992df0caf881d4664377891"}, + {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, + {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, ] [package.extras] @@ -1030,48 +1022,48 @@ files = [ [[package]] name = "mypy" -version = "1.3.0" +version = "1.5.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"}, - {file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"}, - {file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"}, - {file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"}, - {file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"}, - {file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"}, - {file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"}, - {file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"}, - {file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"}, - {file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"}, - {file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"}, - {file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"}, - {file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"}, - {file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"}, - {file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"}, - {file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"}, - {file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"}, - {file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"}, - {file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"}, - {file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"}, - {file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"}, - {file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"}, - {file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"}, - {file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"}, - {file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"}, - {file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] @@ -1087,13 +1079,13 @@ files = [ [[package]] name = "natsort" -version = "8.3.1" +version = "8.4.0" description = "Simple yet flexible natural sorting in Python." optional = false python-versions = ">=3.7" files = [ - {file = "natsort-8.3.1-py3-none-any.whl", hash = "sha256:d583bc9050dd10538de36297c960b93f873f0cd01671a3c50df5bd86dd391dcb"}, - {file = "natsort-8.3.1.tar.gz", hash = "sha256:517595492dde570a4fd6b6a76f644440c1ba51e2338c8a671d7f0475fda8f9fd"}, + {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, + {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, ] [package.extras] @@ -1127,13 +1119,13 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -1149,28 +1141,28 @@ files = [ [[package]] name = "platformdirs" -version = "3.5.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"}, - {file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.extras] @@ -1179,13 +1171,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.3.2" +version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.2-py2.py3-none-any.whl", hash = "sha256:8056bc52181efadf4aac792b1f4f255dfd2fb5a350ded7335d251a68561e8cb6"}, - {file = "pre_commit-3.3.2.tar.gz", hash = "sha256:66e37bec2d882de1f17f88075047ef8962581f83c234ac08da21a0c58953d1f0"}, + {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, + {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, ] [package.dependencies] @@ -1219,13 +1211,13 @@ files = [ [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -1233,13 +1225,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pytest" -version = "7.3.1" +version = "7.4.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, - {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] @@ -1251,17 +1243,17 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.21.0" +version = "0.21.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"}, - {file = "pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"}, + {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, + {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, ] [package.dependencies] @@ -1291,13 +1283,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-mock" -version = "3.10.0" +version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, - {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, ] [package.dependencies] @@ -1319,51 +1311,51 @@ files = [ [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] @@ -1410,13 +1402,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruamel-yaml" -version = "0.17.28" +version = "0.17.32" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" files = [ - {file = "ruamel.yaml-0.17.28-py3-none-any.whl", hash = "sha256:823aff68f88260805049d6a4825e36cb7f1e273a7dd8f391e7b35a16a67a30ea"}, - {file = "ruamel.yaml-0.17.28.tar.gz", hash = "sha256:3bf6df1c481d2463a633be6ee86e8aece941bb3298a9a0cd6d0865f47b1ddce6"}, + {file = "ruamel.yaml-0.17.32-py3-none-any.whl", hash = "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447"}, + {file = "ruamel.yaml-0.17.32.tar.gz", hash = "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2"}, ] [package.dependencies] @@ -1487,18 +1479,18 @@ contextlib2 = ">=0.5.5" [[package]] name = "setuptools" -version = "67.8.0" +version = "68.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, - {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -1639,25 +1631,25 @@ testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "py [[package]] name = "sphinx-toolbox" -version = "3.4.0" +version = "3.5.0" description = "Box of handy tools for Sphinx ๐Ÿงฐ ๐Ÿ“”" optional = false python-versions = ">=3.7" files = [ - {file = "sphinx_toolbox-3.4.0-py3-none-any.whl", hash = "sha256:cdf70facee515a2d9406d568a253fa3e89f930fde23c4e8095ba0c675f7c0a48"}, - {file = "sphinx_toolbox-3.4.0.tar.gz", hash = "sha256:e1cf2a3dea5ce80e175a6a9cee8b5b2792240ecf6c28993d87a63b6fcf606293"}, + {file = "sphinx_toolbox-3.5.0-py3-none-any.whl", hash = "sha256:20dfd3566717db6f2da7a400a54dc4b946f064fb31250fa44802d54cfb9b8a03"}, + {file = "sphinx_toolbox-3.5.0.tar.gz", hash = "sha256:e5b5a7153f1997572d71a06aaf6cec225483492ec2c60097a84f15aad6df18b7"}, ] [package.dependencies] apeye = ">=0.4.0" autodocsumm = ">=0.2.0" beautifulsoup4 = ">=4.9.1" -cachecontrol = {version = ">=0.12.6", extras = ["filecache"]} +cachecontrol = {version = ">=0.13.0", extras = ["filecache"]} dict2css = ">=0.2.3" -docutils = ">=0.16,<0.19" +docutils = ">=0.16" domdf-python-tools = ">=2.9.0" +filelock = ">=3.8.0" html5lib = ">=1.1" -lockfile = ">=0.12.2" "ruamel.yaml" = ">=0.16.12" sphinx = ">=3.2.0" sphinx-autodoc-typehints = ">=1.11.1" @@ -1862,13 +1854,13 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. [[package]] name = "typing-extensions" -version = "4.6.3" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, - {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] @@ -1900,23 +1892,23 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.23.0" +version = "20.24.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.0-py3-none-any.whl", hash = "sha256:6abec7670e5802a528357fdc75b26b9f57d5d92f29c5462ba0fbe45feacc685e"}, - {file = "virtualenv-20.23.0.tar.gz", hash = "sha256:a85caa554ced0c0afbd0d638e7e2d7b5f92d23478d05d17a76daeac8f279f924"}, + {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, + {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, ] [package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.11,<4" -platformdirs = ">=3.2,<4" +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "webencodings" @@ -1945,18 +1937,18 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "zipp" -version = "3.15.0" +version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" From f6b0c333b3b88c3dd4047613ba9ae7d0dcc0581c Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Fri, 18 Aug 2023 21:51:31 -0400 Subject: [PATCH 11/41] Removed workaround for Sphinx building issue --- poetry.lock | 17 +++++++++-------- pyproject.toml | 22 ---------------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5076aa5d..1ba30a81 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1876,19 +1876,20 @@ files = [ [[package]] name = "urllib3" -version = "1.26.16" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, - {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" @@ -1953,4 +1954,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "44241cbd7e7d9967b9e18102dbef9d6fa1153efbc5774965d507ac8ebcdeda13" +content-hash = "56004f450a16bf8da93e80c664beab4998f767dca3643bb3a6e194871e2bcd00" diff --git a/pyproject.toml b/pyproject.toml index 94feea49..0a9b7d76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,29 +65,11 @@ exclude_lines = [ ] fail_under = 95.0 - - - - - -[tool.poetry.group.workaround.dependencies] -urllib3 = "<2" - - - - - - [tool.poetry.group.docs.dependencies] sphinx = "^6.2" sphinx-toolbox = "^3.4" reno = "^4.0" - - - - - [tool.poetry.group.quality.dependencies] mypy = "^1.2" black = "^23" @@ -112,9 +94,6 @@ exclude = ["changes", "docs", "example", "test", "util"] profile = "black" src_paths = ["aiospamc", "tests"] - - - [tool.poetry.group.mgmt.dependencies] tbump = "^6.10" @@ -142,7 +121,6 @@ src = "aiospamc/__init__.py" src = "pyproject.toml" search = 'version = "{current_version}"' - [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From 3be569453113bb65c2ecad100b47b08297a5d422 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Mon, 21 Aug 2023 16:26:18 -0400 Subject: [PATCH 12/41] Fake servers use separate thread so they're compatible with use of asyncio.run() --- aiospamc/cli.py | 12 ++--- tests/conftest.py | 87 +++++++++++++++++++++++++++--------- tests/spamd-image/Dockerfile | 9 ---- tests/test_cli.py | 24 +++++----- tests/test_frontend.py | 7 +-- 5 files changed, 84 insertions(+), 55 deletions(-) delete mode 100644 tests/spamd-image/Dockerfile diff --git a/aiospamc/cli.py b/aiospamc/cli.py index 0cedd2cd..dcf2cb2b 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -338,7 +338,7 @@ def ping( request = Request("PING") runner = CommandRunner(client, request, out) - response = asyncio.get_event_loop().run_until_complete(runner.run()) + response = asyncio.run(runner.run()) runner.exit(response.message) @@ -426,7 +426,7 @@ def check( client = client_builder.build() runner = CommandRunner(client, request, out) - response = asyncio.get_event_loop().run_until_complete(runner.run()) + response = asyncio.run(runner.run()) if spam_header := response.headers.spam: if spam_header.value: @@ -519,7 +519,7 @@ def learn( client = client_builder.build() runner = CommandRunner(client, request, out) - response = asyncio.get_event_loop().run_until_complete(runner.run()) + response = asyncio.run(runner.run()) if response.headers.did_set: runner.exit("Message successfully learned") @@ -605,7 +605,7 @@ def forget( client = client_builder.build() runner = CommandRunner(client, request, out) - response = asyncio.get_event_loop().run_until_complete(runner.run()) + response = asyncio.run(runner.run()) if response.headers.did_remove: runner.exit("Message successfully forgotten") @@ -691,7 +691,7 @@ def report( client = client_builder.build() runner = CommandRunner(client, request, out) - response = asyncio.get_event_loop().run_until_complete(runner.run()) + response = asyncio.run(runner.run()) if response.headers.did_set and response.headers.did_set.remote is True: runner.exit("Message successfully reported") @@ -781,7 +781,7 @@ def revoke( client = client_builder.build() runner = CommandRunner(client, request, out) - response = asyncio.get_event_loop().run_until_complete(runner.run()) + response = asyncio.run(runner.run()) if response.headers.did_remove and response.headers.did_remove.remote: runner.exit("Message successfully revoked") diff --git a/tests/conftest.py b/tests/conftest.py index efce60b1..b2eb076e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,15 @@ import asyncio +import concurrent.futures import datetime import ssl import sys +import threading from asyncio import StreamReader, StreamWriter from dataclasses import dataclass, field from pathlib import Path from shutil import which from socket import gethostbyname from subprocess import PIPE, STDOUT, Popen, TimeoutExpired -from typing import Any, Optional import pytest import trustme @@ -368,63 +369,105 @@ class ServerResponse: response: bytes = b"" -def fake_server(resp: ServerResponse): +def fake_server(resp: ServerResponse, is_done: asyncio.Event): buffer_size = 1024 async def inner(reader: StreamReader, writer: StreamWriter): data = b"" - while len(chunk := await reader.read(buffer_size)) == buffer_size: + while chunk := await reader.read(buffer_size): data += chunk + if len(chunk) < buffer_size: + break writer.write(resp.response) if writer.can_write_eof(): writer.write_eof() await writer.drain() writer.close() await writer.wait_closed() + is_done.set() return inner +def run_fake_tcp_server(is_running, response, port, ssl_context=None): + async def server(): + is_done = asyncio.Event() + server = await asyncio.start_server( + fake_server(response, is_done), "localhost", port, ssl=ssl_context + ) + is_running.set() + await is_done.wait() + server.close() + await server.wait_closed() + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(server()) + loop.close() + + +def run_fake_unix_server(is_running, response, path): + async def server(): + is_done = asyncio.Event() + server = await asyncio.start_unix_server(fake_server(response, is_done), path) + is_running.set() + await is_done.wait() + server.close() + await server.wait_closed() + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(server()) + loop.close() + + @pytest.fixture async def fake_tcp_server(unused_tcp_port, response_ok): + is_running = threading.Event() response = ServerResponse(response_ok) - server = await asyncio.start_server( - fake_server(response), "localhost", unused_tcp_port - ) - yield response, "localhost", unused_tcp_port - server.close() + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + executor.submit(run_fake_tcp_server, is_running, response, unused_tcp_port) + is_running.wait() + yield response, "localhost", unused_tcp_port + executor.shutdown(wait=False, cancel_futures=True) @pytest.fixture async def fake_tcp_ssl_server(unused_tcp_port, response_ok, server_cert): - response = ServerResponse(response_ok) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) server_cert.configure_cert(context) - server = await asyncio.start_server( - fake_server(response), "localhost", unused_tcp_port, ssl=context - ) - yield response, "localhost", unused_tcp_port - server.close() + is_running = threading.Event() + response = ServerResponse(response_ok) + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + executor.submit( + run_fake_tcp_server, is_running, response, unused_tcp_port, context + ) + is_running.wait() + yield response, "localhost", unused_tcp_port + executor.shutdown(wait=False, cancel_futures=True) @pytest.fixture async def fake_tcp_ssl_client(unused_tcp_port, response_ok, ca, server_cert): - response = ServerResponse(response_ok) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.verify_mode = ssl.CERT_REQUIRED server_cert.configure_cert(context) ca.configure_trust(context) - server = await asyncio.start_server( - fake_server(response), "localhost", unused_tcp_port, ssl=context - ) - yield response, "localhost", unused_tcp_port - server.close() + is_running = threading.Event() + response = ServerResponse(response_ok) + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + executor.submit( + run_fake_tcp_server, is_running, response, unused_tcp_port, context + ) + is_running.wait() + yield response, "localhost", unused_tcp_port + executor.shutdown(wait=False, cancel_futures=True) @pytest.fixture -def mock_reader_writer(mocker: MockerFixture): +def mock_reader_writer(mocker: MockerFixture, response_ok): mock_reader = mocker.MagicMock() - mock_reader.read = mocker.AsyncMock() + mock_reader.read = mocker.AsyncMock(return_value=response_ok) mock_writer = mocker.MagicMock() mock_writer.drain = mocker.AsyncMock() mock_writer.write = mocker.MagicMock() diff --git a/tests/spamd-image/Dockerfile b/tests/spamd-image/Dockerfile deleted file mode 100644 index c2bc7461..00000000 --- a/tests/spamd-image/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM alpine:latest - -RUN apk update && \ - apk add spamassassin && \ - sa-update - -EXPOSE 783 - -CMD ["spamd", "--ip-address", "--allowed-ips=0.0.0.0/0", "--allow-tell"] diff --git a/tests/test_cli.py b/tests/test_cli.py index 8605e0c1..9e713894 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -108,11 +108,10 @@ def test_cli_builder_add_ca_client(client_cert_path, client_key_path): assert hasattr(c.connection_manager, "ssl_context") -def test_cli_runner_init_defaults(fake_tcp_server): - _, host, port = fake_tcp_server +def test_cli_runner_init_defaults(): request = Request("PING") c = CommandRunner( - Client(ConnectionManagerBuilder().with_tcp(host, port).build()), request + Client(ConnectionManagerBuilder().with_tcp("localhost", 783).build()), request ) assert request == c.request @@ -137,8 +136,7 @@ async def test_cli_runner_run_success(fake_tcp_server, response_pong): assert expected == c.response -def test_cli_runner_to_json(fake_tcp_server): - _, host, port = fake_tcp_server +def test_cli_runner_to_json(): request = Request("PING") expected = { "request": request.to_json(), @@ -147,7 +145,7 @@ def test_cli_runner_to_json(fake_tcp_server): } c = CommandRunner( - Client(ConnectionManagerBuilder().with_tcp(host, port).build()), request + Client(ConnectionManagerBuilder().with_tcp("localhost", 783).build()), request ) result = c.to_json() @@ -171,6 +169,10 @@ def test_ping_json(mocker, fake_tcp_server, response_pong): assert f"{json.dumps(expected, indent=4)}\n" == result.stdout +def test_ping(mocker, fake_tcp_server): + assert True + + @pytest.mark.parametrize( "args", [ @@ -309,13 +311,11 @@ def test_command_with_message_parser_exception( assert "Error parsing response\n" == result.stdout -def test_command_without_message_timeout_exception(fake_tcp_server): - resp, host, port = fake_tcp_server +def test_command_without_message_timeout_exception(mock_reader_writer): + reader, writer = mock_reader_writer + reader.read.side_effect = asyncio.TimeoutError() runner = CliRunner() - resp.sleep = 10 - result = runner.invoke( - app, ["ping", "--timeout", 0, "--host", host, "--port", port] - ) + result = runner.invoke(app, ["ping"]) assert TIMEOUT_ERROR == result.exit_code assert "Error: timeout\n" == result.stdout diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 94865151..3014cccf 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -4,12 +4,7 @@ import pytest from aiospamc.client import Client -from aiospamc.connections import ( - ConnectionManager, - TcpConnectionManager, - Timeout, - UnixConnectionManager, -) +from aiospamc.connections import TcpConnectionManager, Timeout, UnixConnectionManager from aiospamc.exceptions import BadResponse from aiospamc.frontend import ( FrontendClientBuilder, From 4a07cc324413e31c5e67f44438cee8a2f63bd5c4 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 22 Aug 2023 20:36:42 -0400 Subject: [PATCH 13/41] cancel_futures not available in 3.8 --- tests/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b2eb076e..6a33c28e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -429,7 +429,7 @@ async def fake_tcp_server(unused_tcp_port, response_ok): executor.submit(run_fake_tcp_server, is_running, response, unused_tcp_port) is_running.wait() yield response, "localhost", unused_tcp_port - executor.shutdown(wait=False, cancel_futures=True) + executor.shutdown(wait=False) @pytest.fixture @@ -444,7 +444,7 @@ async def fake_tcp_ssl_server(unused_tcp_port, response_ok, server_cert): ) is_running.wait() yield response, "localhost", unused_tcp_port - executor.shutdown(wait=False, cancel_futures=True) + executor.shutdown(wait=False) @pytest.fixture @@ -461,7 +461,7 @@ async def fake_tcp_ssl_client(unused_tcp_port, response_ok, ca, server_cert): ) is_running.wait() yield response, "localhost", unused_tcp_port - executor.shutdown(wait=False, cancel_futures=True) + executor.shutdown(wait=False) @pytest.fixture From ce61cd234f1d6ddf8b2e3892463e8de03bd51b39 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 22 Aug 2023 20:48:17 -0400 Subject: [PATCH 14/41] Added ReadTheDocs theme to dependencies --- docs/conf.py | 2 +- poetry.lock | 37 +++++++++++++++++++++++++++++++++++-- pyproject.toml | 3 +++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3ad18b74..65c8092e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -90,7 +90,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -# html_theme = 'classic' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/poetry.lock b/poetry.lock index 1ba30a81..0a016e80 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -1609,6 +1609,25 @@ files = [ pygments = "*" Sphinx = "*" +[[package]] +name = "sphinx-rtd-theme" +version = "1.3.0" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, +] + +[package.dependencies] +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + [[package]] name = "sphinx-tabs" version = "3.4.1" @@ -1708,6 +1727,20 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -1954,4 +1987,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "56004f450a16bf8da93e80c664beab4998f767dca3643bb3a6e194871e2bcd00" +content-hash = "d1f3ae7a5842ab392dd4b59adacbb977509f07b8759554139f571b0eea815204" diff --git a/pyproject.toml b/pyproject.toml index 0a9b7d76..d7a8be3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,11 +65,13 @@ exclude_lines = [ ] fail_under = 95.0 + [tool.poetry.group.docs.dependencies] sphinx = "^6.2" sphinx-toolbox = "^3.4" reno = "^4.0" + [tool.poetry.group.quality.dependencies] mypy = "^1.2" black = "^23" @@ -94,6 +96,7 @@ exclude = ["changes", "docs", "example", "test", "util"] profile = "black" src_paths = ["aiospamc", "tests"] + [tool.poetry.group.mgmt.dependencies] tbump = "^6.10" From 45f00be01c6a1d9cd9f829106990c41e2d0e98f4 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 07:54:39 -0400 Subject: [PATCH 15/41] Better threaded fake server implementation --- tests/conftest.py | 117 ++++++++++++++++++++++++---------------------- tests/test_cli.py | 6 +-- 2 files changed, 61 insertions(+), 62 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6a33c28e..b242af40 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -369,82 +369,87 @@ class ServerResponse: response: bytes = b"" -def fake_server(resp: ServerResponse, is_done: asyncio.Event): - buffer_size = 1024 - - async def inner(reader: StreamReader, writer: StreamWriter): +class FakeServer: + def __init__(self, loop: asyncio.AbstractEventLoop, resp: ServerResponse): + self.loop = loop + self.resp = resp + self.is_ready = threading.Event() + self.is_done = asyncio.Event() + self.shutdown = asyncio.Event() + + async def server(self, reader: StreamReader, writer: StreamWriter): + buffer_size = 1024 data = b"" while chunk := await reader.read(buffer_size): data += chunk if len(chunk) < buffer_size: break - writer.write(resp.response) + writer.write(self.resp.response) if writer.can_write_eof(): writer.write_eof() await writer.drain() writer.close() await writer.wait_closed() - is_done.set() - - return inner + self.is_done.set() + async def create_server(self, *args, **kwargs): + raise NotImplementedError -def run_fake_tcp_server(is_running, response, port, ssl_context=None): - async def server(): - is_done = asyncio.Event() - server = await asyncio.start_server( - fake_server(response, is_done), "localhost", port, ssl=ssl_context - ) - is_running.set() - await is_done.wait() + async def start_server(self, *args, **kwargs): + server = await self.create_server(*args, **kwargs) + asyncio.get_event_loop().call_soon_threadsafe(self.is_ready.set) + await self.is_done.wait() server.close() await server.wait_closed() - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.run_until_complete(server()) - loop.close() + async def run(self, *args, **kwargs): + server_task = asyncio.create_task(self.start_server(*args, **kwargs)) + await self.shutdown.wait() + server_task.cancel() + try: + await server_task + except asyncio.CancelledError: + pass + def start(self, *args, **kwargs): + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self.run(*args, **kwargs)) -def run_fake_unix_server(is_running, response, path): - async def server(): - is_done = asyncio.Event() - server = await asyncio.start_unix_server(fake_server(response, is_done), path) - is_running.set() - await is_done.wait() - server.close() - await server.wait_closed() - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.run_until_complete(server()) - loop.close() +class FakeTcpServer(FakeServer): + async def create_server(self, port, ssl_context=None): + return await asyncio.start_server( + self.server, "localhost", port, ssl=ssl_context + ) + + +class FakeUnixServer(FakeServer): + async def create_server(self, path): + return await asyncio.start_unix_server(self.server, path) @pytest.fixture async def fake_tcp_server(unused_tcp_port, response_ok): - is_running = threading.Event() - response = ServerResponse(response_ok) - with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - executor.submit(run_fake_tcp_server, is_running, response, unused_tcp_port) - is_running.wait() - yield response, "localhost", unused_tcp_port - executor.shutdown(wait=False) + resp = ServerResponse(response_ok) + fake = FakeTcpServer(asyncio.new_event_loop(), resp) + with concurrent.futures.ThreadPoolExecutor() as executor: + executor.submit(fake.start, unused_tcp_port) + fake.is_ready.wait() + yield resp, "localhost", unused_tcp_port + fake.loop.call_soon_threadsafe(fake.shutdown.set) @pytest.fixture async def fake_tcp_ssl_server(unused_tcp_port, response_ok, server_cert): context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) server_cert.configure_cert(context) - is_running = threading.Event() - response = ServerResponse(response_ok) - with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - executor.submit( - run_fake_tcp_server, is_running, response, unused_tcp_port, context - ) - is_running.wait() - yield response, "localhost", unused_tcp_port - executor.shutdown(wait=False) + resp = ServerResponse(response_ok) + fake = FakeTcpServer(asyncio.new_event_loop(), resp) + with concurrent.futures.ThreadPoolExecutor() as executor: + executor.submit(fake.start, unused_tcp_port, context) + fake.is_ready.wait() + yield resp, "localhost", unused_tcp_port + fake.loop.call_soon_threadsafe(fake.shutdown.set) @pytest.fixture @@ -453,15 +458,13 @@ async def fake_tcp_ssl_client(unused_tcp_port, response_ok, ca, server_cert): context.verify_mode = ssl.CERT_REQUIRED server_cert.configure_cert(context) ca.configure_trust(context) - is_running = threading.Event() - response = ServerResponse(response_ok) - with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - executor.submit( - run_fake_tcp_server, is_running, response, unused_tcp_port, context - ) - is_running.wait() - yield response, "localhost", unused_tcp_port - executor.shutdown(wait=False) + resp = ServerResponse(response_ok) + fake = FakeTcpServer(asyncio.new_event_loop(), resp) + with concurrent.futures.ThreadPoolExecutor() as executor: + executor.submit(fake.start, unused_tcp_port, context) + fake.is_ready.wait() + yield resp, "localhost", unused_tcp_port + fake.loop.call_soon_threadsafe(fake.shutdown.set) @pytest.fixture diff --git a/tests/test_cli.py b/tests/test_cli.py index 9e713894..a929e3c1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -108,7 +108,7 @@ def test_cli_builder_add_ca_client(client_cert_path, client_key_path): assert hasattr(c.connection_manager, "ssl_context") -def test_cli_runner_init_defaults(): +def test_cli_runner_init_defaults(fake_tcp_server): request = Request("PING") c = CommandRunner( Client(ConnectionManagerBuilder().with_tcp("localhost", 783).build()), request @@ -169,10 +169,6 @@ def test_ping_json(mocker, fake_tcp_server, response_pong): assert f"{json.dumps(expected, indent=4)}\n" == result.stdout -def test_ping(mocker, fake_tcp_server): - assert True - - @pytest.mark.parametrize( "args", [ From 82fef1b3bd586b3137a19ac5ef04c6ffa5cdbd52 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 13:07:07 -0400 Subject: [PATCH 16/41] Moved instantation of Events so they're created with the threaded loop --- tests/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b242af40..a2f52972 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -374,8 +374,8 @@ def __init__(self, loop: asyncio.AbstractEventLoop, resp: ServerResponse): self.loop = loop self.resp = resp self.is_ready = threading.Event() - self.is_done = asyncio.Event() - self.shutdown = asyncio.Event() + self.is_done = None + self.shutdown = None async def server(self, reader: StreamReader, writer: StreamWriter): buffer_size = 1024 @@ -413,6 +413,8 @@ async def run(self, *args, **kwargs): def start(self, *args, **kwargs): asyncio.set_event_loop(self.loop) + self.is_done = asyncio.Event() + self.shutdown = asyncio.Event() self.loop.run_until_complete(self.run(*args, **kwargs)) From d664272afd93cf7e303a3f6d223c2bdc1611e9ef Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 14:39:42 -0400 Subject: [PATCH 17/41] Interface for fake server makes more sense --- tests/conftest.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a2f52972..82fa10e5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -397,12 +397,12 @@ async def create_server(self, *args, **kwargs): async def start_server(self, *args, **kwargs): server = await self.create_server(*args, **kwargs) - asyncio.get_event_loop().call_soon_threadsafe(self.is_ready.set) + self.loop.call_soon_threadsafe(self.is_ready.set) await self.is_done.wait() server.close() await server.wait_closed() - async def run(self, *args, **kwargs): + async def start_controller(self, *args, **kwargs): server_task = asyncio.create_task(self.start_server(*args, **kwargs)) await self.shutdown.wait() server_task.cancel() @@ -411,11 +411,11 @@ async def run(self, *args, **kwargs): except asyncio.CancelledError: pass - def start(self, *args, **kwargs): + def run(self, *args, **kwargs): asyncio.set_event_loop(self.loop) self.is_done = asyncio.Event() self.shutdown = asyncio.Event() - self.loop.run_until_complete(self.run(*args, **kwargs)) + self.loop.run_until_complete(self.start_controller(*args, **kwargs)) class FakeTcpServer(FakeServer): @@ -435,7 +435,7 @@ async def fake_tcp_server(unused_tcp_port, response_ok): resp = ServerResponse(response_ok) fake = FakeTcpServer(asyncio.new_event_loop(), resp) with concurrent.futures.ThreadPoolExecutor() as executor: - executor.submit(fake.start, unused_tcp_port) + executor.submit(fake.run, unused_tcp_port) fake.is_ready.wait() yield resp, "localhost", unused_tcp_port fake.loop.call_soon_threadsafe(fake.shutdown.set) @@ -448,7 +448,7 @@ async def fake_tcp_ssl_server(unused_tcp_port, response_ok, server_cert): resp = ServerResponse(response_ok) fake = FakeTcpServer(asyncio.new_event_loop(), resp) with concurrent.futures.ThreadPoolExecutor() as executor: - executor.submit(fake.start, unused_tcp_port, context) + executor.submit(fake.run, unused_tcp_port, context) fake.is_ready.wait() yield resp, "localhost", unused_tcp_port fake.loop.call_soon_threadsafe(fake.shutdown.set) @@ -463,7 +463,7 @@ async def fake_tcp_ssl_client(unused_tcp_port, response_ok, ca, server_cert): resp = ServerResponse(response_ok) fake = FakeTcpServer(asyncio.new_event_loop(), resp) with concurrent.futures.ThreadPoolExecutor() as executor: - executor.submit(fake.start, unused_tcp_port, context) + executor.submit(fake.run, unused_tcp_port, context) fake.is_ready.wait() yield resp, "localhost", unused_tcp_port fake.loop.call_soon_threadsafe(fake.shutdown.set) From 5cb8a4b95e80450d9d29fb0eae38ccabc3bd7363 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 20:58:22 -0400 Subject: [PATCH 18/41] Eliminated some methods in the fake server --- tests/conftest.py | 21 +++++---------------- tests/test_cli.py | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 82fa10e5..d1356ca9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -375,7 +375,6 @@ def __init__(self, loop: asyncio.AbstractEventLoop, resp: ServerResponse): self.resp = resp self.is_ready = threading.Event() self.is_done = None - self.shutdown = None async def server(self, reader: StreamReader, writer: StreamWriter): buffer_size = 1024 @@ -397,25 +396,15 @@ async def create_server(self, *args, **kwargs): async def start_server(self, *args, **kwargs): server = await self.create_server(*args, **kwargs) - self.loop.call_soon_threadsafe(self.is_ready.set) + self.is_ready.set() await self.is_done.wait() server.close() await server.wait_closed() - async def start_controller(self, *args, **kwargs): - server_task = asyncio.create_task(self.start_server(*args, **kwargs)) - await self.shutdown.wait() - server_task.cancel() - try: - await server_task - except asyncio.CancelledError: - pass - def run(self, *args, **kwargs): asyncio.set_event_loop(self.loop) self.is_done = asyncio.Event() - self.shutdown = asyncio.Event() - self.loop.run_until_complete(self.start_controller(*args, **kwargs)) + self.loop.run_until_complete(self.start_server(*args, **kwargs)) class FakeTcpServer(FakeServer): @@ -438,7 +427,7 @@ async def fake_tcp_server(unused_tcp_port, response_ok): executor.submit(fake.run, unused_tcp_port) fake.is_ready.wait() yield resp, "localhost", unused_tcp_port - fake.loop.call_soon_threadsafe(fake.shutdown.set) + fake.loop.call_soon_threadsafe(fake.is_done.set) @pytest.fixture @@ -451,7 +440,7 @@ async def fake_tcp_ssl_server(unused_tcp_port, response_ok, server_cert): executor.submit(fake.run, unused_tcp_port, context) fake.is_ready.wait() yield resp, "localhost", unused_tcp_port - fake.loop.call_soon_threadsafe(fake.shutdown.set) + fake.loop.call_soon_threadsafe(fake.is_done.set) @pytest.fixture @@ -466,7 +455,7 @@ async def fake_tcp_ssl_client(unused_tcp_port, response_ok, ca, server_cert): executor.submit(fake.run, unused_tcp_port, context) fake.is_ready.wait() yield resp, "localhost", unused_tcp_port - fake.loop.call_soon_threadsafe(fake.shutdown.set) + fake.loop.call_soon_threadsafe(fake.is_done.set) @pytest.fixture diff --git a/tests/test_cli.py b/tests/test_cli.py index a929e3c1..907337e0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -108,7 +108,7 @@ def test_cli_builder_add_ca_client(client_cert_path, client_key_path): assert hasattr(c.connection_manager, "ssl_context") -def test_cli_runner_init_defaults(fake_tcp_server): +def test_cli_runner_init_defaults(): request = Request("PING") c = CommandRunner( Client(ConnectionManagerBuilder().with_tcp("localhost", 783).build()), request From 641dbfc63f8fda4db0c5a2c12f014c686afaa543 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 21:34:34 -0400 Subject: [PATCH 19/41] Added client certificate CLI options to documentation --- aiospamc/cli.py | 20 ++++++--- docs/cli.rst | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/aiospamc/cli.py b/aiospamc/cli.py index dcf2cb2b..e5506645 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -386,7 +386,9 @@ def check( ssl: Annotated[ bool, typer.Option(help="Use SSL to communicate with the daemon.") ] = False, - user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), + user: Annotated[ + str, typer.Option(metavar="USERNAME", help="User to send the request as.") + ] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, @@ -473,7 +475,9 @@ def learn( ssl: Annotated[ bool, typer.Option(help="Use SSL to communicate with the daemon.") ] = False, - user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), + user: Annotated[ + str, typer.Option(metavar="USERNAME", help="User to send the request as.") + ] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, @@ -560,7 +564,9 @@ def forget( ssl: Annotated[ bool, typer.Option(help="Use SSL to communicate with the daemon.") ] = False, - user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), + user: Annotated[ + str, typer.Option(metavar="USERNAME", help="User to send the request as.") + ] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, @@ -646,7 +652,9 @@ def report( ssl: Annotated[ bool, typer.Option(help="Use SSL to communicate with the daemon.") ] = False, - user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), + user: Annotated[ + str, typer.Option(metavar="USERNAME", help="User to send the request as.") + ] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, @@ -733,7 +741,9 @@ def revoke( ssl: Annotated[ bool, typer.Option(help="Use SSL to communicate with the daemon.") ] = False, - user: Annotated[str, typer.Option(help="User to send the request as.")] = getuser(), + user: Annotated[ + str, typer.Option(metavar="USERNAME", help="User to send the request as.") + ] = getuser(), timeout: Annotated[ float, typer.Option(metavar="SECONDS", help="Timeout in seconds") ] = 10, diff --git a/docs/cli.rst b/docs/cli.rst index 24af0e54..78632df8 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -52,6 +52,22 @@ Commands |ssl_description| + .. option:: --ca-cert PATH + + |ca_cert_description| + + .. option:: --client-cert PATH + + |client_cert_description| + + .. option:: --client-key PATH + + |client_key_description| + + .. option:: --key-password + + |key_password_description| + .. option:: --user USERNAME |user_description| @@ -84,6 +100,22 @@ Commands |ssl_description| + .. option:: --ca-cert PATH + + |ca_cert_description| + + .. option:: --client-cert PATH + + |client_cert_description| + + .. option:: --client-key PATH + + |client_key_description| + + .. option:: --key-password + + |key_password_description| + .. option:: --user USERNAME |user_description| @@ -116,6 +148,22 @@ Commands |ssl_description| + .. option:: --ca-cert PATH + + |ca_cert_description| + + .. option:: --client-cert PATH + + |client_cert_description| + + .. option:: --client-key PATH + + |client_key_description| + + .. option:: --key-password + + |key_password_description| + .. option:: --user USERNAME |user_description| @@ -150,6 +198,22 @@ Commands |ssl_description| + .. option:: --ca-cert PATH + + |ca_cert_description| + + .. option:: --client-cert PATH + + |client_cert_description| + + .. option:: --client-key PATH + + |client_key_description| + + .. option:: --key-password + + |key_password_description| + .. option:: --user USERNAME |user_description| @@ -184,6 +248,22 @@ Commands |ssl_description| + .. option:: --ca-cert PATH + + |ca_cert_description| + + .. option:: --client-cert PATH + + |client_cert_description| + + .. option:: --client-key PATH + + |client_key_description| + + .. option:: --key-password + + |key_password_description| + .. option:: --user USERNAME |user_description| @@ -218,6 +298,22 @@ Commands |ssl_description| + .. option:: --ca-cert PATH + + |ca_cert_description| + + .. option:: --client-cert PATH + + |client_cert_description| + + .. option:: --client-key PATH + + |client_key_description| + + .. option:: --key-password + + |key_password_description| + .. option:: --user USERNAME |user_description| @@ -239,6 +335,16 @@ Commands .. |ssl_description| replace:: Enables or disables SSL when using a TCP connection. Will use the system's root certificates by default. +.. |ca_cert_description| replace:: Path to certificate authority file or path. Overrides the + default path. + +.. |client_cert_description| replace:: Filename for the client certificate. + +.. |client_key_description| replace:: Filename for the client certificate key. Specify this if + this isn't included in the client certificate. + +.. |key_password_description| replace:: Password for the client certificate key if encrypted. + .. |user_description| replace:: User to send the request as. .. |timeout_description| replace:: Set the connection timeout. Default is 10 seconds. From b3de21628b80c51ae6c69e8535417fc2377fd03f Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 21:56:22 -0400 Subject: [PATCH 20/41] Added Codecov upload step --- .github/workflows/unit-tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index f249b0a8..aebc0a0a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -38,6 +38,12 @@ jobs: with: name: test-results-${{ matrix.os }}-${{ matrix.python }} path: output/** + - name: Upload to Codecov.io + if: success() || failure() + uses: codecov/codecov-action@v3 + with: + files: output/coverage.xml + flags: unittests status: if: ${{ always() }} runs-on: ubuntu-latest From 125c00a0bade24909b20913aa561cafe5dbf7420 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 23 Aug 2023 22:00:50 -0400 Subject: [PATCH 21/41] Integration tests contribute to coverage --- .github/workflows/integration-tests.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index bd251152..9c1de5b0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -40,14 +40,24 @@ jobs: - run: sudo sa-update - run: spamd --version - name: Run integration tests - run: | - poetry run pytest -m integration --junit-xml="output/integration-tests.xml" + run: > + poetry run pytest -m integration + --cov + --cov-report="xml:output/coverage.xml" + --cov-fail-under=0 + --junit-xml="output/integration-tests.xml" - name: Publish test results if: success() || failure() uses: actions/upload-artifact@v3 with: name: test-results path: output/** + - name: Upload to Codecov.io + if: success() || failure() + uses: codecov/codecov-action@v3 + with: + files: output/coverage.xml + flags: integrationtests status: if: ${{ always() }} runs-on: ubuntu-latest From c506452b595be8df5777cbd4e3f2440ab7fe77e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:09:03 +0000 Subject: [PATCH 22/41] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.7.0 โ†’ 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) - [github.com/python-poetry/poetry: 1.5.0 โ†’ 1.6.0](https://github.com/python-poetry/poetry/compare/1.5.0...1.6.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c65e50c8..2640b04c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black - repo: https://github.com/PyCQA/isort @@ -15,6 +15,6 @@ repos: hooks: - id: isort - repo: https://github.com/python-poetry/poetry - rev: 1.5.0 + rev: 1.6.0 hooks: - id: poetry-check From 32b14b14d9aa31b88a51fc9b19ce3d3a22116487 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Thu, 5 Oct 2023 10:14:29 -0400 Subject: [PATCH 23/41] Simplified Read The Docs configuration --- readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs.yml b/readthedocs.yml index ef0713dd..e9085eca 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -5,7 +5,7 @@ sphinx: build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3" jobs: post_checkout: - git fetch --unshallow || true From 89e04676e92bdcea9b014eee0dd5b83e2261dc69 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Thu, 5 Oct 2023 11:10:04 -0400 Subject: [PATCH 24/41] Added support for Python 3.12 --- poetry.lock | 939 +++++++++--------- pyproject.toml | 5 +- .../422-python-3.12-7b714da4ca0d8564.yaml | 4 + 3 files changed, 460 insertions(+), 488 deletions(-) create mode 100644 releasenotes/notes/422-python-3.12-7b714da4ca0d8564.yaml diff --git a/poetry.lock b/poetry.lock index 0a016e80..36769611 100644 --- a/poetry.lock +++ b/poetry.lock @@ -113,33 +113,33 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.7.0" +version = "23.9.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, ] [package.dependencies] @@ -149,7 +149,7 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -191,75 +191,63 @@ files = [ [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {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"}, ] [package.dependencies] @@ -278,86 +266,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] @@ -414,63 +417,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.dependencies] @@ -481,34 +484,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -608,67 +611,67 @@ dates = ["pytz (>=2019.1)"] [[package]] name = "dulwich" -version = "0.21.5" +version = "0.21.6" description = "Python Git Library" optional = false python-versions = ">=3.7" files = [ - {file = "dulwich-0.21.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8864719bc176cdd27847332a2059127e2f7bab7db2ff99a999873cb7fff54116"}, - {file = "dulwich-0.21.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3800cdc17d144c1f7e114972293bd6c46688f5bcc2c9228ed0537ded72394082"}, - {file = "dulwich-0.21.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2f676bfed8146966fe934ee734969d7d81548fbd250a8308582973670a9dab1"}, - {file = "dulwich-0.21.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db330fb59fe3b9d253bdf0e49a521739db83689520c4921ab1c5242aaf77b82"}, - {file = "dulwich-0.21.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e8f6d4f4f4d01dd1d3c968e486d4cd77f96f772da7265941bc506de0944ddb9"}, - {file = "dulwich-0.21.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1cc0c9ba19ac1b2372598802bc9201a9c45e5d6f1f7a80ec40deeb10acc4e9ae"}, - {file = "dulwich-0.21.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61e10242b5a7a82faa8996b2c76239cfb633620b02cdd2946e8af6e7eb31d651"}, - {file = "dulwich-0.21.5-cp310-cp310-win32.whl", hash = "sha256:7f357639b56146a396f48e5e0bc9bbaca3d6d51c8340bd825299272b588fff5f"}, - {file = "dulwich-0.21.5-cp310-cp310-win_amd64.whl", hash = "sha256:891d5c73e2b66d05dbb502e44f027dc0dbbd8f6198bc90dae348152e69d0befc"}, - {file = "dulwich-0.21.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45d6198e804b539708b73a003419e48fb42ff2c3c6dd93f63f3b134dff6dd259"}, - {file = "dulwich-0.21.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c2a565d4e704d7f784cdf9637097141f6d47129c8fffc2fac699d57cb075a169"}, - {file = "dulwich-0.21.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:823091d6b6a1ea07dc4839c9752198fb39193213d103ac189c7669736be2eaff"}, - {file = "dulwich-0.21.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2c9931b657f2206abec0964ec2355ee2c1e04d05f8864e823ffa23c548c4548"}, - {file = "dulwich-0.21.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dc358c2ee727322a09b7c6da43d47a1026049dbd3ad8d612eddca1f9074b298"}, - {file = "dulwich-0.21.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6155ab7388ee01c670f7c5d8003d4e133eebebc7085a856c007989f0ba921b36"}, - {file = "dulwich-0.21.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a605e10d72f90a39ea2e634fbfd80f866fc4df29a02ea6db52ae92e5fd4a2003"}, - {file = "dulwich-0.21.5-cp311-cp311-win32.whl", hash = "sha256:daa607370722c3dce99a0022397c141caefb5ed32032a4f72506f4817ea6405b"}, - {file = "dulwich-0.21.5-cp311-cp311-win_amd64.whl", hash = "sha256:5e56b2c1911c344527edb2bf1a4356e2fb7e086b1ba309666e1e5c2224cdca8a"}, - {file = "dulwich-0.21.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:85d3401d08b1ec78c7d58ae987c4bb7b768a438f3daa74aeb8372bebc7fb16fa"}, - {file = "dulwich-0.21.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90479608e49db93d8c9e4323bc0ec5496678b535446e29d8fd67dc5bbb5d51bf"}, - {file = "dulwich-0.21.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9a6bf99f57bcac4c77fc60a58f1b322c91cc4d8c65dc341f76bf402622f89cb"}, - {file = "dulwich-0.21.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3e68b162af2aae995355e7920f89d50d72b53d56021e5ac0a546d493b17cbf7e"}, - {file = "dulwich-0.21.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0ab86d6d42e385bf3438e70f3c9b16de68018bd88929379e3484c0ef7990bd3c"}, - {file = "dulwich-0.21.5-cp37-cp37m-win32.whl", hash = "sha256:f2eeca6d61366cf5ee8aef45bed4245a67d4c0f0d731dc2383eabb80fa695683"}, - {file = "dulwich-0.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:1b20a3656b48c941d49c536824e1e5278a695560e8de1a83b53a630143c4552e"}, - {file = "dulwich-0.21.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3932b5e17503b265a85f1eda77ede647681c3bab53bc9572955b6b282abd26ea"}, - {file = "dulwich-0.21.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6616132d219234580de88ceb85dd51480dc43b1bdc05887214b8dd9cfd4a9d40"}, - {file = "dulwich-0.21.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaf6c7fb6b13495c19c9aace88821c2ade3c8c55b4e216cd7cc55d3e3807d7fa"}, - {file = "dulwich-0.21.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be12a46f73023970125808a4a78f610c055373096c1ecea3280edee41613eba8"}, - {file = "dulwich-0.21.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baecef0d8b9199822c7912876a03a1af17833f6c0d461efb62decebd45897e49"}, - {file = "dulwich-0.21.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:82f632afb9c7c341a875d46aaa3e6c5e586c7a64ce36c9544fa400f7e4f29754"}, - {file = "dulwich-0.21.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82cdf482f8f51fcc965ffad66180b54a9abaea9b1e985a32e1acbfedf6e0e363"}, - {file = "dulwich-0.21.5-cp38-cp38-win32.whl", hash = "sha256:c8ded43dc0bd2e65420eb01e778034be5ca7f72e397a839167eda7dcb87c4248"}, - {file = "dulwich-0.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:2aba0fdad2a19bd5bb3aad6882580cb33359c67b48412ccd4cfccd932012b35e"}, - {file = "dulwich-0.21.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd4ad079758514375f11469e081723ba8831ce4eaa1a64b41f06a3a866d5ac34"}, - {file = "dulwich-0.21.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fe62685bf356bfb4d0738f84a3fcf0d1fc9e11fee152e488a20b8c66a52429e"}, - {file = "dulwich-0.21.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aae448da7d80306dda4fc46292fed7efaa466294571ab3448be16714305076f1"}, - {file = "dulwich-0.21.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b24cb1fad0525dba4872e9381bc576ea2a6dcdf06b0ed98f8e953e3b1d719b89"}, - {file = "dulwich-0.21.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e39b7c2c9bda6acae83b25054650a8bb7e373e886e2334721d384e1479bf04b"}, - {file = "dulwich-0.21.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26456dba39d1209fca17187db06967130e27eeecad2b3c2bbbe63467b0bf09d6"}, - {file = "dulwich-0.21.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:281310644e02e3aa6d76bcaffe2063b9031213c4916b5f1a6e68c25bdecfaba4"}, - {file = "dulwich-0.21.5-cp39-cp39-win32.whl", hash = "sha256:4814ca3209dabe0fe7719e9545fbdad7f8bb250c5a225964fe2a31069940c4cf"}, - {file = "dulwich-0.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:c922a4573267486be0ef85216f2da103fb38075b8465dc0e90457843884e4860"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e52b20c4368171b7d32bd3ab0f1d2402e76ad4f2ea915ff9aa73bc9fa2b54d6d"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeb736d777ee21f2117a90fc453ee181aa7eedb9e255b5ef07c51733f3fe5cb6"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e8a79c1ed7166f32ad21974fa98d11bf6fd74e94a47e754c777c320e01257c6"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b943517e30bd651fbc275a892bb96774f3893d95fe5a4dedd84496a98eaaa8ab"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32493a456358a3a6c15bbda07106fc3d4cc50834ee18bc7717968d18be59b223"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa44b812d978fc22a04531f5090c3c369d5facd03fa6e0501d460a661800c7f"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f46bcb6777e5f9f4af24a2bd029e88b77316269d24ce66be590e546a0d8f7b7"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a917fd3b4493db3716da2260f16f6b18f68d46fbe491d851d154fc0c2d984ae4"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:684c52cff867d10c75a7238151ca307582b3d251bbcd6db9e9cffbc998ef804e"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9019189d7a8f7394df6a22cd5b484238c5776e42282ad5d6d6c626b4c5f43597"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:494024f74c2eef9988adb4352b3651ac1b6c0466176ec62b69d3d3672167ba68"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f9b6ac1b1c67fc6083c42b7b6cd3b211292c8a6517216c733caf23e8b103ab6d"}, - {file = "dulwich-0.21.5.tar.gz", hash = "sha256:70955e4e249ddda6e34a4636b90f74e931e558f993b17c52570fa6144b993103"}, + {file = "dulwich-0.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7f89bee4c97372e8aaf8ffaf5899f1bcd5184b5306d7eaf68738c1101ceba10e"}, + {file = "dulwich-0.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:847bb52562a211b596453a602e75739350c86d7edb846b5b1c46896a5c86b9bb"}, + {file = "dulwich-0.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e09d0b4e985b371aa6728773781b19298d361a00772e20f98522868cf7edc6f"}, + {file = "dulwich-0.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfb50b3915e223a97f50fbac0dbc298d5fffeaac004eeeb3d552c57fe38416f"}, + {file = "dulwich-0.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a64eca1601e79c16df78afe08da9ac9497b934cbc5765990ca7d89a4b87453d9"}, + {file = "dulwich-0.21.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fedd924763a5d640348db43a267a394aa80d551228ad45708e0b0cc2130bb62"}, + {file = "dulwich-0.21.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edc21c3784dd9d9b85abd9fe53f81a884e2cdcc4e5e09ada17287420d64cfd46"}, + {file = "dulwich-0.21.6-cp310-cp310-win32.whl", hash = "sha256:daa3584beabfcf0da76df57535a23c80ff6d8ccde6ddbd23bdc79d317a0e20a7"}, + {file = "dulwich-0.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:40623cc39a3f1634663d22d87f86e2e406cc8ff17ae7a3edc7fcf963c288992f"}, + {file = "dulwich-0.21.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ed878553f0b76facbb620b455fafa0943162fe8e386920717781e490444efa"}, + {file = "dulwich-0.21.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a89b19f4960e759915dbc23a4dd0abc067b55d8d65e9df50961b73091b87b81a"}, + {file = "dulwich-0.21.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28acbd08d6b38720d99cc01da9dd307a2e0585e00436c95bcac6357b9a9a6f76"}, + {file = "dulwich-0.21.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2f2683e0598f7c7071ef08a0822f062d8744549a0d45f2c156741033b7e3d7d"}, + {file = "dulwich-0.21.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54342cf96fe8a44648505c65f23d18889595762003a168d67d7263df66143bd2"}, + {file = "dulwich-0.21.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a3fc071e5b14f164191286f7ffc02f60fe8b439d01fad0832697cc08c2237dd"}, + {file = "dulwich-0.21.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32d7acfe3fe2ce4502446d8f7a5ab34cfd24c9ff8961e60337638410906a8fbb"}, + {file = "dulwich-0.21.6-cp311-cp311-win32.whl", hash = "sha256:5e58171a5d70f7910f73d25ff82a058edff09a4c1c3bd1de0dc6b1fbc9a42c3e"}, + {file = "dulwich-0.21.6-cp311-cp311-win_amd64.whl", hash = "sha256:ceabe8f96edfb9183034a860f5dc77586700b517457032867b64a03c44e5cf96"}, + {file = "dulwich-0.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4fdc2f081bc3e9e120079c2cea4be213e3f127335aca7c0ab0c19fe791270caa"}, + {file = "dulwich-0.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fe957564108f74325d0d042d85e0c67ef470921ca92b6e7d330c7c49a3b9c1d"}, + {file = "dulwich-0.21.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2912c8a845c8ccbc79d068a89db7172e355adeb84eb31f062cd3a406d528b30"}, + {file = "dulwich-0.21.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:81e237a6b1b20c79ef62ca19a8fb231f5519bab874b9a1c2acf9c05edcabd600"}, + {file = "dulwich-0.21.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:513d045e74307eeb31592255c38f37042c9aa68ce845a167943018ab5138b0e3"}, + {file = "dulwich-0.21.6-cp37-cp37m-win32.whl", hash = "sha256:e1ac882afa890ef993b8502647e6c6d2b3977ce56e3fe80058ce64607cbc7107"}, + {file = "dulwich-0.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:5d2ccf3d355850674f75655154a6519bf1f1664176c670109fa7041019b286f9"}, + {file = "dulwich-0.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:28c9724a167c84a83fc6238e0781f4702b5fe8c53ede31604525fb1a9d1833f4"}, + {file = "dulwich-0.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c816be529680659b6a19798287b4ec6de49040f58160d40b1b2934fd6c28e93f"}, + {file = "dulwich-0.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0545f0fa9444a0eb84977d08e302e3f55fd7c34a0466ec28bedc3c839b2fc1f"}, + {file = "dulwich-0.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b1682e8e826471ea3c22b8521435e93799e3db8ad05dd3c8f9b1aaacfa78147"}, + {file = "dulwich-0.21.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ad45928a65f39ea0f451f9989b7aaedba9893d48c3189b544a70c6a1043f71"}, + {file = "dulwich-0.21.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1c9e55233f19cd19c484f607cd90ab578ac50ebfef607f77e3b35c2b6049470"}, + {file = "dulwich-0.21.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:18697b58e0fc5972de68b529b08ac9ddda3f39af27bcf3f6999635ed3da7ef68"}, + {file = "dulwich-0.21.6-cp38-cp38-win32.whl", hash = "sha256:22798e9ba59e32b8faff5d9067e2b5a308f6b0fba9b1e1e928571ad278e7b36c"}, + {file = "dulwich-0.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:6c91e1ed20d3d9a6aaaed9e75adae37272b3fcbcc72bab1eb09574806da88563"}, + {file = "dulwich-0.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8b84450766a3b151c3676fec3e3ed76304e52a84d5d69ade0f34fff2782c1b41"}, + {file = "dulwich-0.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3da632648ee27b64bb5b285a3a94fddf297a596891cca12ac0df43c4f59448f"}, + {file = "dulwich-0.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cef50c0a19f322b7150248b8fa0862ce1652dec657e340c4020573721e85f215"}, + {file = "dulwich-0.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ac20dfcfd6057efb8499158d23f2c059f933aefa381e192100e6d8bc25d562"}, + {file = "dulwich-0.21.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d10aa50c0a9a6dd495990c639358e3a3bbff39e17ff302179be6e93b573da7"}, + {file = "dulwich-0.21.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9b52a08d49731375662936d05a12c4a64a6fe0ce257111f62638e475fb5d26d"}, + {file = "dulwich-0.21.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed2f1f638b9adfba862719693b371ffe5d58e94d552ace9a23dea0fb0db6f468"}, + {file = "dulwich-0.21.6-cp39-cp39-win32.whl", hash = "sha256:bf90f2f9328a82778cf85ab696e4a7926918c3f315c75fc432ba31346bfa89b7"}, + {file = "dulwich-0.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e0dee3840c3c72e1d60c8f87a7a715d8eac023b9e1b80199d97790f7a1c60d9c"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32d3a35caad6879d04711b358b861142440a543f5f4e02df67b13cbcd57f84a6"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04df87098053b7767b46fc04b7943d75443f91c73560ca50157cdc22e27a5d3"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e07f145c7b0d82a9f77d157f493a61900e913d1c1f8b1f40d07d919ffb0929a4"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:008ff08629ab16d3638a9f36cfc6f5bd74b4d594657f2dc1583d8d3201794571"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf469cd5076623c2aad69d01ce9d5392fcb38a5faef91abe1501be733453e37d"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6592ef2d16ac61a27022647cf64a048f5be6e0a6ab2ebc7322bfbe24fb2b971b"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99577b2b37f64bc87280079245fb2963494c345d7db355173ecec7ab3d64b949"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d7cd9fb896c65e4c28cb9332f2be192817805978dd8dc299681c4fe83c631158"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9002094198e57e88fe77412d3aa64dd05978046ae725a16123ba621a7704628"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b6f8a16f32190aa88c37ef013858b3e01964774bc983900bd0d74ecb6576e6"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee8aba4dec4d0a52737a8a141f3456229c87dcfd7961f8115786a27b6ebefed"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a780e2a0ff208c4f218e72eff8d13f9aff485ff9a6f3066c22abe4ec8cec7dcd"}, + {file = "dulwich-0.21.6.tar.gz", hash = "sha256:30fbe87e8b51f3813c131e2841c86d007434d160bd16db586b40d47f31dd05b0"}, ] [package.dependencies] @@ -696,18 +699,19 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.2" +version = "3.12.4" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "html5lib" @@ -732,13 +736,13 @@ lxml = ["lxml"] [[package]] name = "identify" -version = "2.5.26" +version = "2.5.30" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, - {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, + {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, + {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, ] [package.extras] @@ -857,13 +861,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "loguru" -version = "0.7.0" +version = "0.7.2" description = "Python logging made (stupidly) simple" optional = false python-versions = ">=3.5" files = [ - {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, - {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, ] [package.dependencies] @@ -871,7 +875,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] +dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] [[package]] name = "markupsafe" @@ -900,6 +904,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -932,92 +946,69 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] -[[package]] -name = "mock" -version = "5.1.0" -description = "Rolling backport of unittest.mock for all Pythons" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, - {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, -] - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest", "pytest-cov"] - [[package]] name = "msgpack" -version = "1.0.5" +version = "1.0.7" description = "MessagePack serializer" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, + {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, + {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, + {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, + {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, + {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, + {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, + {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, + {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, + {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, + {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, + {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, ] [[package]] @@ -1108,13 +1099,13 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -1156,13 +1147,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -1171,13 +1162,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.3.3" +version = "3.4.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, + {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, ] [package.dependencies] @@ -1225,13 +1216,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -1300,13 +1291,13 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] @@ -1321,6 +1312,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1328,8 +1320,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1346,6 +1345,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1353,6 +1353,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1402,13 +1403,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruamel-yaml" -version = "0.17.32" +version = "0.17.33" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" files = [ - {file = "ruamel.yaml-0.17.32-py3-none-any.whl", hash = "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447"}, - {file = "ruamel.yaml-0.17.32.tar.gz", hash = "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2"}, + {file = "ruamel.yaml-0.17.33-py3-none-any.whl", hash = "sha256:2080c7a02b8a30fb3c06727cdf3e254a64055eedf3aa2d17c2b669639c04971b"}, + {file = "ruamel.yaml-0.17.33.tar.gz", hash = "sha256:5c56aa0bff2afceaa93bffbfc78b450b7dc1e01d5edb80b3a570695286ae62b1"}, ] [package.dependencies] @@ -1432,7 +1433,8 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -1479,19 +1481,19 @@ contextlib2 = ">=0.5.5" [[package]] name = "setuptools" -version = "68.1.2" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1517,13 +1519,13 @@ files = [ [[package]] name = "soupsieve" -version = "2.4.1" +version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] @@ -1609,25 +1611,6 @@ files = [ pygments = "*" Sphinx = "*" -[[package]] -name = "sphinx-rtd-theme" -version = "1.3.0" -description = "Read the Docs theme for Sphinx" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, - {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, -] - -[package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<8" -sphinxcontrib-jquery = ">=4,<5" - -[package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] - [[package]] name = "sphinx-tabs" version = "3.4.1" @@ -1727,20 +1710,6 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -1801,13 +1770,13 @@ widechars = ["wcwidth"] [[package]] name = "tbump" -version = "6.10.0" +version = "6.11.0" description = "Bump software releases" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "tbump-6.10.0-py3-none-any.whl", hash = "sha256:170a4395d167daee357cb96af5e874119c470feaba9f605e73f3426e768c2542"}, - {file = "tbump-6.10.0.tar.gz", hash = "sha256:9ebf5d69bc92ca8be1afb13a80f51e374526cb9988f4c3b167036a9e8a10a684"}, + {file = "tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526"}, + {file = "tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870"}, ] [package.dependencies] @@ -1851,18 +1820,18 @@ files = [ [[package]] name = "trustme" -version = "0.9.0" +version = "1.1.0" description = "#1 quality TLS certs while you wait, for the discerning tester" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "trustme-0.9.0-py2.py3-none-any.whl", hash = "sha256:a6e53039cc43e70548ebd9a42ec1af5cba803a16d14321cd96352d2b4e010e04"}, - {file = "trustme-0.9.0.tar.gz", hash = "sha256:5e07b23d70ceed64f3bb36ae4b9abc52354c16c98d45ab037bee2b5fbffe586c"}, + {file = "trustme-1.1.0-py3-none-any.whl", hash = "sha256:ce105b68fb9f6d7ac7a9ee6e95bb2347a22ce4d3be78ef9a6494d5ef890e1e16"}, + {file = "trustme-1.1.0.tar.gz", hash = "sha256:5375ad7fb427074bec956592e0d4ee2a4cf4da68934e1ba4bcf4217126bc45e6"}, ] [package.dependencies] -cryptography = "*" -idna = "*" +cryptography = ">=3.1" +idna = ">=2.0" [[package]] name = "typer" @@ -1887,35 +1856,35 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "unidecode" -version = "1.3.6" +version = "1.3.7" description = "ASCII transliterations of Unicode text" optional = false python-versions = ">=3.5" files = [ - {file = "Unidecode-1.3.6-py3-none-any.whl", hash = "sha256:547d7c479e4f377b430dd91ac1275d593308dce0fc464fb2ab7d41f82ec653be"}, - {file = "Unidecode-1.3.6.tar.gz", hash = "sha256:fed09cf0be8cf415b391642c2a5addfc72194407caee4f98719e40ec2a72b830"}, + {file = "Unidecode-1.3.7-py3-none-any.whl", hash = "sha256:663a537f506834ed836af26a81b210d90cbde044c47bfbdc0fbbc9f94c86a6e4"}, + {file = "Unidecode-1.3.7.tar.gz", hash = "sha256:3c90b4662aa0de0cb591884b934ead8d2225f1800d8da675a7750cbc3bd94610"}, ] [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.5" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.5-py3-none-any.whl", hash = "sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e"}, + {file = "urllib3-2.0.5.tar.gz", hash = "sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594"}, ] [package.extras] @@ -1926,13 +1895,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.3" +version = "20.24.5" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, - {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] @@ -1941,7 +1910,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -1971,20 +1940,20 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "zipp" -version = "3.16.2" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d1f3ae7a5842ab392dd4b59adacbb977509f07b8759554139f571b0eea815204" +content-hash = "db037788d2682758d9ef4d18dfa0357eececfe04ae7d8b18a43d25ddbfc61609" diff --git a/pyproject.toml b/pyproject.toml index d7a8be3d..f397e513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: MIT License', @@ -39,9 +40,8 @@ pytest = "^7.1" pytest-cov = "^4.0" pytest-asyncio = "^0.21" pytest-mock = "^3.10" -mock = "^5.0" coverage = {extras = ["toml"], version = "^7.2"} -trustme = "^0.9.0" +trustme = "^1.1" [tool.pytest.ini_options] minversion = "6.0" @@ -50,7 +50,6 @@ testpaths = [ ] markers = ["integration: spawn an instance of spamd and test against it"] addopts = "-m \"not integration\"" -mock_use_standalone_module = true asyncio_mode = "auto" [tool.coverage.run] diff --git a/releasenotes/notes/422-python-3.12-7b714da4ca0d8564.yaml b/releasenotes/notes/422-python-3.12-7b714da4ca0d8564.yaml new file mode 100644 index 00000000..cf525625 --- /dev/null +++ b/releasenotes/notes/422-python-3.12-7b714da4ca0d8564.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added support for Python 3.12 :github:issue:`422` From 3c242947107a9f4c4085e4a97eba12263e0ba0a1 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Thu, 5 Oct 2023 11:23:49 -0400 Subject: [PATCH 25/41] Added Sphinx RTD theme --- poetry.lock | 35 ++++++++++++++++++++++++++++++++++- pyproject.toml | 4 +--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 36769611..50f93bd9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1611,6 +1611,25 @@ files = [ pygments = "*" Sphinx = "*" +[[package]] +name = "sphinx-rtd-theme" +version = "1.3.0" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, +] + +[package.dependencies] +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + [[package]] name = "sphinx-tabs" version = "3.4.1" @@ -1710,6 +1729,20 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -1956,4 +1989,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "db037788d2682758d9ef4d18dfa0357eececfe04ae7d8b18a43d25ddbfc61609" +content-hash = "8dc6251fe7dad57bf894b9126284e6140d73781b545cd2cadb422b0f034c3158" diff --git a/pyproject.toml b/pyproject.toml index f397e513..988466b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,13 +64,12 @@ exclude_lines = [ ] fail_under = 95.0 - [tool.poetry.group.docs.dependencies] sphinx = "^6.2" +sphinx-rtd-theme = "^1.3" sphinx-toolbox = "^3.4" reno = "^4.0" - [tool.poetry.group.quality.dependencies] mypy = "^1.2" black = "^23" @@ -95,7 +94,6 @@ exclude = ["changes", "docs", "example", "test", "util"] profile = "black" src_paths = ["aiospamc", "tests"] - [tool.poetry.group.mgmt.dependencies] tbump = "^6.10" From 340993f21dc99b81e93fc0c5ec8b09327b42d296 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Thu, 5 Oct 2023 12:01:26 -0400 Subject: [PATCH 26/41] Updated GitHub workflows to use new Python versions --- .github/workflows/unit-test-pre.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-test-pre.yml b/.github/workflows/unit-test-pre.yml index d2804320..35701a16 100644 --- a/.github/workflows/unit-test-pre.yml +++ b/.github/workflows/unit-test-pre.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.12-dev' + python-version: '3.13-dev' cache: 'poetry' - name: Install dependencies run: poetry install diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index aebc0a0a..24dcd6ff 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: ['3.8', '3.9', '3.10', '3.11'] + python: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - name: Install Poetry From bec171fc356492f829b1cb959abcda89c7749eac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:36:49 +0000 Subject: [PATCH 27/41] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 โ†’ v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2640b04c..0d3a4cfb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-merge-conflict - id: debug-statements From cf6bb6e3bed35279f3cc46facd7066d16547a84a Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Mon, 9 Oct 2023 19:51:35 -0400 Subject: [PATCH 28/41] Better handling of client private key --- aiospamc/cli.py | 6 ++-- aiospamc/connections.py | 10 ++++-- aiospamc/frontend.py | 20 +++++++++-- tests/conftest.py | 38 +++++++++++++++++++-- tests/test_cli.py | 14 ++++++-- tests/test_connections.py | 16 ++++++--- tests/test_frontend.py | 43 ++++++++++++++++++++++-- util/create_certs.py | 71 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 198 insertions(+), 20 deletions(-) create mode 100755 util/create_certs.py diff --git a/aiospamc/cli.py b/aiospamc/cli.py index e5506645..8f468142 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -5,7 +5,7 @@ import ssl import sys from enum import Enum -from getpass import getuser +from getpass import getpass, getuser from io import BufferedReader from pathlib import Path from typing import Optional @@ -176,7 +176,9 @@ def add_client_cert( if self._ssl is False: self._ssl = True self.add_verify(True) - self._ssl_builder.add_client(cert, key, password) + self._ssl_builder.add_client( + cert, key, lambda: password or getpass("Private key password") + ) return self diff --git a/aiospamc/connections.py b/aiospamc/connections.py index 047f3ef8..dec90d0f 100644 --- a/aiospamc/connections.py +++ b/aiospamc/connections.py @@ -5,8 +5,9 @@ import asyncio import ssl from enum import Enum, auto +from getpass import getpass from pathlib import Path -from typing import Any, Optional, Tuple, Union +from typing import Any, Callable, Optional, Tuple, Union import certifi import loguru @@ -499,13 +500,16 @@ def add_default_ca(self) -> SSLContextBuilder: return self def add_client( - self, file: Path, key: Optional[Path] = None, password: Optional[str] = None + self, + file: Path, + key: Optional[Path] = None, + password: Optional[Callable[[], Union[str, bytes, bytearray]]] = None, ) -> SSLContextBuilder: """Add client certificate. :param file: Path to the client certificate. :param key: Path to the key. - :param password: Password of the key. + :param password: Callable that returns the password, if any. """ self._context.load_cert_chain(file, key, password) diff --git a/aiospamc/frontend.py b/aiospamc/frontend.py index a2e934ce..bfb9ed38 100644 --- a/aiospamc/frontend.py +++ b/aiospamc/frontend.py @@ -3,6 +3,7 @@ from __future__ import annotations import ssl +from functools import partial from pathlib import Path from typing import Any, Dict, Optional, SupportsBytes, Tuple, Union, cast @@ -118,16 +119,29 @@ def add_client_cert( if not self._ssl: self.add_verify(True) + def pwd_check(password: Optional[str] = None) -> str: + """Return the password, otherwise throw an exception. + + :return: The password. + :raises ValueError: When the password is `None`. + """ + + if password is None: + raise ValueError("Private key password not provided") + return password + if isinstance(cert, Path): - self._ssl_builder.add_client(cert) + self._ssl_builder.add_client(cert, password=partial(pwd_check, None)) elif isinstance(cert, tuple) and len(cert) == 2: client, key = cast(Tuple[Path, Optional[Path]], cert) - self._ssl_builder.add_client(client, key) + self._ssl_builder.add_client(client, key, password=partial(pwd_check, None)) elif isinstance(cert, tuple) and len(cert) == 3: client, key, password = cast( Tuple[Path, Optional[Path], Optional[str]], cert ) - self._ssl_builder.add_client(client, key, password) + self._ssl_builder.add_client( + client, key, password=partial(pwd_check, password) + ) else: raise TypeError("Unexepected value") diff --git a/tests/conftest.py b/tests/conftest.py index d1356ca9..33972795 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ import sys import threading from asyncio import StreamReader, StreamWriter -from dataclasses import dataclass, field +from dataclasses import dataclass from pathlib import Path from shutil import which from socket import gethostbyname @@ -13,6 +13,12 @@ import pytest import trustme +from cryptography.hazmat.primitives.serialization import ( + BestAvailableEncryption, + Encoding, + PrivateFormat, + load_pem_private_key, +) from pytest_mock import MockerFixture from aiospamc.header_values import ContentLengthValue @@ -321,14 +327,24 @@ def server_cert_and_key(server_cert, tmp_path_factory: pytest.TempdirFactory): yield cert_file, key_file +@pytest.fixture(scope="session") +def client_private_key_password(): + yield b"password" + + @pytest.fixture(scope="session") def client_cert_and_key( - ca, hostname, ip_address, tmp_path_factory: pytest.TempdirFactory + ca, + hostname, + ip_address, + tmp_path_factory: pytest.TempdirFactory, + client_private_key_password, ): tmp_path = tmp_path_factory.mktemp("client_certs") cert_file = tmp_path / "client.cert" key_file = tmp_path / "client.key" cert_key_file = tmp_path / "client_cert_key.pem" + enc_key_file = tmp_path / "client_enc_key.pem" cert: trustme.LeafCert = ca.issue_cert(hostname, ip_address) @@ -336,7 +352,18 @@ def client_cert_and_key( cert_file.write_bytes(b"".join([blob.bytes() for blob in cert.cert_chain_pems])) cert.private_key_pem.write_to_path(key_file) - yield cert_file, key_file, cert_key_file + client_private_key = load_pem_private_key( + cert.private_key_pem.bytes(), + None, + ) + client_enc_key_bytes = client_private_key.private_bytes( + Encoding.PEM, + PrivateFormat.PKCS8, + BestAvailableEncryption(client_private_key_password), + ) + enc_key_file.write_bytes(client_enc_key_bytes) + + yield cert_file, key_file, cert_key_file, enc_key_file @pytest.fixture(scope="session") @@ -364,6 +391,11 @@ def client_key_path(client_cert_and_key): yield client_cert_and_key[1] +@pytest.fixture(scope="session") +def client_encrypted_key_path(client_cert_and_key): + yield client_cert_and_key[3] + + @dataclass class ServerResponse: response: bytes = b"" diff --git a/tests/test_cli.py b/tests/test_cli.py index 907337e0..efd6eb21 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,7 +5,7 @@ import pytest from loguru import logger -from pytest_mock import MockerFixture +from pytest_mock import MockerFixture, MockFixture from typer.testing import CliRunner import aiospamc @@ -97,11 +97,19 @@ def test_cli_builder_add_ca_cert_not_found(): CliClientBuilder().with_connection().add_ca_cert(Path("doesnt_exist")).build() -def test_cli_builder_add_ca_client(client_cert_path, client_key_path): +def test_cli_builder_add_ca_client( + mocker: MockFixture, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, +): + mocker.patch("getpass.getpass", return_value=client_private_key_password) c = ( CliClientBuilder() .with_connection() - .add_client_cert(client_cert_path, client_key_path, "password") + .add_client_cert( + client_cert_path, client_encrypted_key_path, client_private_key_password + ) .build() ) diff --git a/tests/test_connections.py b/tests/test_connections.py index 1e68a744..6a974694 100644 --- a/tests/test_connections.py +++ b/tests/test_connections.py @@ -335,10 +335,18 @@ def test_ssl_context_builder_add_ca_path_not_found(): def test_ssl_context_builder_add_client_cert( - mocker: MockerFixture, client_cert_path, client_key_path + mocker: MockerFixture, + client_cert_path, + client_key_path, + client_private_key_password, ): builder = SSLContextBuilder() certs_spy = mocker.spy(builder._context, "load_cert_chain") - s = builder.add_client(client_cert_path, client_key_path, "password").build() - - assert (client_cert_path, client_key_path, "password") == certs_spy.call_args.args + password_call = lambda: client_private_key_password + s = builder.add_client(client_cert_path, client_key_path, password_call).build() + + assert ( + client_cert_path, + client_key_path, + password_call, + ) == certs_spy.call_args.args diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 3014cccf..b2766eed 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -136,12 +136,14 @@ def test_frontend_builder_add_client_cert_and_key(client_cert_path, client_key_p def test_frontend_builder_add_client_cert_key_and_password( - client_cert_path, client_key_path + client_cert_path, client_encrypted_key_path, client_private_key_password ): f = ( FrontendClientBuilder() .with_connection() - .add_client_cert((client_cert_path, client_key_path, "password")) + .add_client_cert( + (client_cert_path, client_encrypted_key_path, client_private_key_password) + ) .build() ) @@ -313,6 +315,43 @@ async def test_ping_returns_response_ssl_client( assert isinstance(result, Response) +async def test_ping_returns_response_ssl_client_encrypted_private_key( + fake_tcp_ssl_client, + spam, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, +): + _, host, port = fake_tcp_ssl_client + result = await ping( + host=host, + port=port, + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert isinstance(result, Response) + + +async def test_ping_returns_response_ssl_client_encrypted_private_key_raises_error( + fake_tcp_ssl_client, + spam, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, +): + _, host, port = fake_tcp_ssl_client + + with pytest.raises(ValueError): + await ping( + host=host, + port=port, + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path), + ) + + async def test_tell_request_with_default_parameters(fake_tcp_server, spam, mocker): _, host, port = fake_tcp_server req_spy = mocker.spy(Client, "request") diff --git a/util/create_certs.py b/util/create_certs.py new file mode 100755 index 00000000..671826f5 --- /dev/null +++ b/util/create_certs.py @@ -0,0 +1,71 @@ +#!/bin/env python3 + +from pathlib import Path + +import trustme +import typer +from cryptography.hazmat.primitives.serialization import ( + BestAvailableEncryption, + Encoding, + PrivateFormat, + load_pem_private_key, +) + + +def main(dest: Path, private_key_password: str = "password"): + """Generates certificates and private keys for local development testing.""" + + if not dest.exists(): + dest.mkdir(parents=True) + + if dest.exists() and not dest.is_dir(): + typer.echo("Destination is not a directory") + raise typer.Exit(1) + + # Define paths + ca_cert_file = dest / "ca.cert" + server_cert_and_key_file = dest / "server_cert_and_key.pem" + server_key_file = dest / "server.key" + server_cert_file = dest / "server.cert" + client_cert_and_key_file = dest / "client_cert_and_key.pem" + client_key_file = dest / "client.key" + client_cert_file = dest / "client.cert" + client_encrypted_key_file = dest / "client_encrypted.key" + + # Certificate authority + ca = trustme.CA() + ca.cert_pem.write_to_path(ca_cert_file) + typer.echo(f"CA certificate to: [{ca_cert_file}]") + + # Server certificate + server = ca.issue_cert("localhost", "::1", "127.0.0.1") + server.private_key_and_cert_chain_pem.write_to_path(server_cert_and_key_file) + typer.echo(f"Server certificate and private key: [{server_cert_and_key_file}]") + server.cert_chain_pems[0].write_to_path(server_cert_file) + typer.echo(f"Server certificate: [{server_key_file}]") + server.private_key_pem.write_to_path(server_key_file) + typer.echo(f"Server private key: [{server_key_file}]") + + # Client certificate + client = ca.issue_cert("localhost", "::1", "127.0.0.1") + client.private_key_and_cert_chain_pem.write_to_path(client_cert_and_key_file) + typer.echo(f"Client certificate and private key: [{client_cert_and_key_file}]") + client.cert_chain_pems[0].write_to_path(client_cert_file) + typer.echo(f"Client certificate: [{client_cert_file}]") + client.private_key_pem.write_to_path(client_key_file) + typer.echo(f"Client private key: [{client_key_file}]") + client_private_key = load_pem_private_key( + client.private_key_pem.bytes(), + None, + ) + client_enc_key_bytes = client_private_key.private_bytes( + Encoding.PEM, + PrivateFormat.PKCS8, + BestAvailableEncryption(private_key_password.encode()), + ) + client_encrypted_key_file.write_bytes(client_enc_key_bytes) + typer.echo(f"Client private key (encrypted): [{client_encrypted_key_file}]") + + +if __name__ == "__main__": + typer.run(main) From c7837c28303f4910df4f349ff02218c44bad6a23 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Mon, 9 Oct 2023 20:02:47 -0400 Subject: [PATCH 29/41] Added integration tests for encrypted private key --- tests/test_integration_ssl_client.py | 159 +++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tests/test_integration_ssl_client.py b/tests/test_integration_ssl_client.py index 464baf66..d88784ca 100644 --- a/tests/test_integration_ssl_client.py +++ b/tests/test_integration_ssl_client.py @@ -145,3 +145,162 @@ async def test_tell_client_auth( ) assert 0 == result.status_code + + +@pytest.mark.integration +async def test_check_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.check( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_headers_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.headers( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_ping_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, +): + result = await aiospamc.ping( + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_process_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.process( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_report_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.report( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_report_if_spam_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.report_if_spam( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_symbols_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.symbols( + spam, + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code + + +@pytest.mark.integration +async def test_tell_client_encrypted( + spamd_ssl_client, + ca_cert_path, + client_cert_path, + client_encrypted_key_path, + client_private_key_password, + spam, +): + result = await aiospamc.tell( + message=spam, + message_class="spam", + host=spamd_ssl_client[0], + port=spamd_ssl_client[1], + verify=ca_cert_path, + cert=(client_cert_path, client_encrypted_key_path, client_private_key_password), + ) + + assert 0 == result.status_code From f240c5c314fa026ea2771eb77c8fde2f2dca7870 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 29 Aug 2023 22:09:13 -0400 Subject: [PATCH 30/41] Cleaned README formatting and examples --- README.rst | 113 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/README.rst b/README.rst index 0456a6e7..068e6e9c 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -======== +######## aiospamc -======== +######## |pypi| |docs| |license| |unit| |integration| |python| @@ -22,54 +22,58 @@ aiospamc .. |python| image:: https://img.shields.io/pypi/pyversions/aiospamc :target: https://python.org ------------ -Description ------------ +**aiospamc** is a client for SpamAssassin that you can use as a library or command line tool. -Python asyncio-based library that implements the SPAMC/SPAMD client protocol used by SpamAssassin. +The implementation is based on asyncio; so you can use it in your applications for asynchronous calls. -------------- +The command line interface provides user-friendly access to SpamAssassin server commands and provides both JSON +and user-consumable outputs. + +************* Documentation -------------- +************* -Documentation can be found at: https://aiospamc.readthedocs.io/ +Detailed documentation can be found at: https://aiospamc.readthedocs.io/ ------------- +************ Requirements ------------- +************ * Python 3.8 or higher +* `certifi` for updated certificate authorities +* `loguru` for structured logging +* `typer` for the command line interface + +******** +Examples +******** ----------------------- -As a Command Line Tool ----------------------- +Command-Line Tool +================= `aiospamc` is your interface to SpamAssassin through CLI. To submit a message for a score, use: -.. code:: - - cat ./gtube.msg | aiospamc check - -and the response will be the score of the message: - -.. code:: +.. code-block:: console + # Take the output of gtube.msg and have SpamAssasin return a score + $ cat ./gtube.msg | aiospamc check 1000.0/5.0 -Sub-commands for learning and reporting are also supported. + # Ping the server + $ aiospamc ping + PONG ------------- -As a Library ------------- +Library +======= -.. code:: python +.. code-block:: python import asyncio import aiospamc - GTUBE = '''Subject: Test spam mail (GTUBE) + GTUBE = """Subject: Test spam mail (GTUBE) Message-ID: Date: Wed, 23 Jul 2003 23:30:00 +0200 From: Sender @@ -94,14 +98,47 @@ As a Library XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X You should send this test mail from an account outside of your network. - '''.encode('ascii') - - loop = asyncio.get_event_loop() - responses = loop.run_until_complete(asyncio.gather( - - aiospamc.ping(host='localhost'), - aiospamc.check(GTUBE, host='localhost'), - aiospamc.headers(GTUBE, host='localhost') - - )) - print(responses) + """.encode("ascii") + + + # Ping the SpamAssassin server + async def is_alive(): + pong = await aiospamc.ping() + return True if pong.status_code == 0 else False + + asyncio.run(is_alive()) + # True + + + # Get the spam score of a message + async def get_score(message): + response = await aiospamc.check(message) + return response.headers.spam.score, response.headers.spam.threshold + + asyncio.run(get_score(GTUBE)) + # (1000.0, 5.0) + + + # List the modified headers + async def list_headers(message): + response = await aiospamc.headers(message) + for line in response.body.splitlines(): + print(line.decode()) + + asyncio.run(list_headers(GTUBE)) + # Received: from localhost by DESKTOP. + # with SpamAssassin (version 4.0.0); + # Wed, 30 Aug 2023 20:11:34 -0400 + # From: Sender + # To: Recipient + # Subject: Test spam mail (GTUBE) + # Date: Wed, 23 Jul 2003 23:30:00 +0200 + # Message-Id: + # X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-14) on DESKTOP. + # X-Spam-Flag: YES + # X-Spam-Level: ************************************************** + # X-Spam-Status: Yes, score=1000.0 required=5.0 tests=GTUBE,NO_RECEIVED, + # NO_RELAYS,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no + # version=4.0.0 + # MIME-Version: 1.0 + # Content-Type: multipart/mixed; boundary="----------=_64EFDAB6.3640FAEF" From 8af78ab3974432feddb56831f9eb5b761c17b331 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 30 Aug 2023 21:31:36 -0400 Subject: [PATCH 31/41] Updated examples in library page --- docs/library.rst | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/docs/library.rst b/docs/library.rst index 6c6bd683..09752a13 100644 --- a/docs/library.rst +++ b/docs/library.rst @@ -11,32 +11,26 @@ message, specify the host and await on the request. In this case, the response will contain a header called `Spam` with a boolean if the message is considered spam as well as the score. -.. code-block:: +.. code-block:: python import asyncio import aiospamc - example_message = ('From: John Doe ' - 'To: Mary Smith ' - 'Subject: Saying Hello' - 'Date: Fri, 21 Nov 1997 09:55:06 -0600' - 'Message-ID: <1234@local.machine.example>' - '' - 'This is a message just to say hello.' - 'So, "Hello".').encode('ascii') - - async def check_for_spam(message): - response = await aiospamc.check(message, host='localhost') - return response - - loop = asyncio.get_event_loop() + example_message = ("From: John Doe " + "To: Mary Smith " + "Subject: Saying Hello" + "Date: Fri, 21 Nov 1997 09:55:06 -0600" + "Message-ID: <1234@local.machine.example>" + "" + "This is a message just to say hello." + "So, 'Hello'.").encode("ascii") - response = loop.run_until_complete(check_for_spam(example_message)) + response = asyncio.run(aiospamc.check(message, host="localhost")) print( - f'Is the message spam? {response.headers['Spam'].value}\n', - f'The score and threshold is {response.headers['Spam'].score} ', - f'/ {response.headers['Spam'].threshold}'), - sep='' + f"Is the message spam? {response.headers.spam.value}\n", + f"The score and threshold is {response.headers.spam.score} ", + f"/ {response.headers.spam.threshold}", + sep="" ) ***************** From 1561ba631165e38e0d03c8d8da1da4ada4e6a31b Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Mon, 4 Sep 2023 20:58:43 -0400 Subject: [PATCH 32/41] Added client certificate docstrings to frontend --- aiospamc/frontend.py | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/aiospamc/frontend.py b/aiospamc/frontend.py index bfb9ed38..d3e302cc 100644 --- a/aiospamc/frontend.py +++ b/aiospamc/frontend.py @@ -212,11 +212,12 @@ async def check( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. - :param client_cert: Client certificate file to use. - :param client_key: Key file to use for the client certificate. - :param key_password: Password to use for the client key if needed. :return: A successful response with a "Spam" header showing if the message is @@ -292,7 +293,6 @@ async def headers( ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return the modified message headers. @@ -305,6 +305,10 @@ async def headers( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -380,7 +384,6 @@ async def ping( Tuple[Path, Optional[Path], Optional[str]], None, ] = None, - **kwargs, ) -> Response: """Sends a ping to the SPAMD service. @@ -392,6 +395,10 @@ async def ping( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :return: A response with "PONG". @@ -462,7 +469,6 @@ async def process( ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return a response with a score header. @@ -475,6 +481,10 @@ async def process( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -553,7 +563,6 @@ async def report( ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return a response with a score header. @@ -566,6 +575,10 @@ async def report( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -643,7 +656,6 @@ async def report_if_spam( ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return a response with a score header. @@ -656,6 +668,10 @@ async def report_if_spam( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -734,7 +750,6 @@ async def symbols( ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return a response with rules that matched. @@ -747,6 +762,10 @@ async def symbols( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -828,7 +847,6 @@ async def tell( ] = None, user: Optional[str] = None, compress: bool = False, - **kwargs, ) -> Response: """Checks a message if it's spam and return a response with a score header. @@ -844,6 +862,10 @@ async def tell( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. + :param cert: Use client certificate. Can either by a `Path` to a file that includes both the + certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to + the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. From 79499363fda300b38bb9ad31a9dbc25479dde5d1 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Thu, 7 Sep 2023 09:59:40 -0400 Subject: [PATCH 33/41] Updated library docs with client certificate --- docs/library.rst | 49 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/docs/library.rst b/docs/library.rst index 09752a13..2fafc730 100644 --- a/docs/library.rst +++ b/docs/library.rst @@ -2,8 +2,7 @@ Library ####### -:mod:`aiospamc` provides top-level functions for basic functionality a lot like -the `requests` library. +:mod:`aiospamc` provides top-level functions for all request types. For example, to ask SpamAssassin to check and score a message you can use the :func:`aiospamc.check` function. Just give it a bytes-encoded copy of the @@ -16,14 +15,15 @@ spam as well as the score. import asyncio import aiospamc - example_message = ("From: John Doe " - "To: Mary Smith " - "Subject: Saying Hello" - "Date: Fri, 21 Nov 1997 09:55:06 -0600" - "Message-ID: <1234@local.machine.example>" - "" - "This is a message just to say hello." - "So, 'Hello'.").encode("ascii") + example_message = ( + "From: John Doe " + "To: Mary Smith " + "Subject: Saying Hello" + "Date: Fri, 21 Nov 1997 09:55:06 -0600" + "Message-ID: <1234@local.machine.example>" + "" + "This is a message just to say hello." + "So, 'Hello'.").encode("ascii") response = asyncio.run(aiospamc.check(message, host="localhost")) print( @@ -49,6 +49,29 @@ is used to load certificates to verify the connection. If `False` then an SSL connection is established, but the server certificate is not verified. +********************************* +Client Certificate Authentication +********************************* + +Client certificate authentication can be used with SSL. It's driven through the `cert` +parameter on frontend functions. The parameter value takes three forms: +* A path to a file expecting the certificate and key in the PEM format +* A tuple of certificate and key files +* A tuple of certificate file, key file, and password if the key is encrypted + +.. code:: python + + import aiospamc + + # Client certificate and key in one file + response = await aiospamc.ping("localhost", cert=cert_file) + + # Client certificate and key file + response = await aiospamc.ping("localhost", cert=(cert_file, key_file)) + + # Client certificate and key in one file + response = await aiospamc.ping("localhost", cert=(cert_file, key_file, password)) + **************** Setting timeouts **************** @@ -64,7 +87,9 @@ You can configure any of the three optional parameters: * connection - time in seconds to wait for a connection to be established * response - time in seconds to wait for a response after sending the request -Example:: +.. code:: python + + import aiospamc my_timeout = aiospamc.Timeout(total=60, connection=10, response=10) @@ -82,7 +107,7 @@ Logging is provided using through the `loguru The `aiospamc` package disables logging by default. It can be enabled by calling the function: -.. code-block:: +.. code-block:: python from loguru import logger logger.enable("aiospamc") From 9ffcad2043f1e9779aff02eb9ea56466e2197710 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 13:15:42 -0400 Subject: [PATCH 34/41] Add missing modules from API documentation --- docs/aiospamc.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/aiospamc.rst b/docs/aiospamc.rst index b3d40cd7..0df93bb9 100644 --- a/docs/aiospamc.rst +++ b/docs/aiospamc.rst @@ -4,6 +4,22 @@ aiospamc package Submodules ---------- +aiospamc.cli module +--------------------------- + +.. automodule:: aiospamc.cli + :members: + :undoc-members: + :show-inheritance: + +aiospamc.client module +--------------------------- + +.. automodule:: aiospamc.client + :members: + :undoc-members: + :show-inheritance: + aiospamc.connections module --------------------------- @@ -60,6 +76,14 @@ aiospamc.responses module :undoc-members: :show-inheritance: +aiospamc.user\_warnings module +------------------------- + +.. automodule:: aiospamc.user_warnings + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- From c1f8fa97b92481628f885d7c363ca3278e834bf6 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 13:22:53 -0400 Subject: [PATCH 35/41] Spelling mistake for class name --- aiospamc/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiospamc/cli.py b/aiospamc/cli.py index 8f468142..bec4b41c 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -59,7 +59,7 @@ class CliClientBuilder: """Client builder for CLI arguments.""" def __init__(self): - """Constructor for the ClieClientBuilder.""" + """Constructor for the CliClientBuilder.""" self._connection_builder = ConnectionManagerBuilder() self._ssl = False From 0c174d5bcb31e4d3c6644ef764fa8ce989b9a3e6 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 13:45:48 -0400 Subject: [PATCH 36/41] Added Sphinx directives to docstrings --- aiospamc/cli.py | 2 +- aiospamc/connections.py | 14 ++++----- aiospamc/frontend.py | 54 +++++++++++++++++----------------- aiospamc/incremental_parser.py | 26 ++++++++-------- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/aiospamc/cli.py b/aiospamc/cli.py index bec4b41c..6d3d7e26 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -66,7 +66,7 @@ def __init__(self): self._ssl_builder = SSLContextBuilder() def build(self) -> Client: - """Builds the `Client`. + """Builds the :class:`aiospamc.client.Client`. :return: An instance of :class:`aiospamc.client.Client`. """ diff --git a/aiospamc/connections.py b/aiospamc/connections.py index dec90d0f..df7fb69f 100644 --- a/aiospamc/connections.py +++ b/aiospamc/connections.py @@ -67,7 +67,7 @@ def __init__(self): self._timeout = None def build(self) -> Union[UnixConnectionManager, TcpConnectionManager]: - """Builds the `ConnectionManager`. + """Builds the :class:`aiospamc.connections.ConnectionManager`. :return: An instance of :class:`aiospamc.connections.TcpConnectionManager` or :class:`aiospamc.connections.UnixConnectionManager` @@ -116,7 +116,7 @@ def with_tcp(self, host: str, port: int = 783) -> ConnectionManagerBuilder: def add_ssl_context(self, context: ssl.SSLContext) -> ConnectionManagerBuilder: """Adds an SSL context when a TCP connection is being used. - :param context: `ssl.SSLContext` instance. + :param context: :class:`ssl.SSLContext` instance. :return: This builder instance. """ @@ -259,7 +259,7 @@ def __init__(self): self._args = {} def build(self) -> TcpConnectionManager: - """Builds the `TcpConnectionManager`. + """Builds the :class:`aiospamc.connections.TcpConnectionManager`. :return: An instance of :class:`aiospamc.connections.TcpConnectionManager`. """ @@ -291,7 +291,7 @@ def set_port(self, port: int) -> TcpConnectionManagerBuilder: def set_ssl_context(self, context: ssl.SSLContext) -> TcpConnectionManagerBuilder: """Set an SSL context. - :param context: An instance of `ssl.SSLContext`. + :param context: An instance of :class:`ssl.SSLContext`. :return: This builder instance. """ @@ -354,7 +354,7 @@ async def open(self) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: class UnixConnectionManagerBuilder: - """Builder for `UnixConnectionManager`.""" + """Builder for :class:`aiospamc.connections.UnixConnectionManager`.""" def __init__(self): """`UnixConnectionManagerBuilder` constructor.""" @@ -362,7 +362,7 @@ def __init__(self): self._args = {} def build(self) -> UnixConnectionManager: - """Builds a `UnixConnectionManager`. + """Builds a :class:`aiospamc.connections.UnixConnectionManager`. :return: An instance of :class:`aiospamc.connections.UnixConnectionManager`. """ @@ -433,7 +433,7 @@ def __init__(self): def build(self) -> ssl.SSLContext: """Builds the SSL context. - :return: An instance of `ssl.SSLContext`. + :return: An instance of :class:`ssl.SSLContext`. """ return self._context diff --git a/aiospamc/frontend.py b/aiospamc/frontend.py index d3e302cc..5e0c2d18 100644 --- a/aiospamc/frontend.py +++ b/aiospamc/frontend.py @@ -28,7 +28,7 @@ def __init__(self): self._ssl_builder = SSLContextBuilder() def build(self) -> Client: - """Builds the `Client`. + """Builds the :class:`aiospamc.client.Client`. :return: An instance of :class:`aiospamc.client.Client`. """ @@ -71,8 +71,8 @@ def add_verify( """Adds an SSL context to the connection manager. :param verify: How to configure the SSL context. If `True`, add the default - certificate authorities. If `False`, accept any certificate. If a `Path`, - add the certificates from it. If an `ssl.SSLContext`, then use it. + certificate authorities. If `False`, accept any certificate. If a :class:`pathlib.Path`, + add the certificates from it. If an :class:`ssl.SSLContext`, then use it. :return: This builder instance. """ @@ -212,9 +212,9 @@ async def check( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -305,9 +305,9 @@ async def headers( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -395,9 +395,9 @@ async def ping( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :return: A response with "PONG". @@ -481,9 +481,9 @@ async def process( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -575,9 +575,9 @@ async def report( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -668,9 +668,9 @@ async def report_if_spam( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -762,9 +762,9 @@ async def symbols( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. @@ -862,9 +862,9 @@ async def tell( Enable SSL. `True` will use the root certificates from the :py:mod:`certifi` package. `False` will use SSL, but not verify the root certificates. Passing a string to a filename will use the path to verify the root certificates. - :param cert: Use client certificate. Can either by a `Path` to a file that includes both the - certificate and key. Can be a tuple containing a `Path` to the certificate and a `Path` to - the key. Can be a tuple containing a `Path` to the certificate, `Path` to the key, and a + :param cert: Use client certificate. Can either by a :class:`pathlib.Path` to a file that includes both the + certificate and key. Can be a tuple containing a :class:`pathlib.Path` to the certificate and a :class:`pathlib.Path` to + the key. Can be a tuple containing a :class:`pathlib.Path` to the certificate, :class:`pathlib.Path` to the key, and a password. :param user: Username to pass to the SPAMD service. :param compress: Enable compress of the request body. diff --git a/aiospamc/incremental_parser.py b/aiospamc/incremental_parser.py index dbf66f80..dc60aa7c 100644 --- a/aiospamc/incremental_parser.py +++ b/aiospamc/incremental_parser.py @@ -109,12 +109,12 @@ def parse(self, stream: bytes) -> Mapping[str, Any]: return self.result def status(self) -> None: - """Splits the message at the delimiter and sends the first part of the message to the `status_line` callable to + """Splits the message at the delimiter and sends the first part of the message to the :attr:`status_parser` callable to be parsed. If successful then the results are stored in the :attr:`result` class attribute and the state transitions to :class:`States.Header`. :raises NotEnoughDataError: When there is no delimiter the message is incomplete. - :raises ParseError: When the `status_line` callable experiences an error. + :raises ParseError: When the :attr:`status_parser` callable experiences an error. """ status_line, delimiter, leftover = self.buffer.partition(self.delimiter) @@ -130,7 +130,7 @@ def status(self) -> None: raise NotEnoughDataError def header(self) -> None: - """Splits the message at the delimiter and sends the line to the `header_parser`. + """Splits the message at the delimiter and sends the line to the :attr:`header_parser`. When splitting the action will be determined depending what is matched: @@ -331,9 +331,9 @@ def parse_message_class_value( ) -> MessageClassValue: """Parses the `Message-class` header value. - :param stream: String or `MessageClassOption` instance. + :param stream: String or :class:`aiospamc.header_values.MessageClassOption` instance. - :return: A `MessageClassValue` instance representing the value. + :return: A :class:`aiospamc.header_values.MessageClassValue` instance representing the value. :raises ParseError: When the value doesn't match either `ham` or `spam`. """ @@ -355,7 +355,7 @@ def parse_content_length_value(stream: Union[str, int]) -> ContentLengthValue: :param stream: String or integer value of the header. - :return: A `ContentLengthValue` instance. + :return: A :class:`aiospamc.header_values.ContentLengthValue` instance. :raises ParseError: When the value cannot be cast to an integer. """ @@ -375,18 +375,18 @@ def parse_compress_value(stream: str) -> CompressValue: :param stream: String to parse. - :return: A `CompressValue` instance. + :return: A :class:`aiospamc.header_values.CompressValue` instance. """ return CompressValue(algorithm=stream.strip()) def parse_set_remove_value(stream: Union[ActionOption, str]) -> SetOrRemoveValue: - """Parse a value for the `DidRemove`, `DidSet`, `Remove`, and `Set` headers. + """Parse a value for the :class:`aiospamc.header_values.DidRemove`, :class:`aiospamc.header_values.DidSet`, :class:`aiospamc.header_values.Remove`, and :class:`aiospamc.header_values.Set` headers. - :param stream: String to parse or an instance of `ActionOption`. + :param stream: String to parse or an instance of :class:`aiospamc.header_values.ActionOption`. - :return: A `SetOrRemoveValue` instance. + :return: A :class:`aiospamc.header_values.SetOrRemoveValue` instance. """ if isinstance(stream, ActionOption): @@ -415,7 +415,7 @@ def parse_spam_value(stream: str) -> SpamValue: :param stream: String to parse. - :return: An `SpamValue` instance. + :return: A :class:`aiospamc.header_values.SpamValue` instance. :raises ParseError: Raised if there is no true/false value, or valid numbers for the score or threshold. """ @@ -452,7 +452,7 @@ def parse_user_value(stream: str) -> UserValue: :param stream: String of username to parse. Whitespace is trimmed. - :return: The `UserValue` instance. + :return: The :class:`aiospamc.header_values.UserValue` instance. """ return UserValue(name=stream.strip()) @@ -464,7 +464,7 @@ def parse_header_value(header: str, value: Union[str, bytes]) -> Any: :param header: Name of the header. :param value: String or byte stream of the header value. - :return: The `HeaderValue` instance from the parsing function. + :return: The :class:`aiospamc.header_values.HeaderValue` instance from the parsing function. """ if header in header_value_parsers: From 508b05f84ee9debe71ce358f58b078cd225be854 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 21:47:57 -0400 Subject: [PATCH 37/41] Resolved Sphinx warnings --- aiospamc/cli.py | 2 +- aiospamc/connections.py | 2 +- aiospamc/frontend.py | 10 +++++----- docs/aiospamc.rst | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/aiospamc/cli.py b/aiospamc/cli.py index 6d3d7e26..df3533bc 100644 --- a/aiospamc/cli.py +++ b/aiospamc/cli.py @@ -120,7 +120,7 @@ def add_verify(self, verify: bool) -> "CliClientBuilder": """Adds an SSL context to the connection manager. :param verify: How to configure the SSL context. If `True`, add the default - certificate authorities. If `False`, accept any certificate. + certificate authorities. If `False`, accept any certificate. :return: This builder instance. """ diff --git a/aiospamc/connections.py b/aiospamc/connections.py index df7fb69f..fc02cc68 100644 --- a/aiospamc/connections.py +++ b/aiospamc/connections.py @@ -70,7 +70,7 @@ def build(self) -> Union[UnixConnectionManager, TcpConnectionManager]: """Builds the :class:`aiospamc.connections.ConnectionManager`. :return: An instance of :class:`aiospamc.connections.TcpConnectionManager` - or :class:`aiospamc.connections.UnixConnectionManager` + or :class:`aiospamc.connections.UnixConnectionManager` """ if self._manager_type is self.ManagerType.Undefined: diff --git a/aiospamc/frontend.py b/aiospamc/frontend.py index 5e0c2d18..a3cabb81 100644 --- a/aiospamc/frontend.py +++ b/aiospamc/frontend.py @@ -71,8 +71,8 @@ def add_verify( """Adds an SSL context to the connection manager. :param verify: How to configure the SSL context. If `True`, add the default - certificate authorities. If `False`, accept any certificate. If a :class:`pathlib.Path`, - add the certificates from it. If an :class:`ssl.SSLContext`, then use it. + certificate authorities. If `False`, accept any certificate. If a :class:`pathlib.Path`, + add the certificates from it. If an :class:`ssl.SSLContext`, then use it. :return: This builder instance. """ @@ -106,9 +106,9 @@ def add_client_cert( """Add a client certificate to authenticate to the server. :param cert: Client certificate. Takes up to three a three tuple value. - 1. Path to the certificate and key. - 2. Path to the certificate and path to the key. - 3. Path to the certificate, path to the key, and password of the key. + 1. Path to the certificate and key. + 2. Path to the certificate and path to the key. + 3. Path to the certificate, path to the key, and password of the key. :return: This builder instance. """ diff --git a/docs/aiospamc.rst b/docs/aiospamc.rst index 0df93bb9..e8e42777 100644 --- a/docs/aiospamc.rst +++ b/docs/aiospamc.rst @@ -77,7 +77,7 @@ aiospamc.responses module :show-inheritance: aiospamc.user\_warnings module -------------------------- +------------------------------ .. automodule:: aiospamc.user_warnings :members: From 12e66f2b29ec4963e9b4fc302c3ac88ad9f59f38 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 22:02:48 -0400 Subject: [PATCH 38/41] Bump to 1.0.0 --- aiospamc/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiospamc/__init__.py b/aiospamc/__init__.py index 7da4b6b8..6583be68 100644 --- a/aiospamc/__init__.py +++ b/aiospamc/__init__.py @@ -20,7 +20,7 @@ __author__ = "Michael Caley" __copyright__ = "Copyright 2016-2023 Michael Caley" __license__ = "MIT" -__version__ = "0.10.1" +__version__ = "1.0.0" __email__ = "mjcaley@darkarctic.com" logger.disable(__package__) diff --git a/pyproject.toml b/pyproject.toml index 988466b5..56a8ce24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiospamc" -version = "0.10.1" +version = "1.0.0" description = "An asyncio-based library to communicate with SpamAssassin's SPAMD service." authors = ["Michael Caley "] license = "MIT" @@ -101,7 +101,7 @@ tbump = "^6.10" github_url = "https://github.com/mjcaley/aiospamc/" [tool.tbump.version] -current = "0.10.1" +current = "1.0.0" regex = ''' (?P\d+) \. From 8c9b12a8d19b78dcd3b53272e7d84f7d250b5ca6 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 22:11:38 -0400 Subject: [PATCH 39/41] Made changes to support trusted publisher for release --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee4e7499..0997b1ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,8 @@ jobs: release: name: "Upload release artifacts" runs-on: ubuntu-latest + permissions: + id-token: write steps: - uses: actions/checkout@v3 - name: Save tag @@ -48,6 +50,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish to PyPI - run: poetry publish -n - env: - POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_ACCESS_TOKEN }} + uses: pypa/gh-action-pypi-publish@release/v1 From ae6f9411e0c1cbe0c048002fee3f46143b74baa7 Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Tue, 10 Oct 2023 22:13:27 -0400 Subject: [PATCH 40/41] Using release environment --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0997b1ab..1c3b0481 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ jobs: release: name: "Upload release artifacts" runs-on: ubuntu-latest + environment: release permissions: id-token: write steps: From 109ef4270c07c1d0c078360f6a5b4976a48bae1b Mon Sep 17 00:00:00 2001 From: Michael Caley Date: Wed, 11 Oct 2023 09:26:55 -0400 Subject: [PATCH 41/41] Added write permission for creating a release --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c3b0481..d0f1ad50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ jobs: environment: release permissions: id-token: write + contents: write steps: - uses: actions/checkout@v3 - name: Save tag