diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 48c35fc..f4050da 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@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,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@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,6 +69,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index cd699ba..8e155b4 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, '3.10', '3.11'] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] steps: @@ -22,7 +22,7 @@ jobs: submodules: recursive - name: Set up Python ${{matrix.python-version}} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{matrix.python-version}} @@ -34,7 +34,12 @@ jobs: - name: Test with nosetests run: | poetry run pytest --cov=pymisp tests/test_*.py - poetry run mypy tests/testlive_comprehensive.py tests/test_mispevent.py tests/testlive_sync.py pymisp + poetry run mypy . + + - name: Test with nosetests with orjson + run: | + pip3 install orjson + poetry run pytest --cov=pymisp tests/test_*.py - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 diff --git a/CHANGELOG.txt b/CHANGELOG.txt index bb9f035..67a609f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,250 @@ Changelog ========= +v2.4.185 (2024-02-16) +--------------------- + +Changes +~~~~~~~ +- Bump deps, version. [Raphaël Vinot] + + +v2.4.184.3 (2024-02-12) +----------------------- + +Changes +~~~~~~~ +- Bump changelog. [Raphaël Vinot] +- Bump version. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] + +Fix +~~~ +- Properly get body from message, without headers. [Raphaël Vinot] +- Remove from __all__ entries that shouldn't be there. [Raphaël Vinot] + +Other +~~~~~ +- Build(deps-dev): bump jupyter-lsp from 2.2.1 to 2.2.2. + [dependabot[bot]] + + Bumps [jupyter-lsp](https://github.com/jupyter-lsp/jupyterlab-lsp) from 2.2.1 to 2.2.2. + - [Release notes](https://github.com/jupyter-lsp/jupyterlab-lsp/releases) + - [Changelog](https://github.com/jupyter-lsp/jupyterlab-lsp/blob/main/CHANGELOG.md) + - [Commits](https://github.com/jupyter-lsp/jupyterlab-lsp/commits) + + --- + updated-dependencies: + - dependency-name: jupyter-lsp + dependency-type: indirect + ... + + +v2.4.184.2 (2024-02-06) +----------------------- + +Changes +~~~~~~~ +- Add changelog. [Raphaël Vinot] +- Bump changelog. [Raphaël Vinot] +- Re-add ExpandedPyMISP, with a warning. [Raphaël Vinot] + +Fix +~~~ +- Do not throw a warning every time one import pymisp... [Raphaël Vinot] + +Other +~~~~~ +- Build(deps): bump codecov/codecov-action from 3 to 4. + [dependabot[bot]] + + Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. + - [Release notes](https://github.com/codecov/codecov-action/releases) + - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) + - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) + + --- + updated-dependencies: + - dependency-name: codecov/codecov-action + dependency-type: direct:production + update-type: version-update:semver-major + ... + + +v2.4.184.1 (2024-02-06) +----------------------- + +Changes +~~~~~~~ +- Bump changelog. [Raphaël Vinot] +- Re-add ExpandedPyMISP, with a warning. [Raphaël Vinot] + + +v2.4.184 (2024-02-02) +--------------------- + +New +~~~ +- Enable support for python 3.12. [Raphaël Vinot] +- Relationship_type in tag. [Raphaël Vinot] + + Fix https://github.com/MISP/MISP/issues/9483 +- [internal] Add support for orjson. [Jakub Onderka] + + orjson is much faster library for decoding and encoding JSON formats + +Changes +~~~~~~~ +- Bump changelog. [Raphaël Vinot] +- Bump deps, version, objects. [Raphaël Vinot] +- Remove IntEnum. [Raphaël Vinot] +- Add even more debug for gha. [Raphaël Vinot] +- Add some debug for gha. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- Add more strict typing, not done yet. [Raphaël Vinot] +- Add a bunch more typing. [Raphaël Vinot] +- Use typing info of lief. [Raphaël Vinot] +- First batch of changes for strict typing. [Raphaël Vinot] +- Update typing to please lief. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- [internal] Simplify code. [Jakub Onderka] +- [internal] User faster method to convert bytes to str. [Jakub Onderka] +- New annotations in tests. [Raphaël Vinot] +- Initial changes to use new annotations. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- Bump deps, try to install with python 3.12. [Raphaël Vinot] +- Make the publish_timestamp a string, as per specs. [Raphaël Vinot] +- [internal] Update poetry.lock. [Jakub Onderka] + +Fix +~~~ +- Revert typing changes. [Raphaël Vinot] +- More responses athat are lists. [Raphaël Vinot] +- Another call that cn be a list or a dict. [Raphaël Vinot] +- Do not cast enum. [Raphaël Vinot] +- More fixes to support responses from MISP. [Raphaël Vinot] +- Handle list responses properly. [Raphaël Vinot] +- Import FileObject as needed. [Raphaël Vinot] +- Also skip docs from mypy. [Raphaël Vinot] +- Run mypy on what I want. [Raphaël Vinot] +- Compatibility with python 3.8. [Raphaël Vinot] +- Python < 3.10 support on typing, for good. [Raphaël Vinot] +- Python < 3.10 support on typing. [Raphaël Vinot] +- Rollback tests on python 3.12 as lief is not supported yet. [Raphaël + Vinot] +- Add missing wheel. [Raphaël Vinot] +- Make publish_timestamp a string in tests. [Raphaël Vinot] +- [internal] README typos. [Jakub Onderka] + +Other +~~~~~ +- Revert "fix: More responses athat are lists" [Raphaël Vinot] + + This reverts commit 709a10c64c0513b515f25c3ecfb9eb577b55084b. +- Build(deps): bump jinja2 from 3.1.2 to 3.1.3. [dependabot[bot]] + + Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3. + - [Release notes](https://github.com/pallets/jinja/releases) + - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) + - [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3) + + --- + updated-dependencies: + - dependency-name: jinja2 + dependency-type: indirect + ... + + +v2.4.183 (2024-01-04) +--------------------- + +New +~~~ +- Documentation to install PyMISP on offline machine. [Raphaël Vinot] + +Changes +~~~~~~~ +- Bump changelog. [Raphaël Vinot] +- Bump objects. [Raphaël Vinot] +- Bump version. [Raphaël Vinot] +- Remove jsonschema from dependencies. [Raphaël Vinot] +- Encrypt malicious js. [Raphaël Vinot] + +Other +~~~~~ +- Fix api ssl verify typing. [Steven] +- Add HTTPS Adapter. [Steven] + + Add the ability to provide a custom HTTPS adapter to the PyMISP class. With M2Crypto and m2requests, this can enable mutual TLS with hardware tokens. + + +v2.4.182 (2023-12-14) +--------------------- + +Changes +~~~~~~~ +- Bump changelog. [Raphaël Vinot] +- Bump version. [Raphaël Vinot] +- Bump objects. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] + +Fix +~~~ +- Avoid exception when the malware file name contains a "|" [Raphaël + Vinot] + +Other +~~~~~ +- Build(deps): bump github/codeql-action from 2 to 3. [dependabot[bot]] + + Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. + - [Release notes](https://github.com/github/codeql-action/releases) + - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) + - [Commits](https://github.com/github/codeql-action/compare/v2...v3) + + --- + updated-dependencies: + - dependency-name: github/codeql-action + dependency-type: direct:production + update-type: version-update:semver-major + ... +- Build(deps): bump actions/setup-python from 4 to 5. [dependabot[bot]] + + Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. + - [Release notes](https://github.com/actions/setup-python/releases) + - [Commits](https://github.com/actions/setup-python/compare/v4...v5) + + --- + updated-dependencies: + - dependency-name: actions/setup-python + dependency-type: direct:production + update-type: version-update:semver-major + ... + + +v2.4.179 (2023-11-23) +--------------------- + +Changes +~~~~~~~ +- Bump version, changelog. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- Bump deps. [Raphaël Vinot] +- [misp-objects] Bumped latest version. [Christian Studer] + +Fix +~~~ +- Eml and msg are in sync again. [Raphaël Vinot] +- Update calls to getStringStream to the public method. [Raphaël Vinot] +- Avoid confusing error when an auth key is limited to an IP. [Raphaël + Vinot] + + Fix #1099 + + v2.4.178 (2023-10-24) --------------------- @@ -11,6 +255,7 @@ New Changes ~~~~~~~ +- Bump changelog. [Raphaël Vinot] - Bump version, make __version__ dynamic. [Raphaël Vinot] - Bump deps, allow older jsonschema for compatibility. [Raphaël Vinot] - Bump deps. [Raphaël Vinot] @@ -5080,5 +5325,3 @@ v1.1.2 (2015-08-05) - Json export is not supported everywhere. [Raphaël Vinot] - Some testing. [Raphaël Vinot] - Initial commit. [Raphaël Vinot] - - diff --git a/README.md b/README.md index 39f2855..89da00f 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,6 @@ logging.basicConfig(level=logging.DEBUG, filename="debug.log", filemode='w', for # From poetry pytest --cov=pymisp tests/test_*.py tests/testlive_comprehensive.py:TestComprehensive.[test_name] - ``` ## Documentation @@ -159,6 +158,35 @@ Your new MISPObject generator must generate attributes and add them as class pro When the object is sent to MISP, all the class properties will be exported to the JSON export. +## Installing PyMISP on a machine with no internet access + +This is done using poetry and you need to have this repository cloned on your machine. +The commands below have to be run from inside the cloned directory. + + +1. From a machine with access to the internet, get the dependencies: + +```bash +mkdir offline +poetry export --all-extras > offline/requirements.txt +poetry run pip download -r offline/requirements.txt -d offline/packages/ +``` + +2. Prepare the PyMISP Package + +```bash +poetry build +mv dist/*.whl offline/packages/ +``` + +3. Copy the content of `offline/packages/` to the machine with no internet access. + +4. Install the packages: + +```bash +python -m pip install --no-index --no-deps packages/*.whl +``` + # License PyMISP is distributed under an [open source license](./LICENSE). A simplified 2-BSD license. diff --git a/examples/add_email_object.py b/examples/add_email_object.py index 756a562..5f190ae 100755 --- a/examples/add_email_object.py +++ b/examples/add_email_object.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from pymisp import ExpandedPyMISP +from pymisp import PyMISP from pymisp.tools import EMailObject import traceback from keys import misp_url, misp_key, misp_verifycert # type: ignore @@ -15,7 +14,7 @@ if __name__ == '__main__': parser.add_argument("-p", "--path", required=True, help="Path to process (expanded using glob).") args = parser.parse_args() - pymisp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert, debug=True) + pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) for f in glob.glob(args.path): try: diff --git a/mypy.ini b/mypy.ini index 78ad923..3f7aec0 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,15 @@ [mypy] -ignore_errors = False - +strict = True +warn_return_any = False show_error_context = True pretty = True -exclude = pymisp/data|example|docs +exclude = tests/testlive_comprehensive.py|tests/testlive_sync.py|feed-generator|examples|pymisp/data|docs|pymisp/tools/openioc.py|pymisp/tools/reportlab_generator.py|tests/test_reportlab.py + +# Stuff to remove gradually +disallow_untyped_defs = False +disallow_untyped_calls = False +disable_error_code = arg-type,return-value,assignment,call-overload,union-attr + + +[mypy-docs.source.*] +ignore_errors = True diff --git a/poetry.lock b/poetry.lock index 7203e30..794c49a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -11,36 +11,48 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = true +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + [[package]] name = "anyio" -version = "4.0.0" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, - {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "appnope" -version = "0.1.3" +version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, ] [[package]] @@ -121,20 +133,21 @@ test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock [[package]] name = "asttokens" -version = "2.4.0" +version = "2.4.1" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, - {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] [package.dependencies] six = ">=1.12.0" [package.extras] -test = ["astroid", "pytest"] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "async-lru" @@ -152,31 +165,32 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "babel" -version = "2.13.0" +version = "2.14.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, - {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] [package.dependencies] @@ -226,19 +240,22 @@ tzdata = ["tzdata"] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -393,13 +410,13 @@ cffi = ">=1.0.0" [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -466,103 +483,114 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = true +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + [[package]] name = "charset-normalizer" -version = "3.3.1" +version = "3.3.2" 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.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -589,22 +617,20 @@ files = [ [[package]] name = "comm" -version = "0.1.4" +version = "0.2.1" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "comm-0.1.4-py3-none-any.whl", hash = "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a"}, - {file = "comm-0.1.4.tar.gz", hash = "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15"}, + {file = "comm-0.2.1-py3-none-any.whl", hash = "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021"}, + {file = "comm-0.2.1.tar.gz", hash = "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a"}, ] [package.dependencies] traitlets = ">=4" [package.extras] -lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (>=0.0.156)"] test = ["pytest"] -typing = ["mypy (>=0.990)"] [[package]] name = "commonmark" @@ -632,63 +658,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf54c3e089179d9d23900e3efc86d46e4431188d9a657f345410eecdd0151f50"}, + {file = "coverage-7.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe6e43c8b510719b48af7db9631b5fbac910ade4bd90e6378c85ac5ac706382c"}, + {file = "coverage-7.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b98c89db1b150d851a7840142d60d01d07677a18f0f46836e691c38134ed18b"}, + {file = "coverage-7.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5f9683be6a5b19cd776ee4e2f2ffb411424819c69afab6b2db3a0a364ec6642"}, + {file = "coverage-7.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cdcbf7b9cb83fe047ee09298e25b1cd1636824067166dc97ad0543b079d22f"}, + {file = "coverage-7.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2599972b21911111114100d362aea9e70a88b258400672626efa2b9e2179609c"}, + {file = "coverage-7.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ef00d31b7569ed3cb2036f26565f1984b9fc08541731ce01012b02a4c238bf03"}, + {file = "coverage-7.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:20a875bfd8c282985c4720c32aa05056f77a68e6d8bbc5fe8632c5860ee0b49b"}, + {file = "coverage-7.4.2-cp310-cp310-win32.whl", hash = "sha256:b3f2b1eb229f23c82898eedfc3296137cf1f16bb145ceab3edfd17cbde273fb7"}, + {file = "coverage-7.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7df95fdd1432a5d2675ce630fef5f239939e2b3610fe2f2b5bf21fa505256fa3"}, + {file = "coverage-7.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8ddbd158e069dded57738ea69b9744525181e99974c899b39f75b2b29a624e2"}, + {file = "coverage-7.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81a5fb41b0d24447a47543b749adc34d45a2cf77b48ca74e5bf3de60a7bd9edc"}, + {file = "coverage-7.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2412e98e70f16243be41d20836abd5f3f32edef07cbf8f407f1b6e1ceae783ac"}, + {file = "coverage-7.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb79414c15c6f03f56cc68fa06994f047cf20207c31b5dad3f6bab54a0f66ef"}, + {file = "coverage-7.4.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf89ab85027427d351f1de918aff4b43f4eb5f33aff6835ed30322a86ac29c9e"}, + {file = "coverage-7.4.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a178b7b1ac0f1530bb28d2e51f88c0bab3e5949835851a60dda80bff6052510c"}, + {file = "coverage-7.4.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:06fe398145a2e91edaf1ab4eee66149c6776c6b25b136f4a86fcbbb09512fd10"}, + {file = "coverage-7.4.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:18cac867950943fe93d6cd56a67eb7dcd2d4a781a40f4c1e25d6f1ed98721a55"}, + {file = "coverage-7.4.2-cp311-cp311-win32.whl", hash = "sha256:f72cdd2586f9a769570d4b5714a3837b3a59a53b096bb954f1811f6a0afad305"}, + {file = "coverage-7.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:d779a48fac416387dd5673fc5b2d6bd903ed903faaa3247dc1865c65eaa5a93e"}, + {file = "coverage-7.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:adbdfcda2469d188d79771d5696dc54fab98a16d2ef7e0875013b5f56a251047"}, + {file = "coverage-7.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac4bab32f396b03ebecfcf2971668da9275b3bb5f81b3b6ba96622f4ef3f6e17"}, + {file = "coverage-7.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:006d220ba2e1a45f1de083d5022d4955abb0aedd78904cd5a779b955b019ec73"}, + {file = "coverage-7.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3733545eb294e5ad274abe131d1e7e7de4ba17a144505c12feca48803fea5f64"}, + {file = "coverage-7.4.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42a9e754aa250fe61f0f99986399cec086d7e7a01dd82fd863a20af34cbce962"}, + {file = "coverage-7.4.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2ed37e16cf35c8d6e0b430254574b8edd242a367a1b1531bd1adc99c6a5e00fe"}, + {file = "coverage-7.4.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b953275d4edfab6cc0ed7139fa773dfb89e81fee1569a932f6020ce7c6da0e8f"}, + {file = "coverage-7.4.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32b4ab7e6c924f945cbae5392832e93e4ceb81483fd6dc4aa8fb1a97b9d3e0e1"}, + {file = "coverage-7.4.2-cp312-cp312-win32.whl", hash = "sha256:f5df76c58977bc35a49515b2fbba84a1d952ff0ec784a4070334dfbec28a2def"}, + {file = "coverage-7.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:34423abbaad70fea9d0164add189eabaea679068ebdf693baa5c02d03e7db244"}, + {file = "coverage-7.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b11f9c6587668e495cc7365f85c93bed34c3a81f9f08b0920b87a89acc13469"}, + {file = "coverage-7.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:51593a1f05c39332f623d64d910445fdec3d2ac2d96b37ce7f331882d5678ddf"}, + {file = "coverage-7.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69f1665165ba2fe7614e2f0c1aed71e14d83510bf67e2ee13df467d1c08bf1e8"}, + {file = "coverage-7.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3c8bbb95a699c80a167478478efe5e09ad31680931ec280bf2087905e3b95ec"}, + {file = "coverage-7.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:175f56572f25e1e1201d2b3e07b71ca4d201bf0b9cb8fad3f1dfae6a4188de86"}, + {file = "coverage-7.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8562ca91e8c40864942615b1d0b12289d3e745e6b2da901d133f52f2d510a1e3"}, + {file = "coverage-7.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a1ef0f173e1a19738f154fb3644f90d0ada56fe6c9b422f992b04266c55d5a"}, + {file = "coverage-7.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f40ac873045db4fd98a6f40387d242bde2708a3f8167bd967ccd43ad46394ba2"}, + {file = "coverage-7.4.2-cp38-cp38-win32.whl", hash = "sha256:d1b750a8409bec61caa7824bfd64a8074b6d2d420433f64c161a8335796c7c6b"}, + {file = "coverage-7.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b4ae777bebaed89e3a7e80c4a03fac434a98a8abb5251b2a957d38fe3fd30088"}, + {file = "coverage-7.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ff7f92ae5a456101ca8f48387fd3c56eb96353588e686286f50633a611afc95"}, + {file = "coverage-7.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:861d75402269ffda0b33af94694b8e0703563116b04c681b1832903fac8fd647"}, + {file = "coverage-7.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3507427d83fa961cbd73f11140f4a5ce84208d31756f7238d6257b2d3d868405"}, + {file = "coverage-7.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf711d517e21fb5bc429f5c4308fbc430a8585ff2a43e88540264ae87871e36a"}, + {file = "coverage-7.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c00e54f0bd258ab25e7f731ca1d5144b0bf7bec0051abccd2bdcff65fa3262c9"}, + {file = "coverage-7.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f8e845d894e39fb53834da826078f6dc1a933b32b1478cf437007367efaf6f6a"}, + {file = "coverage-7.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:840456cb1067dc350af9080298c7c2cfdddcedc1cb1e0b30dceecdaf7be1a2d3"}, + {file = "coverage-7.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c11ca2df2206a4e3e4c4567f52594637392ed05d7c7fb73b4ea1c658ba560265"}, + {file = "coverage-7.4.2-cp39-cp39-win32.whl", hash = "sha256:3ff5bdb08d8938d336ce4088ca1a1e4b6c8cd3bef8bb3a4c0eb2f37406e49643"}, + {file = "coverage-7.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:ac9e95cefcf044c98d4e2c829cd0669918585755dd9a92e28a1a7012322d0a95"}, + {file = "coverage-7.4.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:f593a4a90118d99014517c2679e04a4ef5aee2d81aa05c26c734d271065efcb6"}, + {file = "coverage-7.4.2.tar.gz", hash = "sha256:1a5ee18e3a8d766075ce9314ed1cb695414bae67df6a4b0805f5137d93d6f1cb"}, ] [package.dependencies] @@ -699,74 +725,87 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.4" +version = "42.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.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"}, + {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, + {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, + {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, + {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, + {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, + {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, + {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, + {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [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)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "debugpy" -version = "1.8.0" +version = "1.8.1" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7fb95ca78f7ac43393cd0e0f2b6deda438ec7c5e47fa5d38553340897d2fbdfb"}, - {file = "debugpy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9ab7df0b9a42ed9c878afd3eaaff471fce3fa73df96022e1f5c9f8f8c87ada"}, - {file = "debugpy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:a8b7a2fd27cd9f3553ac112f356ad4ca93338feadd8910277aff71ab24d8775f"}, - {file = "debugpy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d9de202f5d42e62f932507ee8b21e30d49aae7e46d5b1dd5c908db1d7068637"}, - {file = "debugpy-1.8.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ef54404365fae8d45cf450d0544ee40cefbcb9cb85ea7afe89a963c27028261e"}, - {file = "debugpy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60009b132c91951354f54363f8ebdf7457aeb150e84abba5ae251b8e9f29a8a6"}, - {file = "debugpy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:8cd0197141eb9e8a4566794550cfdcdb8b3db0818bdf8c49a8e8f8053e56e38b"}, - {file = "debugpy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a64093656c4c64dc6a438e11d59369875d200bd5abb8f9b26c1f5f723622e153"}, - {file = "debugpy-1.8.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b05a6b503ed520ad58c8dc682749113d2fd9f41ffd45daec16e558ca884008cd"}, - {file = "debugpy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c6fb41c98ec51dd010d7ed650accfd07a87fe5e93eca9d5f584d0578f28f35f"}, - {file = "debugpy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:46ab6780159eeabb43c1495d9c84cf85d62975e48b6ec21ee10c95767c0590aa"}, - {file = "debugpy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:bdc5ef99d14b9c0fcb35351b4fbfc06ac0ee576aeab6b2511702e5a648a2e595"}, - {file = "debugpy-1.8.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:61eab4a4c8b6125d41a34bad4e5fe3d2cc145caecd63c3fe953be4cc53e65bf8"}, - {file = "debugpy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125b9a637e013f9faac0a3d6a82bd17c8b5d2c875fb6b7e2772c5aba6d082332"}, - {file = "debugpy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:57161629133113c97b387382045649a2b985a348f0c9366e22217c87b68b73c6"}, - {file = "debugpy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:e3412f9faa9ade82aa64a50b602544efcba848c91384e9f93497a458767e6926"}, - {file = "debugpy-1.8.0-py2.py3-none-any.whl", hash = "sha256:9c9b0ac1ce2a42888199df1a1906e45e6f3c9555497643a85e0bf2406e3ffbc4"}, - {file = "debugpy-1.8.0.zip", hash = "sha256:12af2c55b419521e33d5fb21bd022df0b5eb267c3e178f1d374a63a2a6bdccd0"}, + {file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"}, + {file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"}, + {file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"}, + {file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"}, + {file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"}, + {file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"}, + {file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"}, + {file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"}, + {file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"}, + {file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"}, + {file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"}, + {file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"}, + {file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"}, + {file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"}, + {file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"}, + {file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"}, + {file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"}, + {file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"}, + {file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"}, + {file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"}, + {file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"}, + {file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"}, ] [[package]] @@ -842,13 +881,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -856,13 +895,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.0.0" +version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, - {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, ] [package.extras] @@ -870,39 +909,40 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "extract-msg" -version = "0.45.0" +version = "0.47.0" description = "Extracts emails and attachments saved in Microsoft Outlook's .msg files" optional = true python-versions = ">=3.8" files = [ - {file = "extract_msg-0.45.0-py2.py3-none-any.whl", hash = "sha256:af645ffe1534bce93b20390576dac2aee027c17a714365172d31b3894f810ca7"}, - {file = "extract_msg-0.45.0.tar.gz", hash = "sha256:6814865cf2ba806bd69af53af688a13e000a95d4991cce6a0416b3bdeb739496"}, + {file = "extract_msg-0.47.0-py2.py3-none-any.whl", hash = "sha256:ab177546d6ebbea7818e9acb352f6f8cce3821e39319405e6a873808238564a5"}, + {file = "extract_msg-0.47.0.tar.gz", hash = "sha256:d3ed5fdc8cdff3567421d7e4183511905eb3c83d2605e6c9335c653efa6cfb41"}, ] [package.dependencies] beautifulsoup4 = ">=4.11.1,<4.13" compressed-rtf = ">=1.0.6,<2" ebcdic = ">=1.1.1,<2" -imapclient = ">=2.3.0,<3" -olefile = "0.46" +olefile = "0.47" red-black-tree-mod = "1.20" -RTFDE = ">=0.1.0,<0.2" +RTFDE = ">=0.1.1,<0.2" tzlocal = ">=4.2,<6" [package.extras] -all = ["extract-msg[image]", "extract-msg[mime]"] +all = ["extract-msg[encoding]", "extract-msg[image]", "extract-msg[mime]"] +encoding = ["chardet (>=3.0.0,<6)"] image = ["Pillow (>=9.5.0,<10)"] mime = ["python-magic (>=0.4.27,<0.5)"] +readthedocs = ["sphinx-rtd-theme"] [[package]] name = "fastjsonschema" -version = "2.18.1" +version = "2.19.1" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" files = [ - {file = "fastjsonschema-2.18.1-py3-none-any.whl", hash = "sha256:aec6a19e9f66e9810ab371cc913ad5f4e9e479b63a7072a2cd060a9369e329a8"}, - {file = "fastjsonschema-2.18.1.tar.gz", hash = "sha256:06dc8680d937628e993fa0cd278f196d20449a1adc087640710846b324d422ea"}, + {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, + {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, ] [package.extras] @@ -919,15 +959,71 @@ files = [ {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, ] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.4" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.25.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -941,52 +1037,34 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -[[package]] -name = "imapclient" -version = "2.3.1" -description = "Easy-to-use, Pythonic and complete IMAP client library" -optional = true -python-versions = "*" -files = [ - {file = "IMAPClient-2.3.1-py2.py3-none-any.whl", hash = "sha256:057f28025d2987c63e065afb0e4370b0b850b539b0e1494cea0427e88130108c"}, - {file = "IMAPClient-2.3.1.zip", hash = "sha256:26ea995664fae3a88b878ebce2aff7402931697b86658b7882043ddb01b0e6ba"}, -] - -[package.dependencies] -six = "*" - -[package.extras] -doc = ["sphinx"] -test = ["mock (>=1.3.0)"] - [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "7.0.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "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"] perf = ["ipython"] 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 = "importlib-resources" -version = "6.1.0" +version = "6.1.1" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, - {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, + {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, + {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, ] [package.dependencies] @@ -1009,13 +1087,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.25.2" +version = "6.29.2" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.25.2-py3-none-any.whl", hash = "sha256:2e2ee359baba19f10251b99415bb39de1e97d04e1fab385646f24f0596510b77"}, - {file = "ipykernel-6.25.2.tar.gz", hash = "sha256:f468ddd1f17acb48c8ce67fcfa49ba6d46d4f9ac0438c1f441be7c3d1372230b"}, + {file = "ipykernel-6.29.2-py3-none-any.whl", hash = "sha256:50384f5c577a260a1d53f1f59a828c7266d321c9b7d00d345693783f66616055"}, + {file = "ipykernel-6.29.2.tar.gz", hash = "sha256:3bade28004e3ff624ed57974948116670604ac5f676d12339693f3142176d3f0"}, ] [package.dependencies] @@ -1029,7 +1107,7 @@ matplotlib-inline = ">=0.1" nest-asyncio = "*" packaging = "*" psutil = "*" -pyzmq = ">=20" +pyzmq = ">=24" tornado = ">=6.1" traitlets = ">=5.4.0" @@ -1038,7 +1116,7 @@ cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] pyqt5 = ["pyqt5"] pyside6 = ["pyside6"] -test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov", "pytest-timeout"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (==0.23.4)", "pytest-cov", "pytest-timeout"] [[package]] name = "ipython" @@ -1081,43 +1159,76 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa [[package]] name = "ipython" -version = "8.16.1" +version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" files = [ - {file = "ipython-8.16.1-py3-none-any.whl", hash = "sha256:0852469d4d579d9cd613c220af7bf0c9cc251813e12be647cb9d463939db9b1e"}, - {file = "ipython-8.16.1.tar.gz", hash = "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938"}, + {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, + {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, ] [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" typing-extensions = {version = "*", markers = "python_version < \"3.10\""} [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] + +[[package]] +name = "ipython" +version = "8.22.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.22.0-py3-none-any.whl", hash = "sha256:a3e962e1d42927f5825dab048f4de617f5856cf3272fff1cb9245bea0c785a46"}, + {file = "ipython-8.22.0.tar.gz", hash = "sha256:bc649987e35a75ecccab7a245d7403710e3a289384c268d6d846ab8933ca0811"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,terminal]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "stack-data", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "isoduration" @@ -1154,13 +1265,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -1171,13 +1282,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "json5" -version = "0.9.14" +version = "0.9.17" description = "A Python implementation of the JSON5 data format." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"}, - {file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"}, + {file = "json5-0.9.17-py2.py3-none-any.whl", hash = "sha256:f8ec1ecf985951d70f780f6f877c4baca6a47b6e61e02c4cd190138d10a7805a"}, + {file = "json5-0.9.17.tar.gz", hash = "sha256:717d99d657fa71b7094877b1d921b1cce40ab444389f6d770302563bb7dfd9ae"}, ] [package.extras] @@ -1191,17 +1302,18 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, ] [[package]] name = "jsonschema" -version = "4.19.1" +version = "4.21.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, - {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, ] [package.dependencies] @@ -1226,28 +1338,28 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.7.1" +version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, - {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, ] [package.dependencies] importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} -referencing = ">=0.28.0" +referencing = ">=0.31.0" [[package]] name = "jupyter-client" -version = "8.4.0" +version = "8.6.0" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_client-8.4.0-py3-none-any.whl", hash = "sha256:6a2a950ec23a8f62f9e4c66acec7f0ea6c7d1f80ba0992e747b10c56ce2e6dbe"}, - {file = "jupyter_client-8.4.0.tar.gz", hash = "sha256:dc1b857d5d7d76ac101766c6e9b646bf18742721126e72e5d484c75a993cada2"}, + {file = "jupyter_client-8.6.0-py3-none-any.whl", hash = "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99"}, + {file = "jupyter_client-8.6.0.tar.gz", hash = "sha256:0642244bb83b4764ae60d07e010e15f0e2d275ec4e918a8f7b80fbbef3ca60c7"}, ] [package.dependencies] @@ -1264,13 +1376,13 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt [[package]] name = "jupyter-core" -version = "5.4.0" +version = "5.7.1" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_core-5.4.0-py3-none-any.whl", hash = "sha256:66e252f675ac04dcf2feb6ed4afb3cd7f68cf92f483607522dc251f32d471571"}, - {file = "jupyter_core-5.4.0.tar.gz", hash = "sha256:e4b98344bb94ee2e3e6c4519a97d001656009f9cb2b7f2baf15b3c205770011d"}, + {file = "jupyter_core-5.7.1-py3-none-any.whl", hash = "sha256:c65c82126453a723a2804aa52409930434598fd9d35091d63dfb919d2b765bb7"}, + {file = "jupyter_core-5.7.1.tar.gz", hash = "sha256:de61a9d7fc71240f688b2fb5ab659fbb56979458dc66a71decd098e03c79e218"}, ] [package.dependencies] @@ -1279,18 +1391,18 @@ pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_ traitlets = ">=5.3" [package.extras] -docs = ["myst-parser", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "jupyter-events" -version = "0.8.0" +version = "0.9.0" description = "Jupyter Event System library" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_events-0.8.0-py3-none-any.whl", hash = "sha256:81f07375c7673ff298bfb9302b4a981864ec64edaed75ca0fe6f850b9b045525"}, - {file = "jupyter_events-0.8.0.tar.gz", hash = "sha256:fda08f0defce5e16930542ce60634ba48e010830d50073c3dfd235759cee77bf"}, + {file = "jupyter_events-0.9.0-py3-none-any.whl", hash = "sha256:d853b3c10273ff9bc8bb8b30076d65e2c9685579db736873de6c2232dde148bf"}, + {file = "jupyter_events-0.9.0.tar.gz", hash = "sha256:81ad2e4bc710881ec274d31c6c50669d71bbaa5dd9d01e600b56faa85700d399"}, ] [package.dependencies] @@ -1309,13 +1421,13 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p [[package]] name = "jupyter-lsp" -version = "2.2.0" +version = "2.2.2" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter-lsp-2.2.0.tar.gz", hash = "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1"}, - {file = "jupyter_lsp-2.2.0-py3-none-any.whl", hash = "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"}, + {file = "jupyter-lsp-2.2.2.tar.gz", hash = "sha256:256d24620542ae4bba04a50fc1f6ffe208093a07d8e697fea0a8d1b8ca1b7e5b"}, + {file = "jupyter_lsp-2.2.2-py3-none-any.whl", hash = "sha256:3b95229e4168355a8c91928057c1621ac3510ba98b2a925e82ebd77f078b1aa5"}, ] [package.dependencies] @@ -1324,13 +1436,13 @@ jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.8.0" +version = "2.12.5" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.8.0-py3-none-any.whl", hash = "sha256:c57270faa6530393ae69783a2d2f1874c718b9f109080581ea076b05713249fa"}, - {file = "jupyter_server-2.8.0.tar.gz", hash = "sha256:b11e2ba80667c75f55630faf8ac3d5809f8734f9006d65cce117c46a0a516ab8"}, + {file = "jupyter_server-2.12.5-py3-none-any.whl", hash = "sha256:184a0f82809a8522777cfb6b760ab6f4b1bb398664c5860a27cec696cb884923"}, + {file = "jupyter_server-2.12.5.tar.gz", hash = "sha256:0edb626c94baa22809be1323f9770cf1c00a952b17097592e40d03e6a3951689"}, ] [package.dependencies] @@ -1339,7 +1451,7 @@ argon2-cffi = "*" jinja2 = "*" jupyter-client = ">=7.4.4" jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -jupyter-events = ">=0.6.0" +jupyter-events = ">=0.9.0" jupyter-server-terminals = "*" nbconvert = ">=6.4.4" nbformat = ">=5.3.0" @@ -1360,13 +1472,13 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc [[package]] name = "jupyter-server-terminals" -version = "0.4.4" +version = "0.5.2" description = "A Jupyter Server Extension Providing Terminals." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server_terminals-0.4.4-py3-none-any.whl", hash = "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"}, - {file = "jupyter_server_terminals-0.4.4.tar.gz", hash = "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d"}, + {file = "jupyter_server_terminals-0.5.2-py3-none-any.whl", hash = "sha256:1b80c12765da979513c42c90215481bbc39bd8ae7c0350b4f85bc3eb58d0fa80"}, + {file = "jupyter_server_terminals-0.5.2.tar.gz", hash = "sha256:396b5ccc0881e550bf0ee7012c6ef1b53edbde69e67cab1d56e89711b46052e8"}, ] [package.dependencies] @@ -1374,22 +1486,23 @@ pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} terminado = ">=0.8.3" [package.extras] -docs = ["jinja2", "jupyter-server", "mistune (<3.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] -test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] +docs = ["jinja2", "jupyter-server", "mistune (<4.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] [[package]] name = "jupyterlab" -version = "4.0.7" +version = "4.1.2" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.0.7-py3-none-any.whl", hash = "sha256:08683045117cc495531fdb39c22ababb9aaac6977a45e67cfad20046564c9c7c"}, - {file = "jupyterlab-4.0.7.tar.gz", hash = "sha256:48792efd9f962b2bcda1f87d72168ff122c288b1d97d32109e4a11b33dc862be"}, + {file = "jupyterlab-4.1.2-py3-none-any.whl", hash = "sha256:aa88193f03cf4d3555f6712f04d74112b5eb85edd7d222c588c7603a26d33c5b"}, + {file = "jupyterlab-4.1.2.tar.gz", hash = "sha256:5d6348b3ed4085181499f621b7dfb6eb0b1f57f3586857aadfc8e3bf4c4885f9"}, ] [package.dependencies] async-lru = ">=1.0.0" +httpx = ">=0.25.0" importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} importlib-resources = {version = ">=1.4", markers = "python_version < \"3.9\""} ipykernel = "*" @@ -1405,31 +1518,31 @@ tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["black[jupyter] (==23.7.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.286)"] -docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] -docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.2.0)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<7.3.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.2.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.1)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post6)", "matplotlib (==3.8.2)", "nbconvert (>=7.0.0)", "pandas (==2.2.0)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] [[package]] name = "jupyterlab-pygments" -version = "0.2.2" +version = "0.3.0" description = "Pygments theme using JupyterLab CSS variables" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, - {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, ] [[package]] name = "jupyterlab-server" -version = "2.25.0" +version = "2.25.3" description = "A set of server components for JupyterLab and JupyterLab like applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab_server-2.25.0-py3-none-any.whl", hash = "sha256:c9f67a98b295c5dee87f41551b0558374e45d449f3edca153dd722140630dcb2"}, - {file = "jupyterlab_server-2.25.0.tar.gz", hash = "sha256:77c2f1f282d610f95e496e20d5bf1d2a7706826dfb7b18f3378ae2870d272fb7"}, + {file = "jupyterlab_server-2.25.3-py3-none-any.whl", hash = "sha256:c48862519fded9b418c71645d85a49b2f0ec50d032ba8316738e9276046088c1"}, + {file = "jupyterlab_server-2.25.3.tar.gz", hash = "sha256:846f125a8a19656611df5b03e5912c8393cea6900859baa64fa515eb64a8dc40"}, ] [package.dependencies] @@ -1445,113 +1558,131 @@ requests = ">=2.31" [package.extras] docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] -test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.7.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] +test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] [[package]] name = "lark" -version = "1.1.5" +version = "1.1.8" description = "a modern parsing library" optional = true -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "lark-1.1.5-py3-none-any.whl", hash = "sha256:8476f9903e93fbde4f6c327f74d79e9b4bd0ed9294c5dfa3164ab8c581b5de2a"}, - {file = "lark-1.1.5.tar.gz", hash = "sha256:4b534eae1f9af5b4ea000bea95776350befe1981658eea3820a01c37e504bb4d"}, + {file = "lark-1.1.8-py3-none-any.whl", hash = "sha256:7d2c221a66a8165f3f81aacb958d26033d40d972fdb70213ab0a2e0627e29c86"}, + {file = "lark-1.1.8.tar.gz", hash = "sha256:7ef424db57f59c1ffd6f0d4c2b705119927f566b68c0fe1942dddcc0e44391a5"}, ] [package.extras] atomic-cache = ["atomicwrites"] +interegular = ["interegular (>=0.3.1,<0.4.0)"] nearley = ["js2py"] regex = ["regex"] [[package]] name = "lief" -version = "0.13.2" +version = "0.14.1" description = "Library to instrument executable formats" optional = true python-versions = ">=3.8" files = [ - {file = "lief-0.13.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:0390cfaaf0e9aed46bebf26f00f34852768f76bc7f90abf7ceb384566200e5f5"}, - {file = "lief-0.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5581bf0072c1e7a9ea2fb2e2252b8582016e8b298804b5461e552b402c9cd4e9"}, - {file = "lief-0.13.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:dbbf2fb3d7807e815f345c77e287da162e081100f059ec03005995befc295d7f"}, - {file = "lief-0.13.2-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:d344d37334c2b488dc02f04cb13c22cd61aa065eeb9bca7424588e0c8c23bdfb"}, - {file = "lief-0.13.2-cp310-cp310-win32.whl", hash = "sha256:bc041b28b94139843a33c014e355822a9276b35f3c5ae10d82da56bf572f8222"}, - {file = "lief-0.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:01d4075bbc3541e9dd3ef008045fa1eb128294a0c5b0c1f69ce60d8948d248c7"}, - {file = "lief-0.13.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6570dacebe107ad60c2ba0968d1a865d316009d43cc85af3719d3eeb0911abf3"}, - {file = "lief-0.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ce2e3f7c791efba327c2bb3499dbef81e682027109045a9bae696c62e2aeeb0"}, - {file = "lief-0.13.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:11ab900e0644b6735ecdef2bbd04439b4866a527650fc054470c195d6cfe2917"}, - {file = "lief-0.13.2-cp311-cp311-manylinux_2_24_x86_64.whl", hash = "sha256:042ad2105a136b11a7494b9af8178468e8cb32b8fa2a0a55cb659a5605aeb069"}, - {file = "lief-0.13.2-cp311-cp311-win32.whl", hash = "sha256:1ce289b6ab3cf4be654270007e8a2c0d2e42116180418c29d3ce83762955de63"}, - {file = "lief-0.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:eccb248ffb598e410fd2ef7c1f171a3cde57a40c9bb8c4fa15d8e7b90eb4eb2d"}, - {file = "lief-0.13.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:95731cadedd6ffc5fb48c147fcefe004624e436b75e8ee9fb2dbf2ae5f084342"}, - {file = "lief-0.13.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8da75df0ea472557fcc37a27ba583bad5a8f3a256c186600d00a6dd0a57f718a"}, - {file = "lief-0.13.2-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:b99092f02c13f580c2d00b504af224b7e60e7c98a791e72ae8519f530b7687bb"}, - {file = "lief-0.13.2-cp38-cp38-win32.whl", hash = "sha256:03db0138e4dbbdfa8bba74de312b0cebb30f504e44f38a9c8918b84022da340b"}, - {file = "lief-0.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:36c5bea3f8460dee3ebb75d35949f445638ec85d2871f31e293c47fb4a0a5af7"}, - {file = "lief-0.13.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:eca8ecbcae1ad851ed7cf1e22ec8accd74f2267fa7375194559fb917523d8a92"}, - {file = "lief-0.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8703cb5308b4828563badc6885ff07a3926ec3403d1caa3aa75f24fe9cbcf84"}, - {file = "lief-0.13.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c60f2f79e7d0d1f18dec7dcdb4d4f35e6b126ac29e2f2f056d28ec50599d868a"}, - {file = "lief-0.13.2-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:e0f84a7443b7f1b02666fd16a9aa57f5d9027e60ba2885e0d76db8426d689707"}, - {file = "lief-0.13.2-cp39-cp39-win32.whl", hash = "sha256:3f8f251de874929d9c9e94a35891621ab8c059149f8a1c24e543fd9cf0c2a31c"}, - {file = "lief-0.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:2bbe294385e629aa7206b2f39f0ca34e3948605a8db50b22091603053889a759"}, + {file = "lief-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a9a94882f9af110fb01b4558a58941d2352b9a4ae3fef15570a3fab921ff462"}, + {file = "lief-0.14.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:bcc06f24f64fa6f20372d625ce60c40a7a6f669e11bdd02c2f0b8c5c6d09a5ee"}, + {file = "lief-0.14.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d22f804eee7f1b4a4b37e7a3d35e2003c4c054f3450d40389e54c8ac9fc2a5db"}, + {file = "lief-0.14.1-cp310-cp310-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:26134815adecfd7f15dfbdf12cc64df25bcf3d0db917cf115fc3b296d09be496"}, + {file = "lief-0.14.1-cp310-cp310-win32.whl", hash = "sha256:6ca0220189698599df30b8044f43fb1fc7ba919fb9ef6047c892f9faee16393a"}, + {file = "lief-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:c321234b50997c217107c09e69f53518c37fac637f8735c968c258dd4c748fb2"}, + {file = "lief-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ca365c704c6b6b1ce631b92fea2eddaf93d66c897a0ec4ab51e9ab9e3345920"}, + {file = "lief-0.14.1-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:1f3c40eadff07a4c8fa74f1e268f9fa70b68f39b6795a00cd82160ca6782d5c3"}, + {file = "lief-0.14.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:c202ed13b641db2e1f8a24743fb0c85595b32ea92cc3c517d3f7a9977e16dcb4"}, + {file = "lief-0.14.1-cp311-cp311-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:fd481bfdfef04e8be4d200bca771d0d9394d9146c6cd403f9e58c80c4196a24e"}, + {file = "lief-0.14.1-cp311-cp311-win32.whl", hash = "sha256:473e9a37beef8db8bab1a777271aa49cce44dfe35af65cb8fad576377518c0bd"}, + {file = "lief-0.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:24f687244e14d4a8307430babc5c712a1dd4e519172886ad4aeb9825f88f7569"}, + {file = "lief-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6df40e3750b8b26f88a6b28ac01db7338cdb6158f28363c755bf36452ce20d28"}, + {file = "lief-0.14.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:e7f7a55db2fcf269569f9e9fa5ea752620396de17bd9d29fc8b29e176975ecdb"}, + {file = "lief-0.14.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:50795b51884b76a78c481d6d069d992561c217180bd81cf12554180389eff0a3"}, + {file = "lief-0.14.1-cp312-cp312-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:497b88f9c9aaae999766ba188744ee35c5f38b4b64016f7dbb7037e9bf325382"}, + {file = "lief-0.14.1-cp312-cp312-win32.whl", hash = "sha256:08bad88083f696915f8dcda4042a3bfc514e17462924ec8984085838b2261921"}, + {file = "lief-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:e131d6158a085f8a72124136816fefc29405c725cd3695ce22a904e471f0f815"}, + {file = "lief-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:df650fa05ca131e4dfeb42c77985e1eb239730af9944bc0aadb1dfac8576e0e8"}, + {file = "lief-0.14.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b4e76eeb48ca2925c6ca6034d408582615f2faa855f9bb11482e7acbdecc4803"}, + {file = "lief-0.14.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:016e4fac91303466024154dd3c4b599e8b7c52882f72038b62a2be386d98c8f9"}, + {file = "lief-0.14.1-cp38-cp38-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:9a5c7732a3ce53b306c8180ab64fdfb36d8cd9df91aedd9e2b4dad9faf47492b"}, + {file = "lief-0.14.1-cp38-cp38-win32.whl", hash = "sha256:7030c22a4446ea2ac673fd50128e9c639121c0a4dae11ca1cd8cc20d62d26e7e"}, + {file = "lief-0.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a35ceeee74bb9bb4c7171f4bca814576a3aa6dec16a0a9469e5743db0a9ba0c"}, + {file = "lief-0.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abb15e4de34e70661fd35e87e2634abf0ae57a8c8ac78d02ad4259f5a5817e26"}, + {file = "lief-0.14.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:33d062340c709c1a33539d221ea3cb764cbb8d7c9ee8aae28bf9797bc8715a0b"}, + {file = "lief-0.14.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:66deb1b26de43acb2fd0b2fc5e6be70093eaaa93797332cc4613e163164c77e7"}, + {file = "lief-0.14.1-cp39-cp39-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:c1c15bd3e5b15da6dcc0ba75d5549f15bfbf9214c0d8e3938f85877a40c352d9"}, + {file = "lief-0.14.1-cp39-cp39-win32.whl", hash = "sha256:ebcbe4eadd33d8cf2c6015f44d6c9b72f81388af745938e633c4bb90262b2036"}, + {file = "lief-0.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:2db3eb282a35daf51f89c6509226668a08fb6a6d1f507dd549dd9f077585db11"}, ] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {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"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -1581,13 +1712,13 @@ files = [ [[package]] name = "msoffcrypto-tool" -version = "5.1.1" +version = "5.3.1" description = "Python tool and library for decrypting MS Office files with passwords or other keys" optional = true python-versions = ">=3.8,<4.0" files = [ - {file = "msoffcrypto_tool-5.1.1-py3-none-any.whl", hash = "sha256:27475aaf8a70485471ad86426c0be10ee4e24c6fad70335e4a8f88d2da323ca1"}, - {file = "msoffcrypto_tool-5.1.1.tar.gz", hash = "sha256:5585a303fa3ee49eec0253f912be17b82cf83f13f0f7489b4ea10f4ecb285278"}, + {file = "msoffcrypto_tool-5.3.1-py3-none-any.whl", hash = "sha256:4e44c10e38ca06d0ea511a31ee8834bfdedaf304b1369a0d3919db4f495dd5e4"}, + {file = "msoffcrypto_tool-5.3.1.tar.gz", hash = "sha256:f800ff133b9a753dfedff6a37b0f79bfeb8cc6856487b91dd486110c7d4f4099"}, ] [package.dependencies] @@ -1596,38 +1727,38 @@ olefile = ">=0.46" [[package]] name = "mypy" -version = "1.6.1" +version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, - {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, - {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, - {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, - {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, - {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, - {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, - {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, - {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, - {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, - {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, - {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, - {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, - {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, - {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, - {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, - {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, - {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, - {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, - {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, - {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, - {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, - {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, - {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, - {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, - {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, - {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] @@ -1638,6 +1769,7 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -1653,13 +1785,13 @@ files = [ [[package]] name = "nbclient" -version = "0.8.0" +version = "0.9.0" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." optional = false python-versions = ">=3.8.0" files = [ - {file = "nbclient-0.8.0-py3-none-any.whl", hash = "sha256:25e861299e5303a0477568557c4045eccc7a34c17fc08e7959558707b9ebe548"}, - {file = "nbclient-0.8.0.tar.gz", hash = "sha256:f9b179cd4b2d7bca965f900a2ebf0db4a12ebff2f36a711cb66861e4ae158e55"}, + {file = "nbclient-0.9.0-py3-none-any.whl", hash = "sha256:a3a1ddfb34d4a9d17fc744d655962714a866639acd30130e9be84191cd97cd15"}, + {file = "nbclient-0.9.0.tar.gz", hash = "sha256:4b28c207877cf33ef3a9838cdc7a54c5ceff981194a82eac59d558f05487295e"}, ] [package.dependencies] @@ -1675,13 +1807,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.9.2" -description = "Converting Jupyter Notebooks" +version = "7.16.1" +description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." optional = false python-versions = ">=3.8" files = [ - {file = "nbconvert-7.9.2-py3-none-any.whl", hash = "sha256:39fe4b8bdd1b0104fdd86fc8a43a9077ba64c720bda4c6132690d917a0a154ee"}, - {file = "nbconvert-7.9.2.tar.gz", hash = "sha256:e56cc7588acc4f93e2bb5a34ec69028e4941797b2bfaf6462f18a41d1cc258c9"}, + {file = "nbconvert-7.16.1-py3-none-any.whl", hash = "sha256:3188727dffadfdc9c6a1c7250729063d7bc78b355ad7aa023138afa030d1cd07"}, + {file = "nbconvert-7.16.1.tar.gz", hash = "sha256:e79e6a074f49ba3ed29428ed86487bf51509d9aab613bd8522ac08f6d28fd7fd"}, ] [package.dependencies] @@ -1708,7 +1840,7 @@ docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sp qtpdf = ["nbconvert[qtpng]"] qtpng = ["pyqtwebengine (>=5.15)"] serve = ["tornado (>=6.1)"] -test = ["flaky", "ipykernel", "ipywidgets (>=7)", "pytest", "pytest-dependency"] +test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest"] webpdf = ["playwright"] [[package]] @@ -1734,24 +1866,24 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] [[package]] name = "nest-asyncio" -version = "1.5.8" +version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" files = [ - {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, - {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] [[package]] name = "notebook-shim" -version = "0.2.3" +version = "0.2.4" description = "A shim layer for notebook traits and config" optional = false python-versions = ">=3.7" files = [ - {file = "notebook_shim-0.2.3-py3-none-any.whl", hash = "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7"}, - {file = "notebook_shim-0.2.3.tar.gz", hash = "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"}, + {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, + {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, ] [package.dependencies] @@ -1762,14 +1894,18 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" [[package]] name = "olefile" -version = "0.46" +version = "0.47" description = "Python package to parse, read and write Microsoft OLE2 files (Structured Storage or Compound Document, Microsoft Office)" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"}, + {file = "olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f"}, + {file = "olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c"}, ] +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "oletools" version = "0.60.1" @@ -1794,13 +1930,13 @@ full = ["XLMMacroDeobfuscator"] [[package]] name = "overrides" -version = "7.4.0" +version = "7.7.0" description = "A decorator to automatically detect mismatch when overriding a method." optional = false python-versions = ">=3.6" files = [ - {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, - {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, + {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, + {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, ] [[package]] @@ -1816,13 +1952,13 @@ files = [ [[package]] name = "pandocfilters" -version = "1.5.0" +version = "1.5.1" description = "Utilities for writing pandoc filters in python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, - {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, ] [[package]] @@ -1857,13 +1993,13 @@ win-unicode-console = {version = "*", markers = "platform_system == \"Windows\" [[package]] name = "pexpect" -version = "4.8.0" +version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" files = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] @@ -1882,70 +2018,88 @@ files = [ [[package]] name = "pillow" -version = "10.1.0" +version = "10.2.0" description = "Python Imaging Library (Fork)" optional = true python-versions = ">=3.8" files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "pkgutil-resolve-name" @@ -1960,28 +2114,28 @@ files = [ [[package]] name = "platformdirs" -version = "3.11.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -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)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -1990,13 +2144,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prometheus-client" -version = "0.17.1" +version = "0.20.0" description = "Python client for the Prometheus monitoring system." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, - {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, + {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"}, + {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"}, ] [package.extras] @@ -2004,13 +2158,13 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.39" +version = "3.0.43" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] [package.dependencies] @@ -2018,27 +2172,27 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.6" +version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"}, - {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"}, - {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"}, - {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"}, - {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"}, - {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"}, - {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"}, - {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, - {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"}, - {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"}, - {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, - {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, - {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, - {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] [package.extras] @@ -2057,13 +2211,13 @@ files = [ [[package]] name = "publicsuffixlist" -version = "0.10.0.20231022" +version = "0.10.0.20240214" description = "publicsuffixlist implement" optional = true python-versions = ">=2.6" files = [ - {file = "publicsuffixlist-0.10.0.20231022-py2.py3-none-any.whl", hash = "sha256:97eb6e2f87d3ea913144ef218ea1851fe3e3241a7b2f0c84ac0b61d87816e2aa"}, - {file = "publicsuffixlist-0.10.0.20231022.tar.gz", hash = "sha256:b179a7b6e92be26f3beb1e07bb8e6d1e54f4d6b236f25ed84f9f6ed935bc4d5e"}, + {file = "publicsuffixlist-0.10.0.20240214-py2.py3-none-any.whl", hash = "sha256:2c3b8da819571bb610328bda5b25d27fcbf6bc400896ca3c6502d291a16b32f4"}, + {file = "publicsuffixlist-0.10.0.20240214.tar.gz", hash = "sha256:45a206c5f9c1eccf138481280cfb0a67c2ccafc782ef89c7fd6dc6c4356230fe"}, ] [package.extras] @@ -2106,6 +2260,8 @@ files = [ {file = "pydeep2-0.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2283893e25826b547dd1e5c71a010e86ddfd7270e2f2b8c90973c1d7984c7eb7"}, {file = "pydeep2-0.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f248e3161deb53d46a9368a7c164e36d83004faf2f11625d47a5cf23a6bdd2cb"}, {file = "pydeep2-0.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a13fca9be89a9fa8d92a4f49d7b9191eef94555f8ddf030fb2be4c8c15ad618c"}, + {file = "pydeep2-0.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cb4757db97ac15ddf034c21cd6bab984f841586b6d53984e63c9a7803b2cd4"}, + {file = "pydeep2-0.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7809a1d6640bdbee68f075d53229d05229e11b4711f232728dd540f68e6483a4"}, {file = "pydeep2-0.5.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fedc1c9660cb5d0b73ad0b5f1dbffe16990e6721cbfc6454571a4b9882d0ea4"}, {file = "pydeep2-0.5.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca68f7d63e2ef510d410d20b223e8e97df41707fb50c4c526b6dd1d8698d9e6"}, {file = "pydeep2-0.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:199d05d8b4b7544509a2ba4802ead4b41dfe7859e0ecea9d9be9e41939f11660"}, @@ -2130,17 +2286,18 @@ files = [ [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyparsing" @@ -2155,13 +2312,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.2" +version = "8.0.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-8.0.1-py3-none-any.whl", hash = "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca"}, + {file = "pytest-8.0.1.tar.gz", hash = "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae"}, ] [package.dependencies] @@ -2169,7 +2326,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=1.3.0,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] @@ -2231,13 +2388,13 @@ files = [ [[package]] name = "pytz" -version = "2023.3.post1" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -2290,6 +2447,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"}, @@ -2297,8 +2455,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"}, @@ -2315,6 +2480,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"}, @@ -2322,6 +2488,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"}, @@ -2329,104 +2496,104 @@ files = [ [[package]] name = "pyzmq" -version = "25.1.1" +version = "25.1.2" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" files = [ - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, - {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, - {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, - {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, - {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, - {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, - {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, - {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, - {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, - {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, - {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, - {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08"}, + {file = "pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886"}, + {file = "pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3"}, + {file = "pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097"}, + {file = "pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737"}, + {file = "pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d"}, + {file = "pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win32.whl", hash = "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68"}, + {file = "pyzmq-25.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win32.whl", hash = "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755"}, + {file = "pyzmq-25.1.2-cp38-cp38-win32.whl", hash = "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07"}, + {file = "pyzmq-25.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2"}, + {file = "pyzmq-25.1.2-cp39-cp39-win32.whl", hash = "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289"}, + {file = "pyzmq-25.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, + {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, ] [package.dependencies] @@ -2460,13 +2627,13 @@ files = [ [[package]] name = "referencing" -version = "0.30.2" +version = "0.33.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, - {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, ] [package.dependencies] @@ -2475,16 +2642,17 @@ rpds-py = ">=0.7.0" [[package]] name = "reportlab" -version = "4.0.6" +version = "4.1.0" description = "The Reportlab Toolkit" optional = true python-versions = ">=3.7,<4" files = [ - {file = "reportlab-4.0.6-py3-none-any.whl", hash = "sha256:ec062675202eb76f6100ed44da64f38ed3c7feb5016cf4fe7f17ce35423ab14a"}, - {file = "reportlab-4.0.6.tar.gz", hash = "sha256:069aa35da7c882921f419f6e26327e14dac1d9d0adeb40b584cdadd974d99fc0"}, + {file = "reportlab-4.1.0-py3-none-any.whl", hash = "sha256:28a40d5000afbd8ccae15a47f7abe2841768461354bede1a9d42841132997c98"}, + {file = "reportlab-4.1.0.tar.gz", hash = "sha256:3a99faf412691159c068b3ff01c15307ce2fd2cf6b860199434874e002040a84"}, ] [package.dependencies] +chardet = "*" pillow = ">=9.0.0" [package.extras] @@ -2559,125 +2727,125 @@ files = [ [[package]] name = "rpds-py" -version = "0.10.6" +version = "0.18.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6bdc11f9623870d75692cc33c59804b5a18d7b8a4b79ef0b00b773a27397d1f6"}, - {file = "rpds_py-0.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26857f0f44f0e791f4a266595a7a09d21f6b589580ee0585f330aaccccb836e3"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7f5e15c953ace2e8dde9824bdab4bec50adb91a5663df08d7d994240ae6fa31"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61fa268da6e2e1cd350739bb61011121fa550aa2545762e3dc02ea177ee4de35"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c48f3fbc3e92c7dd6681a258d22f23adc2eb183c8cb1557d2fcc5a024e80b094"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0503c5b681566e8b722fe8c4c47cce5c7a51f6935d5c7012c4aefe952a35eed"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:734c41f9f57cc28658d98270d3436dba65bed0cfc730d115b290e970150c540d"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5d7ed104d158c0042a6a73799cf0eb576dfd5fc1ace9c47996e52320c37cb7c"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3df0bc35e746cce42579826b89579d13fd27c3d5319a6afca9893a9b784ff1b"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:73e0a78a9b843b8c2128028864901f55190401ba38aae685350cf69b98d9f7c9"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ed505ec6305abd2c2c9586a7b04fbd4baf42d4d684a9c12ec6110deefe2a063"}, - {file = "rpds_py-0.10.6-cp310-none-win32.whl", hash = "sha256:d97dd44683802000277bbf142fd9f6b271746b4846d0acaf0cefa6b2eaf2a7ad"}, - {file = "rpds_py-0.10.6-cp310-none-win_amd64.whl", hash = "sha256:b455492cab07107bfe8711e20cd920cc96003e0da3c1f91297235b1603d2aca7"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e8cdd52744f680346ff8c1ecdad5f4d11117e1724d4f4e1874f3a67598821069"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66414dafe4326bca200e165c2e789976cab2587ec71beb80f59f4796b786a238"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc435d059f926fdc5b05822b1be4ff2a3a040f3ae0a7bbbe672babb468944722"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7f2219cb72474571974d29a191714d822e58be1eb171f229732bc6fdedf0ac"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3953c6926a63f8ea5514644b7afb42659b505ece4183fdaaa8f61d978754349e"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bb2e4826be25e72013916eecd3d30f66fd076110de09f0e750163b416500721"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf347b495b197992efc81a7408e9a83b931b2f056728529956a4d0858608b80"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:102eac53bb0bf0f9a275b438e6cf6904904908562a1463a6fc3323cf47d7a532"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40f93086eef235623aa14dbddef1b9fb4b22b99454cb39a8d2e04c994fb9868c"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e22260a4741a0e7a206e175232867b48a16e0401ef5bce3c67ca5b9705879066"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4e56860a5af16a0fcfa070a0a20c42fbb2012eed1eb5ceeddcc7f8079214281"}, - {file = "rpds_py-0.10.6-cp311-none-win32.whl", hash = "sha256:0774a46b38e70fdde0c6ded8d6d73115a7c39d7839a164cc833f170bbf539116"}, - {file = "rpds_py-0.10.6-cp311-none-win_amd64.whl", hash = "sha256:4a5ee600477b918ab345209eddafde9f91c0acd931f3776369585a1c55b04c57"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:5ee97c683eaface61d38ec9a489e353d36444cdebb128a27fe486a291647aff6"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0713631d6e2d6c316c2f7b9320a34f44abb644fc487b77161d1724d883662e31"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a53f5998b4bbff1cb2e967e66ab2addc67326a274567697379dd1e326bded7"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a555ae3d2e61118a9d3e549737bb4a56ff0cec88a22bd1dfcad5b4e04759175"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:945eb4b6bb8144909b203a88a35e0a03d22b57aefb06c9b26c6e16d72e5eb0f0"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52c215eb46307c25f9fd2771cac8135d14b11a92ae48d17968eda5aa9aaf5071"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b3cd23d905589cb205710b3988fc8f46d4a198cf12862887b09d7aaa6bf9b9"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64ccc28683666672d7c166ed465c09cee36e306c156e787acef3c0c62f90da5a"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:516a611a2de12fbea70c78271e558f725c660ce38e0006f75139ba337d56b1f6"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ff93d3aedef11f9c4540cf347f8bb135dd9323a2fc705633d83210d464c579d"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d858532212f0650be12b6042ff4378dc2efbb7792a286bee4489eaa7ba010586"}, - {file = "rpds_py-0.10.6-cp312-none-win32.whl", hash = "sha256:3c4eff26eddac49d52697a98ea01b0246e44ca82ab09354e94aae8823e8bda02"}, - {file = "rpds_py-0.10.6-cp312-none-win_amd64.whl", hash = "sha256:150eec465dbc9cbca943c8e557a21afdcf9bab8aaabf386c44b794c2f94143d2"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:cf693eb4a08eccc1a1b636e4392322582db2a47470d52e824b25eca7a3977b53"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4134aa2342f9b2ab6c33d5c172e40f9ef802c61bb9ca30d21782f6e035ed0043"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e782379c2028a3611285a795b89b99a52722946d19fc06f002f8b53e3ea26ea9"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f6da6d842195fddc1cd34c3da8a40f6e99e4a113918faa5e60bf132f917c247"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a9fe992887ac68256c930a2011255bae0bf5ec837475bc6f7edd7c8dfa254e"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b788276a3c114e9f51e257f2a6f544c32c02dab4aa7a5816b96444e3f9ffc336"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa1afc70a02645809c744eefb7d6ee8fef7e2fad170ffdeacca267fd2674f13"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bddd4f91eede9ca5275e70479ed3656e76c8cdaaa1b354e544cbcf94c6fc8ac4"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:775049dfa63fb58293990fc59473e659fcafd953bba1d00fc5f0631a8fd61977"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c6c45a2d2b68c51fe3d9352733fe048291e483376c94f7723458cfd7b473136b"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0699ab6b8c98df998c3eacf51a3b25864ca93dab157abe358af46dc95ecd9801"}, - {file = "rpds_py-0.10.6-cp38-none-win32.whl", hash = "sha256:ebdab79f42c5961682654b851f3f0fc68e6cc7cd8727c2ac4ffff955154123c1"}, - {file = "rpds_py-0.10.6-cp38-none-win_amd64.whl", hash = "sha256:24656dc36f866c33856baa3ab309da0b6a60f37d25d14be916bd3e79d9f3afcf"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:0898173249141ee99ffcd45e3829abe7bcee47d941af7434ccbf97717df020e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9184fa6c52a74a5521e3e87badbf9692549c0fcced47443585876fcc47e469"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5752b761902cd15073a527b51de76bbae63d938dc7c5c4ad1e7d8df10e765138"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99a57006b4ec39dbfb3ed67e5b27192792ffb0553206a107e4aadb39c5004cd5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09586f51a215d17efdb3a5f090d7cbf1633b7f3708f60a044757a5d48a83b393"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e225a6a14ecf44499aadea165299092ab0cba918bb9ccd9304eab1138844490b"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2039f8d545f20c4e52713eea51a275e62153ee96c8035a32b2abb772b6fc9e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34ad87a831940521d462ac11f1774edf867c34172010f5390b2f06b85dcc6014"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdc88b6b01015da066da3fb76545e8bb9a6880a5ebf89e0f0b2e3ca557b3ab7"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25860ed5c4e7f5e10c496ea78af46ae8d8468e0be745bd233bab9ca99bfd2647"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7854a207ef77319ec457c1eb79c361b48807d252d94348305db4f4b62f40f7f3"}, - {file = "rpds_py-0.10.6-cp39-none-win32.whl", hash = "sha256:e6fcc026a3f27c1282c7ed24b7fcac82cdd70a0e84cc848c0841a3ab1e3dea2d"}, - {file = "rpds_py-0.10.6-cp39-none-win_amd64.whl", hash = "sha256:e98c4c07ee4c4b3acf787e91b27688409d918212dfd34c872201273fdd5a0e18"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:68fe9199184c18d997d2e4293b34327c0009a78599ce703e15cd9a0f47349bba"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3339eca941568ed52d9ad0f1b8eb9fe0958fa245381747cecf2e9a78a5539c42"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a360cfd0881d36c6dc271992ce1eda65dba5e9368575663de993eeb4523d895f"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:031f76fc87644a234883b51145e43985aa2d0c19b063e91d44379cd2786144f8"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f36a9d751f86455dc5278517e8b65580eeee37d61606183897f122c9e51cef3"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:052a832078943d2b2627aea0d19381f607fe331cc0eb5df01991268253af8417"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023574366002bf1bd751ebaf3e580aef4a468b3d3c216d2f3f7e16fdabd885ed"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:defa2c0c68734f4a82028c26bcc85e6b92cced99866af118cd6a89b734ad8e0d"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879fb24304ead6b62dbe5034e7b644b71def53c70e19363f3c3be2705c17a3b4"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:53c43e10d398e365da2d4cc0bcaf0854b79b4c50ee9689652cdc72948e86f487"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3777cc9dea0e6c464e4b24760664bd8831738cc582c1d8aacf1c3f546bef3f65"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:40578a6469e5d1df71b006936ce95804edb5df47b520c69cf5af264d462f2cbb"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf71343646756a072b85f228d35b1d7407da1669a3de3cf47f8bbafe0c8183a4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f32b53f424fc75ff7b713b2edb286fdbfc94bf16317890260a81c2c00385dc"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81de24a1c51cfb32e1fbf018ab0bdbc79c04c035986526f76c33e3f9e0f3356c"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac17044876e64a8ea20ab132080ddc73b895b4abe9976e263b0e30ee5be7b9c2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8a78bd4879bff82daef48c14d5d4057f6856149094848c3ed0ecaf49f5aec2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca33811e1d95cac8c2e49cb86c0fb71f4d8409d8cbea0cb495b6dbddb30a55"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c63c3ef43f0b3fb00571cff6c3967cc261c0ebd14a0a134a12e83bdb8f49f21f"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7fde6d0e00b2fd0dbbb40c0eeec463ef147819f23725eda58105ba9ca48744f4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:79edd779cfc46b2e15b0830eecd8b4b93f1a96649bcb502453df471a54ce7977"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9164ec8010327ab9af931d7ccd12ab8d8b5dc2f4c6a16cbdd9d087861eaaefa1"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d29ddefeab1791e3c751e0189d5f4b3dbc0bbe033b06e9c333dca1f99e1d523e"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:30adb75ecd7c2a52f5e76af50644b3e0b5ba036321c390b8e7ec1bb2a16dd43c"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd609fafdcdde6e67a139898196698af37438b035b25ad63704fd9097d9a3482"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eef672de005736a6efd565577101277db6057f65640a813de6c2707dc69f396"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf4393c7b41abbf07c88eb83e8af5013606b1cdb7f6bc96b1b3536b53a574b8"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad857f42831e5b8d41a32437f88d86ead6c191455a3499c4b6d15e007936d4cf"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7360573f1e046cb3b0dceeb8864025aa78d98be4bb69f067ec1c40a9e2d9df"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d08f63561c8a695afec4975fae445245386d645e3e446e6f260e81663bfd2e38"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f0f17f2ce0f3529177a5fff5525204fad7b43dd437d017dd0317f2746773443d"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:442626328600bde1d09dc3bb00434f5374948838ce75c41a52152615689f9403"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e9616f5bd2595f7f4a04b67039d890348ab826e943a9bfdbe4938d0eba606971"}, - {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, + {file = "rpds_py-0.18.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e"}, + {file = "rpds_py-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88"}, + {file = "rpds_py-0.18.0-cp310-none-win32.whl", hash = "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337"}, + {file = "rpds_py-0.18.0-cp310-none-win_amd64.whl", hash = "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836"}, + {file = "rpds_py-0.18.0-cp311-none-win32.whl", hash = "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1"}, + {file = "rpds_py-0.18.0-cp311-none-win_amd64.whl", hash = "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7"}, + {file = "rpds_py-0.18.0-cp312-none-win32.whl", hash = "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98"}, + {file = "rpds_py-0.18.0-cp312-none-win_amd64.whl", hash = "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594"}, + {file = "rpds_py-0.18.0-cp38-none-win32.whl", hash = "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e"}, + {file = "rpds_py-0.18.0-cp38-none-win_amd64.whl", hash = "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20"}, + {file = "rpds_py-0.18.0-cp39-none-win32.whl", hash = "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7"}, + {file = "rpds_py-0.18.0-cp39-none-win_amd64.whl", hash = "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f"}, + {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, ] [[package]] name = "rtfde" -version = "0.1.0" +version = "0.1.1" description = "A library for extracting HTML content from RTF encapsulated HTML as commonly found in the exchange MSG email format." optional = true -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "RTFDE-0.1.0-py3-none-any.whl", hash = "sha256:a110dbef435803f3fba717d51a7b9c7a92695c2461637cc6eaf36a9f54386e26"}, - {file = "RTFDE-0.1.0.tar.gz", hash = "sha256:12215ee59856208010b9200c19afe0f9fa13a3fb39f44015979299c248cbacd7"}, + {file = "RTFDE-0.1.1-py3-none-any.whl", hash = "sha256:ea7ab0e0b9d4af08415f5017ecff91d74e24216a5e4e4682155cedc478035e99"}, + {file = "RTFDE-0.1.1.tar.gz", hash = "sha256:9e43485e79b2dd1018127735d8134f65d2a9d73af314d2a101f10346333b241e"}, ] [package.dependencies] -lark = "1.1.5" +lark = "1.1.8" oletools = ">=0.56" [package.extras] @@ -2816,22 +2984,22 @@ test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools [[package]] name = "sphinx-autodoc-typehints" -version = "1.24.0" +version = "2.0.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = true python-versions = ">=3.8" files = [ - {file = "sphinx_autodoc_typehints-1.24.0-py3-none-any.whl", hash = "sha256:6a73c0c61a9144ce2ed5ef2bed99d615254e5005c1cc32002017d72d69fb70e6"}, - {file = "sphinx_autodoc_typehints-1.24.0.tar.gz", hash = "sha256:94e440066941bb237704bb880785e2d05e8ae5406c88674feefbb938ad0dc6af"}, + {file = "sphinx_autodoc_typehints-2.0.0-py3-none-any.whl", hash = "sha256:12c0e161f6fe191c2cdfd8fa3caea271f5387d9fbc67ebcd6f4f1f24ce880993"}, + {file = "sphinx_autodoc_typehints-2.0.0.tar.gz", hash = "sha256:7f2cdac2e70fd9787926b6e9e541cd4ded1e838d2b46fda2a1bb0a75ec5b7f3a"}, ] [package.dependencies] -sphinx = ">=7.0.1" +sphinx = ">=7.1.2" [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)"] +docs = ["furo (>=2023.9.10)"] numpy = ["nptyping (>=2.5)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.6.3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.8)"] [[package]] name = "sphinxcontrib-applehelp" @@ -2850,20 +3018,18 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.7" +version = "1.0.8" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = true python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, - {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -2883,20 +3049,18 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.5" +version = "1.0.6" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = true python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, - {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -2916,20 +3080,18 @@ test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.4" +version = "2.0.5" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = true python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, - {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] @@ -2963,20 +3125,18 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.6" +version = "1.0.7" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = true python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, - {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -2996,20 +3156,18 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.9" +version = "1.1.10" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = true python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, - {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -3033,13 +3191,13 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "terminado" -version = "0.17.1" +version = "0.18.0" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "terminado-0.17.1-py3-none-any.whl", hash = "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"}, - {file = "terminado-0.17.1.tar.gz", hash = "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333"}, + {file = "terminado-0.18.0-py3-none-any.whl", hash = "sha256:87b0d96642d0fe5f5abd7783857b9cab167f221a39ff98e3b9619a788a3c0f2e"}, + {file = "terminado-0.18.0.tar.gz", hash = "sha256:1ea08a89b835dd1b8c0c900d92848147cef2537243361b2e3f4dc15df9b6fded"}, ] [package.dependencies] @@ -3050,6 +3208,7 @@ tornado = ">=6.1.0" [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] +typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] [[package]] name = "tinycss2" @@ -3082,38 +3241,38 @@ files = [ [[package]] name = "tornado" -version = "6.3.3" +version = "6.4" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" files = [ - {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, - {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, - {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, - {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, - {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, + {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, + {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, + {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] [[package]] name = "traitlets" -version = "5.11.2" +version = "5.14.1" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, - {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, + {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, + {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "types-click" @@ -3169,13 +3328,13 @@ files = [ [[package]] name = "types-pyopenssl" -version = "23.2.0.2" +version = "24.0.0.20240130" description = "Typing stubs for pyOpenSSL" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-pyOpenSSL-23.2.0.2.tar.gz", hash = "sha256:6a010dac9ecd42b582d7dd2cc3e9e40486b79b3b64bb2fffba1474ff96af906d"}, - {file = "types_pyOpenSSL-23.2.0.2-py3-none-any.whl", hash = "sha256:19536aa3debfbe25a918cf0d898e9f5fbbe6f3594a429da7914bf331deb1b342"}, + {file = "types-pyOpenSSL-24.0.0.20240130.tar.gz", hash = "sha256:c812e5c1c35249f75ef5935708b2a997d62abf9745be222e5f94b9595472ab25"}, + {file = "types_pyOpenSSL-24.0.0.20240130-py3-none-any.whl", hash = "sha256:24a255458b5b8a7fca8139cf56f2a8ad5a4f1a5f711b73a5bb9cb50dc688fab5"}, ] [package.dependencies] @@ -3183,24 +3342,24 @@ cryptography = ">=35.0.0" [[package]] name = "types-python-dateutil" -version = "2.8.19.14" +version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, - {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, + {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, + {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, ] [[package]] name = "types-redis" -version = "4.6.0.7" +version = "4.6.0.20240218" description = "Typing stubs for redis" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-redis-4.6.0.7.tar.gz", hash = "sha256:28c4153ddb5c9d4f10def44a2454673c361d2d5fc3cd867cf3bb1520f3f59a38"}, - {file = "types_redis-4.6.0.7-py3-none-any.whl", hash = "sha256:05b1bf92879b25df20433fa1af07784a0d7928c616dc2ebf9087618db77ccbd0"}, + {file = "types-redis-4.6.0.20240218.tar.gz", hash = "sha256:5103d7e690e5c74c974a161317b2d59ac2303cf8bef24175b04c2a4c3486cb39"}, + {file = "types_redis-4.6.0.20240218-py3-none-any.whl", hash = "sha256:dc9c45a068240e33a04302aec5655cf41e80f91eecffccbb2df215b2f6fc375d"}, ] [package.dependencies] @@ -3209,13 +3368,13 @@ types-pyOpenSSL = "*" [[package]] name = "types-requests" -version = "2.31.0.10" +version = "2.31.0.20240218" description = "Typing stubs for requests" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.10.tar.gz", hash = "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92"}, - {file = "types_requests-2.31.0.10-py3-none-any.whl", hash = "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc"}, + {file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"}, + {file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"}, ] [package.dependencies] @@ -3234,24 +3393,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "tzdata" -version = "2023.3" +version = "2024.1" description = "Provider of IANA time zone data" optional = true python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] @@ -3288,13 +3447,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.dependencies] @@ -3303,7 +3462,7 @@ brotlicffi = {version = ">=0.8.0", optional = true, markers = "platform_python_i [package.extras] 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"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3331,13 +3490,13 @@ tooling-extras = ["pyaml (>=23.7.0)", "pypandoc-binary (>=1.11)", "pytest (>=7.4 [[package]] name = "wcwidth" -version = "0.2.8" +version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, - {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [[package]] @@ -3368,13 +3527,13 @@ files = [ [[package]] name = "websocket-client" -version = "1.6.4" +version = "1.7.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, - {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, ] [package.extras] @@ -3394,86 +3553,81 @@ files = [ [[package]] name = "wrapt" -version = "1.15.0" +version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] [[package]] @@ -3504,4 +3658,4 @@ virustotal = ["validators"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "7aa52b25c8e8ee932fe7e405a814040e6080ff88fa5c854d0505cb40407afb41" +content-hash = "87ffab7b554e19ae1d029620c6861210b226f577aff26bdc436bc7c70230fd0e" diff --git a/pymisp/__init__.py b/pymisp/__init__.py index e3f07c0..73f1be8 100644 --- a/pymisp/__init__.py +++ b/pymisp/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import sys import warnings @@ -9,7 +11,7 @@ logger = logging.getLogger(__name__) __version__ = importlib.metadata.version("pymisp") -def warning_2024(): +def warning_2024() -> None: if sys.version_info < (3, 10): warnings.warn(""" As our baseline system is the latest Ubuntu LTS, and Ubuntu LTS 22.04 has Python 3.10 available, @@ -38,17 +40,14 @@ try: MISPEventDelegation, MISPUserSetting, MISPInbox, MISPEventBlocklist, MISPOrganisationBlocklist, MISPEventReport, MISPCorrelationExclusion, MISPDecayingModel, MISPGalaxy, MISPGalaxyCluster, MISPGalaxyClusterElement, MISPGalaxyClusterRelation) + from .api import PyMISP, register_user # noqa + # NOTE: the direct imports to .tools are kept for backward compatibility but should be removed in the future from .tools import AbstractMISPObjectGenerator # noqa - from .tools import Neo4j # noqa - from .tools import stix # noqa from .tools import openioc # noqa from .tools import ext_lookups # noqa from .tools import update_objects # noqa - - from .api import PyMISP, register_user # noqa - from .api import PyMISP as ExpandedPyMISP # noqa from .tools import load_warninglists # noqa - # Let's not bother with old python + try: from .tools import reportlab_generator # noqa except ImportError: @@ -59,4 +58,25 @@ try: pass logger.debug('pymisp loaded properly') except ImportError as e: - logger.warning('Unable to load pymisp properly: {}'.format(e)) + logger.warning(f'Unable to load pymisp properly: {e}') + + +class ExpandedPyMISP(PyMISP): + + def __init__(self, *args, **kwargs): + warnings.warn('This class is deprecated, use PyMISP instead', FutureWarning) + super().__init__(*args, **kwargs) + + +__all__ = ['PyMISP', 'register_user', 'AbstractMISP', 'MISPTag', + 'MISPEvent', 'MISPAttribute', 'MISPObjectReference', 'MISPObjectAttribute', + 'MISPObject', 'MISPUser', 'MISPOrganisation', 'MISPSighting', 'MISPLog', + 'MISPShadowAttribute', 'MISPWarninglist', 'MISPTaxonomy', 'MISPNoticelist', + 'MISPObjectTemplate', 'MISPSharingGroup', 'MISPRole', 'MISPServer', 'MISPFeed', + 'MISPEventDelegation', 'MISPUserSetting', 'MISPInbox', 'MISPEventBlocklist', + 'MISPOrganisationBlocklist', 'MISPEventReport', 'MISPCorrelationExclusion', + 'MISPDecayingModel', 'MISPGalaxy', 'MISPGalaxyCluster', 'MISPGalaxyClusterElement', + 'MISPGalaxyClusterRelation', 'PyMISPError', 'NewEventError', 'NewAttributeError', + 'NoURL', 'NoKey', 'InvalidMISPObject', 'UnknownMISPObjectTemplate', 'PyMISPInvalidFormat', + 'Distribution', 'ThreatLevel', 'Analysis', 'ExpandedPyMISP' + ] diff --git a/pymisp/abstract.py b/pymisp/abstract.py index 15a123c..41b335a 100644 --- a/pymisp/abstract.py +++ b/pymisp/abstract.py @@ -1,53 +1,48 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- +from __future__ import annotations + +import logging from datetime import date, datetime - from deprecated import deprecated # type: ignore from json import JSONEncoder from uuid import UUID from abc import ABCMeta - -try: - from rapidjson import load # type: ignore - from rapidjson import loads # type: ignore - from rapidjson import dumps # type: ignore - HAS_RAPIDJSON = True -except ImportError: - from json import load - from json import loads - from json import dumps - HAS_RAPIDJSON = False - -import logging -from enum import Enum -from typing import Union, Optional, Any, Dict, List, Set, Mapping - -from .exceptions import PyMISPInvalidFormat, PyMISPError - - +from enum import Enum, IntEnum +from typing import Any, Mapping from collections.abc import MutableMapping from functools import lru_cache from pathlib import Path +try: + import orjson # type: ignore + from orjson import loads, dumps + HAS_ORJSON = True +except ImportError: + from json import loads, dumps + HAS_ORJSON = False + +from .exceptions import PyMISPInvalidFormat, PyMISPError + logger = logging.getLogger('pymisp') + resources_path = Path(__file__).parent / 'data' misp_objects_path = resources_path / 'misp-objects' / 'objects' -with (resources_path / 'describeTypes.json').open('r') as f: - describe_types = load(f)['result'] +with (resources_path / 'describeTypes.json').open('rb') as f: + describe_types: dict[str, Any] = loads(f.read())['result'] -class MISPFileCache(object): +class MISPFileCache: # cache up to 150 JSON structures in class attribute @staticmethod @lru_cache(maxsize=150) - def _load_json(path: Path) -> Union[dict, None]: + def _load_json(path: Path) -> dict[str, Any] | None: if not path.exists(): return None - with path.open('r', encoding='utf-8') as f: - data = load(f) + with path.open('rb') as f: + data = loads(f.read()) return data @@ -73,7 +68,7 @@ class Analysis(Enum): completed = 2 -def _int_to_str(d: Dict[str, Any]) -> Dict[str, Any]: +def _int_to_str(d: dict[str, Any]) -> dict[str, Any]: # transform all integer back to string for k, v in d.items(): if isinstance(v, dict): @@ -85,7 +80,7 @@ def _int_to_str(d: Dict[str, Any]) -> Dict[str, Any]: @deprecated(reason=" Use method default=pymisp_json_default instead of cls=MISPEncode", version='2.4.117', action='default') class MISPEncode(JSONEncoder): - def default(self, obj): + def default(self, obj: Any) -> dict[str, Any] | str: if isinstance(obj, AbstractMISP): return obj.jsonable() elif isinstance(obj, (datetime, date)): @@ -97,12 +92,12 @@ class MISPEncode(JSONEncoder): return JSONEncoder.default(self, obj) -class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): +class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): # type: ignore[type-arg] __resources_path = resources_path __misp_objects_path = misp_objects_path __describe_types = describe_types - def __init__(self, **kwargs: Dict): + def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Abstract class for all the MISP objects. NOTE: Every method in every classes inheriting this one are doing changes in memory and do not modify data on a remote MISP instance. @@ -111,9 +106,9 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): """ super().__init__() self.__edited: bool = True # As we create a new object, we assume it is edited - self.__not_jsonable: List[str] = [] - self._fields_for_feed: Set - self.__self_defined_describe_types: Optional[Dict] = None + self.__not_jsonable: list[str] = [] + self._fields_for_feed: set[str] + self.__self_defined_describe_types: dict[str, Any] | None = None self.uuid: str if kwargs.get('force_timestamps') is not None: @@ -123,13 +118,13 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): self.__force_timestamps = False @property - def describe_types(self) -> Dict: + def describe_types(self) -> dict[str, Any]: if self.__self_defined_describe_types: return self.__self_defined_describe_types return self.__describe_types @describe_types.setter - def describe_types(self, describe_types: Dict): + def describe_types(self, describe_types: dict[str, Any]) -> None: self.__self_defined_describe_types = describe_types @property @@ -141,12 +136,12 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return self.__misp_objects_path @misp_objects_path.setter - def misp_objects_path(self, misp_objects_path: Union[str, Path]): + def misp_objects_path(self, misp_objects_path: str | Path) -> None: if isinstance(misp_objects_path, str): misp_objects_path = Path(misp_objects_path) self.__misp_objects_path = misp_objects_path - def from_dict(self, **kwargs) -> None: + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Loading all the parameters as class properties, if they aren't `None`. This method aims to be called when all the properties requiring a special treatment are processed. @@ -159,15 +154,15 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): # We load an existing dictionary, marking it an not-edited self.__edited = False - def update_not_jsonable(self, *args) -> None: + def update_not_jsonable(self, *args) -> None: # type: ignore[no-untyped-def] """Add entries to the __not_jsonable list""" self.__not_jsonable += args - def set_not_jsonable(self, args: List[str]) -> None: + def set_not_jsonable(self, args: list[str]) -> None: """Set __not_jsonable to a new list""" self.__not_jsonable = args - def _remove_from_not_jsonable(self, *args) -> None: + def _remove_from_not_jsonable(self, *args) -> None: # type: ignore[no-untyped-def] """Remove the entries that are in the __not_jsonable list""" for entry in args: try: @@ -179,7 +174,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): """Load a JSON string""" self.from_dict(**loads(json_string)) - def to_dict(self, json_format: bool = False) -> Dict: + def to_dict(self, json_format: bool = False) -> dict[str, Any]: """Dump the class to a dictionary. This method automatically removes the timestamp recursively in every object that has been edited is order to let MISP update the event accordingly.""" @@ -221,15 +216,15 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): to_return = _int_to_str(to_return) return to_return - def jsonable(self) -> Dict: + def jsonable(self) -> dict[str, Any]: """This method is used by the JSON encoder""" return self.to_dict() - def _to_feed(self) -> Dict: + def _to_feed(self) -> dict[str, Any]: if not hasattr(self, '_fields_for_feed') or not self._fields_for_feed: raise PyMISPError('Unable to export in the feed format, _fields_for_feed is missing.') - if hasattr(self, '_set_default') and callable(self._set_default): # type: ignore - self._set_default() # type: ignore + if hasattr(self, '_set_default') and callable(self._set_default): + self._set_default() to_return = {} for field in sorted(self._fields_for_feed): if getattr(self, field, None) is not None: @@ -243,15 +238,25 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): if field in ['data', 'first_seen', 'last_seen', 'deleted']: # special fields continue - raise PyMISPError('The field {} is required in {} when generating a feed.'.format(field, self.__class__.__name__)) + raise PyMISPError(f'The field {field} is required in {self.__class__.__name__} when generating a feed.') to_return = _int_to_str(to_return) return to_return - def to_json(self, sort_keys: bool = False, indent: Optional[int] = None) -> str: + def to_json(self, sort_keys: bool = False, indent: int | None = None) -> str: """Dump recursively any class of type MISPAbstract to a json string""" + if HAS_ORJSON: + option = 0 + if sort_keys: + option |= orjson.OPT_SORT_KEYS + if indent: + option |= orjson.OPT_INDENT_2 + # orjson dumps method returns bytes instead of bytes, to keep compatibility with json + # we have to convert output to str + return str(dumps(self, default=pymisp_json_default, option=option)) + return dumps(self, default=pymisp_json_default, sort_keys=sort_keys, indent=indent) - def __getitem__(self, key): + def __getitem__(self, key: str) -> Any: try: if key[0] != '_' and key not in self.__not_jsonable: return self.__dict__[key] @@ -260,13 +265,13 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): # Expected by pop and other dict-related methods raise KeyError - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any) -> None: setattr(self, key, value) - def __delitem__(self, key): + def __delitem__(self, key: str) -> None: delattr(self, key) - def __iter__(self): + def __iter__(self) -> Any: '''When we call **self, skip keys: * starting with _ * in __not_jsonable @@ -285,7 +290,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return self.__force_timestamps @force_timestamp.setter - def force_timestamp(self, force: bool): + def force_timestamp(self, force: bool) -> None: self.__force_timestamps = force @property @@ -305,28 +310,28 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return self.__edited @edited.setter - def edited(self, val: bool): + def edited(self, val: bool) -> None: """Set the edit flag""" if isinstance(val, bool): self.__edited = val else: raise PyMISPError('edited can only be True or False') - def __setattr__(self, name: str, value: Any): + def __setattr__(self, name: str, value: Any) -> None: if name[0] != '_' and not self.__edited and name in self: # The private members don't matter # If we already have a key with that name, we're modifying it. self.__edited = True super().__setattr__(name, value) - def _datetime_to_timestamp(self, d: Union[int, float, str, datetime]) -> int: + def _datetime_to_timestamp(self, d: int | float | str | datetime) -> int: """Convert a datetime object to a timestamp (int)""" if isinstance(d, (int, float, str)): # Assume we already have a timestamp return int(d) return int(d.timestamp()) - def _add_tag(self, tag: Optional[Union[str, 'MISPTag', Mapping]] = None, **kwargs): + def _add_tag(self, tag: str | MISPTag | Mapping[str, Any] | None = None, **kwargs): # type: ignore[no-untyped-def] """Add a tag to the attribute (by name or a MISPTag object)""" if isinstance(tag, str): misp_tag = MISPTag() @@ -346,14 +351,14 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): self.edited = True return misp_tag - def _set_tags(self, tags: List['MISPTag']): + def _set_tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" if all(isinstance(x, MISPTag) for x in tags): self.Tag = tags else: raise PyMISPInvalidFormat('All the attributes have to be of type MISPTag.') - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: if isinstance(other, AbstractMISP): return self.to_dict() == other.to_dict() elif isinstance(other, dict): @@ -362,26 +367,26 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return False def __repr__(self) -> str: - return '<{self.__class__.__name__} - please define me>'.format(self=self) + return f'<{self.__class__.__name__} - please define me>' class MISPTag(AbstractMISP): - _fields_for_feed: set = {'name', 'colour', 'relationship_type', 'local'} + _fields_for_feed: set[str] = {'name', 'colour', 'relationship_type', 'local'} - def __init__(self, **kwargs: Dict): + def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__(**kwargs) self.name: str self.exportable: bool self.local: bool - self.relationship_type: Optional[str] + self.relationship_type: str | None - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if kwargs.get('Tag'): - kwargs = kwargs.get('Tag') + kwargs = kwargs.get('Tag') # type: ignore[assignment] super().from_dict(**kwargs) - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'relationship_type'): self.relationship_type = '' if not hasattr(self, 'colour'): @@ -389,40 +394,30 @@ class MISPTag(AbstractMISP): if not hasattr(self, 'local'): self.local = False - def _to_feed(self, with_local: bool = True) -> Dict: + def _to_feed(self, with_local: bool = True) -> dict[str, Any]: if hasattr(self, 'exportable') and not self.exportable: return {} if with_local is False and hasattr(self, 'local') and self.local: return {} return super()._to_feed() - def delete(self): + def delete(self) -> None: self.deleted = True self.edited = True def __repr__(self) -> str: if hasattr(self, 'name'): return '<{self.__class__.__name__}(name={self.name})>'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)>'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)>' -if HAS_RAPIDJSON: - def pymisp_json_default(obj: Union[AbstractMISP, datetime, date, Enum, UUID]) -> Union[Dict, str]: - if isinstance(obj, AbstractMISP): - return obj.jsonable() - elif isinstance(obj, (datetime, date)): - return obj.isoformat() - elif isinstance(obj, Enum): - return obj.value - elif isinstance(obj, UUID): - return str(obj) -else: - def pymisp_json_default(obj: Union[AbstractMISP, datetime, date, Enum, UUID]) -> Union[Dict, str]: - if isinstance(obj, AbstractMISP): - return obj.jsonable() - elif isinstance(obj, (datetime, date)): - return obj.isoformat() - elif isinstance(obj, Enum): - return obj.value - elif isinstance(obj, UUID): - return str(obj) +# UUID, datetime, date and Enum is serialized by ORJSON by default +def pymisp_json_default(obj: AbstractMISP | datetime | date | Enum | UUID) -> dict[str, Any] | str: + if isinstance(obj, AbstractMISP): + return obj.jsonable() + elif isinstance(obj, (datetime, date)): + return obj.isoformat() + elif isinstance(obj, Enum): + return obj.value + elif isinstance(obj, UUID): + return str(obj) diff --git a/pymisp/api.py b/pymisp/api.py index 83935ff..bc8e7cc 100644 --- a/pymisp/api.py +++ b/pymisp/api.py @@ -1,13 +1,11 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- +from __future__ import annotations -from typing import TypeVar, Optional, Tuple, List, Dict, Union, Any, Mapping, Iterable, MutableMapping +from typing import TypeVar, Any, Mapping, Iterable, MutableMapping, Union, List, Dict from datetime import date, datetime import csv from pathlib import Path import logging from urllib.parse import urljoin -import json import requests from requests.auth import AuthBase import re @@ -15,8 +13,16 @@ from uuid import UUID import warnings import sys import copy -import urllib3 # type: ignore from io import BytesIO, StringIO +from importlib.metadata import version + +try: + # orjson is optional dependency that speedups parsing and encoding JSON + from orjson import loads, dumps # type: ignore + HAS_ORJSON = True +except ImportError: + from json import loads, dumps + HAS_ORJSON = False from . import __version__, everything_broken from .exceptions import MISPServerError, PyMISPUnexpectedResponse, PyMISPError, NoURL, NoKey @@ -42,12 +48,12 @@ if sys.platform == 'linux': try: # cached_property exists since Python 3.8 - from functools import cached_property # type: ignore + from functools import cached_property except ImportError: from functools import lru_cache def cached_property(func): # type: ignore - return property(lru_cache()(func)) + return property(lru_cache(func)) SearchType = TypeVar('SearchType', str, int) # str: string to search / list: values to search (OR) / dict: {'OR': [list], 'NOT': [list], 'AND': [list]} @@ -58,14 +64,14 @@ ToIDSType = TypeVar('ToIDSType', str, int, bool) logger = logging.getLogger('pymisp') -def get_uuid_or_id_from_abstract_misp(obj: Union[AbstractMISP, int, str, UUID, dict]) -> Union[str, int]: +def get_uuid_or_id_from_abstract_misp(obj: AbstractMISP | int | str | UUID | dict[str, Any]) -> str | int: """Extract the relevant ID accordingly to the given type passed as parameter""" if isinstance(obj, UUID): return str(obj) if isinstance(obj, (int, str)): return obj - if isinstance(obj, dict) and len(obj.keys()) == 1: + if isinstance(obj, dict) and len(obj) == 1: # We have an object in that format: {'Event': {'id': 2, ...}} # We need to get the content of that dictionary obj = obj[list(obj.keys())[0]] @@ -90,11 +96,11 @@ def get_uuid_or_id_from_abstract_misp(obj: Union[AbstractMISP, int, str, UUID, d def register_user(misp_url: str, email: str, - organisation: Optional[Union[MISPOrganisation, int, str, UUID]] = None, - org_id: Optional[str] = None, org_name: Optional[str] = None, - message: Optional[str] = None, custom_perms: Optional[str] = None, + organisation: MISPOrganisation | int | str | UUID | None = None, + org_id: str | None = None, org_name: str | None = None, + message: str | None = None, custom_perms: str | None = None, perm_sync: bool = False, perm_publish: bool = False, perm_admin: bool = False, - verify: bool = True) -> Dict: + verify: bool = True) -> dict[str, Any] | list[dict[str, Any]]: """Ask for the creation of an account for the user with the given email address""" data = copy.deepcopy(locals()) if organisation: @@ -119,13 +125,13 @@ def brotli_supported() -> bool: patch: int # urllib >= 1.25.1 includes brotli support - version_splitted = urllib3.__version__.split('.') # noqa: F811 + version_splitted = version('urllib3').split('.') # noqa: F811 if len(version_splitted) == 2: major, minor = version_splitted # type: ignore patch = 0 else: major, minor, patch = version_splitted # type: ignore - major, minor, patch = int(major), int(minor), int(patch) # type: ignore + major, minor, patch = int(major), int(minor), int(patch) urllib3_with_brotli = (major == 1 and ((minor == 25 and patch >= 1) or (minor >= 26))) or major >= 2 if not urllib3_with_brotli: @@ -151,13 +157,15 @@ class PyMISP: :param auth: The auth parameter is passed directly to requests, as described here: http://docs.python-requests.org/en/master/user/authentication/ :param tool: The software using PyMISP (string), used to set a unique user-agent :param http_headers: Arbitrary headers to pass to all the requests. + :param https_adapter: Arbitrary HTTPS adapter for the requests session. :param timeout: Timeout, as described here: https://requests.readthedocs.io/en/master/user/advanced/#timeouts """ - def __init__(self, url: str, key: str, ssl: bool = True, debug: bool = False, proxies: Optional[MutableMapping[str, str]] = None, - cert: Optional[Union[str, Tuple[str, str]]] = None, auth: Optional[AuthBase] = None, tool: str = '', - timeout: Optional[Union[float, Tuple[float, float]]] = None, - http_headers: Optional[Dict[str, str]]=None + def __init__(self, url: str, key: str, ssl: bool | str = True, debug: bool = False, proxies: MutableMapping[str, str] | None = None, + cert: str | tuple[str, str] | None = None, auth: AuthBase | None = None, tool: str = '', + timeout: float | tuple[float, float] | None = None, + http_headers: dict[str, str] | None = None, + https_adapter: requests.adapters.BaseAdapter | None = None ): if not url: @@ -167,17 +175,20 @@ class PyMISP: self.root_url: str = url self.key: str = key - self.ssl: bool = ssl - self.proxies: Optional[MutableMapping[str, str]] = proxies - self.cert: Optional[Union[str, Tuple[str, str]]] = cert - self.auth: Optional[AuthBase] = auth + self.ssl: bool | str = ssl + self.proxies: MutableMapping[str, str] | None = proxies + self.cert: str | tuple[str, str] | None = cert + self.auth: AuthBase | None = auth self.tool: str = tool - self.timeout: Optional[Union[float, Tuple[float, float]]] = timeout + self.timeout: float | tuple[float, float] | None = timeout self.__session = requests.Session() # use one session to keep connection between requests + if https_adapter is not None: + self.__session.mount('https://', https_adapter) if brotli_supported(): self.__session.headers['Accept-Encoding'] = ', '.join(('br', 'gzip', 'deflate')) if http_headers: self.__session.headers.update(http_headers) + self._user_agent = f'PyMISP {__version__} - Python {".".join(str(x) for x in sys.version_info[:2])}' self.global_pythonify = False @@ -206,8 +217,19 @@ class PyMISP: # Get the user information self._current_user: MISPUser self._current_role: MISPRole - self._current_user_settings: List[MISPUserSetting] - self._current_user, self._current_role, self._current_user_settings = self.get_user(pythonify=True, expanded=True) + self._current_user_settings: list[MISPUserSetting] + user_infos = self.get_user(pythonify=True, expanded=True) + if isinstance(user_infos, dict): + # There was an error during the get_user call + if e := user_infos.get('errors'): + raise PyMISPError(f'Unable to get the user settings: {e}') + raise PyMISPError(f'Unexpected error when initializing the connection: {user_infos}') + elif isinstance(user_infos, tuple) and len(user_infos) == 3: + self._current_user, self._current_role, self._current_user_settings = user_infos + else: + raise PyMISPError(f'Unexpected error when initializing the connection: {user_infos}') + except PyMISPError as e: + raise e except Exception as e: raise PyMISPError(f'Unable to connect to MISP ({self.root_url}). Please make sure the API key and the URL are correct (http/https is required): {e}') @@ -221,7 +243,7 @@ class PyMISP: self.category_type_mapping = self.describe_types['category_type_mappings'] self.sane_default = self.describe_types['sane_defaults'] - def remote_acl(self, debug_type: str = 'findMissingFunctionNames') -> Dict: + def remote_acl(self, debug_type: str = 'findMissingFunctionNames') -> dict[str, Any] | list[dict[str, Any]]: """This should return an empty list, unless the ACL is outdated. :param debug_type: printAllFunctionNames, findMissingFunctionNames, or printRoleAccess @@ -230,19 +252,19 @@ class PyMISP: return self._check_json_response(response) @property - def describe_types_local(self) -> Dict: + def describe_types_local(self) -> dict[str, Any] | list[dict[str, Any]]: '''Returns the content of describe types from the package''' return describe_types @property - def describe_types_remote(self) -> Dict: + def describe_types_remote(self) -> dict[str, Any] | list[dict[str, Any]]: '''Returns the content of describe types from the remote instance''' response = self._prepare_request('GET', 'attributes/describeTypes.json') remote_describe_types = self._check_json_response(response) return remote_describe_types['result'] @property - def recommended_pymisp_version(self) -> Dict: + def recommended_pymisp_version(self) -> dict[str, Any] | list[dict[str, Any]]: """Returns the recommended API version from the server""" # Sine MISP 2.4.146 is recommended PyMISP version included in getVersion call misp_version = self.misp_instance_version @@ -253,17 +275,17 @@ class PyMISP: return self._check_json_response(response) @property - def version(self) -> Dict: + def version(self) -> dict[str, Any] | list[dict[str, Any]]: """Returns the version of PyMISP you're currently using""" return {'version': __version__} @property - def pymisp_version_master(self) -> Dict: + def pymisp_version_master(self) -> dict[str, Any] | list[dict[str, Any]]: """PyMISP version as defined in the main repository""" return self.pymisp_version_main @property - def pymisp_version_main(self) -> Dict: + def pymisp_version_main(self) -> dict[str, Any] | list[dict[str, Any]]: """Get the most recent version of PyMISP from github""" r = requests.get('https://raw.githubusercontent.com/MISP/PyMISP/main/pyproject.toml') if r.status_code == 200: @@ -272,26 +294,26 @@ class PyMISP: return {'error': 'Impossible to retrieve the version of the main branch.'} @cached_property - def misp_instance_version(self) -> Dict: + def misp_instance_version(self) -> dict[str, Any] | list[dict[str, Any]]: """Returns the version of the instance.""" response = self._prepare_request('GET', 'servers/getVersion') return self._check_json_response(response) @property - def misp_instance_version_master(self) -> Dict: + def misp_instance_version_master(self) -> dict[str, Any] | list[dict[str, Any]]: """Get the most recent version from github""" r = requests.get('https://raw.githubusercontent.com/MISP/MISP/2.4/VERSION.json') if r.status_code == 200: - master_version = json.loads(r.text) + master_version = loads(r.content) return {'version': '{}.{}.{}'.format(master_version['major'], master_version['minor'], master_version['hotfix'])} return {'error': 'Impossible to retrieve the version of the master branch.'} - def update_misp(self) -> Dict: + def update_misp(self) -> dict[str, Any] | list[dict[str, Any]]: """Trigger a server update""" response = self._prepare_request('POST', 'servers/update') return self._check_json_response(response) - def set_server_setting(self, setting: str, value: Union[str, int, bool], force: bool = False) -> Dict: + def set_server_setting(self, setting: str, value: str | int | bool, force: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Set a setting on the MISP instance :param setting: server setting name @@ -302,7 +324,7 @@ class PyMISP: response = self._prepare_request('POST', f'servers/serverSettingsEdit/{setting}', data=data) return self._check_json_response(response) - def get_server_setting(self, setting: str) -> Dict: + def get_server_setting(self, setting: str) -> dict[str, Any] | list[dict[str, Any]]: """Get a setting from the MISP instance :param setting: server setting name @@ -310,17 +332,17 @@ class PyMISP: response = self._prepare_request('GET', f'servers/getSetting/{setting}') return self._check_json_response(response) - def server_settings(self) -> Dict: + def server_settings(self) -> dict[str, Any] | list[dict[str, Any]]: """Get all the settings from the server""" response = self._prepare_request('GET', 'servers/serverSettings') return self._check_json_response(response) - def restart_workers(self) -> Dict: + def restart_workers(self) -> dict[str, Any] | list[dict[str, Any]]: """Restart all the workers""" response = self._prepare_request('POST', 'servers/restartWorkers') return self._check_json_response(response) - def db_schema_diagnostic(self) -> Dict: + def db_schema_diagnostic(self) -> dict[str, Any] | list[dict[str, Any]]: """Get the schema diagnostic""" response = self._prepare_request('GET', 'servers/dbSchemaDiagnostic') return self._check_json_response(response) @@ -331,14 +353,14 @@ class PyMISP: # ## BEGIN Event ## - def events(self, pythonify: bool = False) -> Union[Dict, List[MISPEvent]]: + def events(self, pythonify: bool = False) -> dict[str, Any] | list[MISPEvent] | list[dict[str, Any]]: """Get all the events from the MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/getEvents :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'events/index') events_r = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in events_r: + if not (self.global_pythonify or pythonify) or isinstance(events_r, dict): return events_r to_return = [] for event in events_r: @@ -347,10 +369,10 @@ class PyMISP: to_return.append(e) return to_return - def get_event(self, event: Union[MISPEvent, int, str, UUID], - deleted: Union[bool, int, list] = False, - extended: Union[bool, int] = False, - pythonify: bool = False) -> Union[Dict, MISPEvent]: + def get_event(self, event: MISPEvent | int | str | UUID, + deleted: bool | int | list[int] = False, + extended: bool | int = False, + pythonify: bool = False) -> dict[str, Any] | MISPEvent: """Get an event from a MISP instance. Includes collections like Attribute, EventReport, Feed, Galaxy, Object, Tag, etc. so the response size may be large : https://www.misp-project.org/openapi/#tag/Events/operation/getEventById @@ -377,7 +399,7 @@ class PyMISP: e.load(event_r) return e - def event_exists(self, event: Union[MISPEvent, int, str, UUID]) -> bool: + def event_exists(self, event: MISPEvent | int | str | UUID) -> bool: """Fast check if event exists. :param event: Event to check @@ -386,7 +408,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'events/view/{event_id}') return self._check_head_response(r) - def add_event(self, event: MISPEvent, pythonify: bool = False, metadata: bool = False) -> Union[Dict, MISPEvent]: + def add_event(self, event: MISPEvent, pythonify: bool = False, metadata: bool = False) -> dict[str, Any] | MISPEvent: """Add a new event on a MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/addEvent :param event: event to add @@ -401,8 +423,8 @@ class PyMISP: e.load(new_event) return e - def update_event(self, event: MISPEvent, event_id: Optional[int] = None, pythonify: bool = False, - metadata: bool = False) -> Union[Dict, MISPEvent]: + def update_event(self, event: MISPEvent, event_id: int | None = None, pythonify: bool = False, + metadata: bool = False) -> dict[str, Any] | MISPEvent: """Update an event on a MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/editEvent :param event: event to update @@ -422,7 +444,7 @@ class PyMISP: e.load(updated_event) return e - def delete_event(self, event: Union[MISPEvent, int, str, UUID]) -> Dict: + def delete_event(self, event: MISPEvent | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete an event from a MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/deleteEvent :param event: event to delete @@ -431,7 +453,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/delete/{event_id}') return self._check_json_response(response) - def publish(self, event: Union[MISPEvent, int, str, UUID], alert: bool = False) -> Dict: + def publish(self, event: MISPEvent | int | str | UUID, alert: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Publish the event with one single HTTP POST: https://www.misp-project.org/openapi/#tag/Events/operation/publishEvent :param event: event to publish @@ -444,7 +466,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/publish/{event_id}') return self._check_json_response(response) - def unpublish(self, event: Union[MISPEvent, int, str, UUID]) -> Dict: + def unpublish(self, event: MISPEvent | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Unpublish the event with one single HTTP POST: https://www.misp-project.org/openapi/#tag/Events/operation/unpublishEvent :param event: event to unpublish @@ -453,7 +475,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/unpublish/{event_id}') return self._check_json_response(response) - def contact_event_reporter(self, event: Union[MISPEvent, int, str, UUID], message: str) -> Dict: + def contact_event_reporter(self, event: MISPEvent | int | str | UUID, message: str) -> dict[str, Any] | list[dict[str, Any]]: """Send a message to the reporter of an event :param event: event with reporter to contact @@ -468,8 +490,8 @@ class PyMISP: # ## BEGIN Event Report ### - def get_event_report(self, event_report: Union[MISPEventReport, int, str, UUID], - pythonify: bool = False) -> Union[Dict, MISPEventReport]: + def get_event_report(self, event_report: MISPEventReport | int | str | UUID, + pythonify: bool = False) -> dict[str, Any] | MISPEventReport: """Get an event report from a MISP instance :param event_report: event report to get @@ -484,8 +506,8 @@ class PyMISP: er.from_dict(**event_report_r) return er - def get_event_reports(self, event_id: Union[int, str], - pythonify: bool = False) -> Union[Dict, List[MISPEventReport]]: + def get_event_reports(self, event_id: int | str, + pythonify: bool = False) -> dict[str, Any] | list[MISPEventReport] | list[dict[str, Any]]: """Get event report from a MISP instance that are attached to an event ID :param event_id: event id to get the event reports for @@ -493,7 +515,7 @@ class PyMISP: """ r = self._prepare_request('GET', f'eventReports/index/event_id:{event_id}') event_reports = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in event_reports: + if not (self.global_pythonify or pythonify) or isinstance(event_reports, dict): return event_reports to_return = [] for event_report in event_reports: @@ -502,7 +524,7 @@ class PyMISP: to_return.append(er) return to_return - def add_event_report(self, event: Union[MISPEvent, int, str, UUID], event_report: MISPEventReport, pythonify: bool = False) -> Union[Dict, MISPEventReport]: + def add_event_report(self, event: MISPEvent | int | str | UUID, event_report: MISPEventReport, pythonify: bool = False) -> dict[str, Any] | MISPEventReport: """Add an event report to an existing MISP event :param event: event to extend @@ -518,7 +540,7 @@ class PyMISP: er.from_dict(**new_event_report) return er - def update_event_report(self, event_report: MISPEventReport, event_report_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPEventReport]: + def update_event_report(self, event_report: MISPEventReport, event_report_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPEventReport: """Update an event report on a MISP instance :param event_report: event report to update @@ -537,7 +559,7 @@ class PyMISP: er.from_dict(**updated_event_report) return er - def delete_event_report(self, event_report: Union[MISPEventReport, int, str, UUID], hard: bool = False) -> Dict: + def delete_event_report(self, event_report: MISPEventReport | int | str | UUID, hard: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Delete an event report from a MISP instance :param event_report: event report to delete @@ -555,7 +577,7 @@ class PyMISP: # ## BEGIN Object ### - def get_object(self, misp_object: Union[MISPObject, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPObject]: + def get_object(self, misp_object: MISPObject | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPObject: """Get an object from the remote MISP instance: https://www.misp-project.org/openapi/#tag/Objects/operation/getObjectById :param misp_object: object to get @@ -570,7 +592,7 @@ class PyMISP: o.from_dict(**misp_object_r) return o - def object_exists(self, misp_object: Union[MISPObject, int, str, UUID]) -> bool: + def object_exists(self, misp_object: MISPObject | int | str | UUID) -> bool: """Fast check if object exists. :param misp_object: Attribute to check @@ -579,7 +601,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'objects/view/{object_id}') return self._check_head_response(r) - def add_object(self, event: Union[MISPEvent, int, str, UUID], misp_object: MISPObject, pythonify: bool = False, break_on_duplicate: bool = False) -> Union[Dict, MISPObject]: + def add_object(self, event: MISPEvent | int | str | UUID, misp_object: MISPObject, pythonify: bool = False, break_on_duplicate: bool = False) -> dict[str, Any] | MISPObject: """Add a MISP Object to an existing MISP event: https://www.misp-project.org/openapi/#tag/Objects/operation/addObject :param event: event to extend @@ -597,7 +619,7 @@ class PyMISP: o.from_dict(**new_object) return o - def update_object(self, misp_object: MISPObject, object_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPObject]: + def update_object(self, misp_object: MISPObject, object_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPObject: """Update an object on a MISP instance :param misp_object: object to update @@ -616,7 +638,7 @@ class PyMISP: o.from_dict(**updated_object) return o - def delete_object(self, misp_object: Union[MISPObject, int, str, UUID], hard: bool = False) -> Dict: + def delete_object(self, misp_object: MISPObject | int | str | UUID, hard: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Delete an object from a MISP instance: https://www.misp-project.org/openapi/#tag/Objects/operation/deleteObject :param misp_object: object to delete @@ -629,7 +651,7 @@ class PyMISP: r = self._prepare_request('POST', f'objects/delete/{object_id}', data=data) return self._check_json_response(r) - def add_object_reference(self, misp_object_reference: MISPObjectReference, pythonify: bool = False) -> Union[Dict, MISPObjectReference]: + def add_object_reference(self, misp_object_reference: MISPObjectReference, pythonify: bool = False) -> dict[str, Any] | MISPObjectReference: """Add a reference to an object :param misp_object_reference: object reference @@ -645,9 +667,9 @@ class PyMISP: def delete_object_reference( self, - object_reference: Union[MISPObjectReference, int, str, UUID], + object_reference: MISPObjectReference | int | str | UUID, hard: bool = False, - ) -> Dict: + ) -> dict[str, Any] | list[dict[str, Any]]: """Delete a reference to an object.""" object_reference_id = get_uuid_or_id_from_abstract_misp(object_reference) query_url = f"objectReferences/delete/{object_reference_id}" @@ -658,14 +680,14 @@ class PyMISP: # Object templates - def object_templates(self, pythonify: bool = False) -> Union[Dict, List[MISPObjectTemplate]]: + def object_templates(self, pythonify: bool = False) -> dict[str, Any] | list[MISPObjectTemplate] | list[dict[str, Any]]: """Get all the object templates :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'objectTemplates/index') templates = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in templates: + if not (self.global_pythonify or pythonify) or isinstance(templates, dict): return templates to_return = [] for object_template in templates: @@ -674,7 +696,7 @@ class PyMISP: to_return.append(o) return to_return - def get_object_template(self, object_template: Union[MISPObjectTemplate, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPObjectTemplate]: + def get_object_template(self, object_template: MISPObjectTemplate | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPObjectTemplate: """Gets the full object template :param object_template: template or ID to get @@ -689,14 +711,14 @@ class PyMISP: t.from_dict(**object_template_r) return t - def get_raw_object_template(self, uuid_or_name: str) -> Dict: + def get_raw_object_template(self, uuid_or_name: str) -> dict[str, Any] | list[dict[str, Any]]: """Get a row template. It needs to be present on disk on the MISP instance you're connected to. The response of this method can be passed to MISPObject(, misp_objects_template_custom=) """ r = self._prepare_request('GET', f'objectTemplates/getRaw/{uuid_or_name}') return self._check_json_response(r) - def update_object_templates(self) -> Dict: + def update_object_templates(self) -> dict[str, Any] | list[dict[str, Any]]: """Trigger an update of the object templates""" response = self._prepare_request('POST', 'objectTemplates/update') return self._check_json_response(response) @@ -705,14 +727,14 @@ class PyMISP: # ## BEGIN Attribute ### - def attributes(self, pythonify: bool = False) -> Union[Dict, List[MISPAttribute]]: + def attributes(self, pythonify: bool = False) -> dict[str, Any] | list[MISPAttribute] | list[dict[str, Any]]: """Get all the attributes from the MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/getAttributes :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'attributes/index') attributes_r = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in attributes_r: + if not (self.global_pythonify or pythonify) or isinstance(attributes_r, dict): return attributes_r to_return = [] for attribute in attributes_r: @@ -721,7 +743,7 @@ class PyMISP: to_return.append(a) return to_return - def get_attribute(self, attribute: Union[MISPAttribute, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPAttribute]: + def get_attribute(self, attribute: MISPAttribute | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPAttribute: """Get an attribute from a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/getAttributeById :param attribute: attribute to get @@ -736,7 +758,7 @@ class PyMISP: a.from_dict(**attribute_r) return a - def attribute_exists(self, attribute: Union[MISPAttribute, int, str, UUID]) -> bool: + def attribute_exists(self, attribute: MISPAttribute | int | str | UUID) -> bool: """Fast check if attribute exists. :param attribute: Attribute to check @@ -745,7 +767,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'attributes/view/{attribute_id}') return self._check_head_response(r) - def add_attribute(self, event: Union[MISPEvent, int, str, UUID], attribute: Union[MISPAttribute, Iterable], pythonify: bool = False, break_on_duplicate: bool = True) -> Union[Dict, MISPAttribute, MISPShadowAttribute]: + def add_attribute(self, event: MISPEvent | int | str | UUID, attribute: MISPAttribute | Iterable[str], pythonify: bool = False, break_on_duplicate: bool = True) -> dict[str, Any] | MISPAttribute | MISPShadowAttribute: """Add an attribute to an existing MISP event: https://www.misp-project.org/openapi/#tag/Attributes/operation/addAttribute :param event: event to extend @@ -763,7 +785,7 @@ class PyMISP: # Multiple attributes were passed at once, the handling is totally different if not (self.global_pythonify or pythonify): return new_attribute - to_return: Dict[str, List[MISPAttribute]] = {'attributes': []} + to_return: dict[str, list[MISPAttribute]] = {'attributes': []} if 'errors' in new_attribute: to_return['errors'] = new_attribute['errors'] @@ -792,7 +814,7 @@ class PyMISP: a.from_dict(**new_attribute) return a - def update_attribute(self, attribute: MISPAttribute, attribute_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPAttribute, MISPShadowAttribute]: + def update_attribute(self, attribute: MISPAttribute, attribute_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPAttribute | MISPShadowAttribute: """Update an attribute on a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/editAttribute :param attribute: attribute to update @@ -817,7 +839,7 @@ class PyMISP: a.from_dict(**updated_attribute) return a - def delete_attribute(self, attribute: Union[MISPAttribute, int, str, UUID], hard: bool = False) -> Dict: + def delete_attribute(self, attribute: MISPAttribute | int | str | UUID, hard: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Delete an attribute from a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/deleteAttribute :param attribute: attribute to delete @@ -837,7 +859,7 @@ class PyMISP: return self.delete_attribute_proposal(attribute_id) return response - def restore_attribute(self, attribute: Union[MISPAttribute, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPAttribute]: + def restore_attribute(self, attribute: MISPAttribute | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPAttribute: """Restore a soft deleted attribute from a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/restoreAttribute :param attribute: attribute to restore @@ -855,7 +877,7 @@ class PyMISP: # ## BEGIN Attribute Proposal ### - def attribute_proposals(self, event: Optional[Union[MISPEvent, int, str, UUID]] = None, pythonify: bool = False) -> Union[Dict, List[MISPShadowAttribute]]: + def attribute_proposals(self, event: MISPEvent | int | str | UUID | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPShadowAttribute] | list[dict[str, Any]]: """Get all the attribute proposals :param event: event @@ -867,7 +889,7 @@ class PyMISP: else: r = self._prepare_request('GET', 'shadowAttributes/index') attribute_proposals = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in attribute_proposals: + if not (self.global_pythonify or pythonify) or isinstance(attribute_proposals, dict): return attribute_proposals to_return = [] for attribute_proposal in attribute_proposals: @@ -876,7 +898,7 @@ class PyMISP: to_return.append(a) return to_return - def get_attribute_proposal(self, proposal: Union[MISPShadowAttribute, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPShadowAttribute]: + def get_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPShadowAttribute: """Get an attribute proposal :param proposal: proposal to get @@ -893,7 +915,7 @@ class PyMISP: # NOTE: the tree following method have a very specific meaning, look at the comments - def add_attribute_proposal(self, event: Union[MISPEvent, int, str, UUID], attribute: MISPAttribute, pythonify: bool = False) -> Union[Dict, MISPShadowAttribute]: + def add_attribute_proposal(self, event: MISPEvent | int | str | UUID, attribute: MISPAttribute, pythonify: bool = False) -> dict[str, Any] | MISPShadowAttribute: """Propose a new attribute in an event :param event: event to receive new attribute @@ -909,7 +931,7 @@ class PyMISP: a.from_dict(**new_attribute_proposal) return a - def update_attribute_proposal(self, initial_attribute: Union[MISPAttribute, int, str, UUID], attribute: MISPAttribute, pythonify: bool = False) -> Union[Dict, MISPShadowAttribute]: + def update_attribute_proposal(self, initial_attribute: MISPAttribute | int | str | UUID, attribute: MISPAttribute, pythonify: bool = False) -> dict[str, Any] | MISPShadowAttribute: """Propose a change for an attribute :param initial_attribute: attribute to change @@ -925,7 +947,7 @@ class PyMISP: a.from_dict(**update_attribute_proposal) return a - def delete_attribute_proposal(self, attribute: Union[MISPAttribute, int, str, UUID]) -> Dict: + def delete_attribute_proposal(self, attribute: MISPAttribute | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Propose the deletion of an attribute :param attribute: attribute to delete @@ -934,7 +956,7 @@ class PyMISP: response = self._prepare_request('POST', f'shadowAttributes/delete/{attribute_id}') return self._check_json_response(response) - def accept_attribute_proposal(self, proposal: Union[MISPShadowAttribute, int, str, UUID]) -> Dict: + def accept_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Accept a proposal. You cannot modify an existing proposal, only accept/discard :param proposal: attribute proposal to accept @@ -943,7 +965,7 @@ class PyMISP: response = self._prepare_request('POST', f'shadowAttributes/accept/{proposal_id}') return self._check_json_response(response) - def discard_attribute_proposal(self, proposal: Union[MISPShadowAttribute, int, str, UUID]) -> Dict: + def discard_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Discard a proposal. You cannot modify an existing proposal, only accept/discard :param proposal: attribute proposal to discard @@ -956,9 +978,9 @@ class PyMISP: # ## BEGIN Sighting ### - def sightings(self, misp_entity: Optional[AbstractMISP] = None, - org: Optional[Union[MISPOrganisation, int, str, UUID]] = None, - pythonify: bool = False) -> Union[Dict, List[MISPSighting]]: + def sightings(self, misp_entity: AbstractMISP | None = None, + org: MISPOrganisation | int | str | UUID | None = None, + pythonify: bool = False) -> dict[str, Any] | list[MISPSighting] | list[dict[str, Any]]: """Get the list of sightings related to a MISPEvent or a MISPAttribute (depending on type of misp_entity): https://www.misp-project.org/openapi/#tag/Sightings/operation/getSightingsByEventId :param misp_entity: MISP entity @@ -981,7 +1003,7 @@ class PyMISP: r = self._prepare_request('POST', url, data=to_post) sightings = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in sightings: + if not (self.global_pythonify or pythonify) or isinstance(sightings, dict): return sightings to_return = [] for sighting in sightings: @@ -990,9 +1012,9 @@ class PyMISP: to_return.append(s) return to_return - def add_sighting(self, sighting: MISPSighting, - attribute: Optional[Union[MISPAttribute, int, str, UUID]] = None, - pythonify: bool = False) -> Union[Dict, MISPSighting]: + def add_sighting(self, sighting: MISPSighting | dict[str, Any], + attribute: MISPAttribute | int | str | UUID | None = None, + pythonify: bool = False) -> dict[str, Any] | MISPSighting: """Add a new sighting (globally, or to a specific attribute): https://www.misp-project.org/openapi/#tag/Sightings/operation/addSighting and https://www.misp-project.org/openapi/#tag/Sightings/operation/getSightingsByEventId :param sighting: sighting to add @@ -1012,7 +1034,7 @@ class PyMISP: s.from_dict(**new_sighting) return s - def delete_sighting(self, sighting: Union[MISPSighting, int, str, UUID]) -> Dict: + def delete_sighting(self, sighting: MISPSighting | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a sighting from a MISP instance: https://www.misp-project.org/openapi/#tag/Sightings/operation/deleteSighting :param sighting: sighting to delete @@ -1025,7 +1047,7 @@ class PyMISP: # ## BEGIN Tags ### - def tags(self, pythonify: bool = False, **kw_params) -> Union[Dict, List[MISPTag]]: + def tags(self, pythonify: bool = False, **kw_params) -> dict[str, Any] | list[MISPTag]: # type: ignore[no-untyped-def] """Get the list of existing tags: https://www.misp-project.org/openapi/#tag/Tags/operation/getTags :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM @@ -1041,7 +1063,7 @@ class PyMISP: to_return.append(t) return to_return - def get_tag(self, tag: Union[MISPTag, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPTag]: + def get_tag(self, tag: MISPTag | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Get a tag by id: https://www.misp-project.org/openapi/#tag/Tags/operation/getTagById :param tag: tag to get @@ -1056,7 +1078,7 @@ class PyMISP: t.from_dict(**tag_r) return t - def add_tag(self, tag: MISPTag, pythonify: bool = False) -> Union[Dict, MISPTag]: + def add_tag(self, tag: MISPTag, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Add a new tag on a MISP instance: https://www.misp-project.org/openapi/#tag/Tags/operation/addTag The user calling this method needs the Tag Editor permission. It doesn't add a tag to an event, simply creates it on the MISP instance. @@ -1072,7 +1094,7 @@ class PyMISP: t.from_dict(**new_tag) return t - def enable_tag(self, tag: MISPTag, pythonify: bool = False) -> Union[Dict, MISPTag]: + def enable_tag(self, tag: MISPTag, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Enable a tag :param tag: tag to enable @@ -1081,7 +1103,7 @@ class PyMISP: tag.hide_tag = False return self.update_tag(tag, pythonify=pythonify) - def disable_tag(self, tag: MISPTag, pythonify: bool = False) -> Union[Dict, MISPTag]: + def disable_tag(self, tag: MISPTag, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Disable a tag :param tag: tag to disable @@ -1090,7 +1112,7 @@ class PyMISP: tag.hide_tag = True return self.update_tag(tag, pythonify=pythonify) - def update_tag(self, tag: MISPTag, tag_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPTag]: + def update_tag(self, tag: MISPTag, tag_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Edit only the provided parameters of a tag: https://www.misp-project.org/openapi/#tag/Tags/operation/editTag :param tag: tag to update @@ -1109,7 +1131,7 @@ class PyMISP: t.from_dict(**updated_tag) return t - def delete_tag(self, tag: Union[MISPTag, int, str, UUID]) -> Dict: + def delete_tag(self, tag: MISPTag | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a tag from a MISP instance: https://www.misp-project.org/openapi/#tag/Tags/operation/deleteTag :param tag: tag to delete @@ -1118,7 +1140,7 @@ class PyMISP: response = self._prepare_request('POST', f'tags/delete/{tag_id}') return self._check_json_response(response) - def search_tags(self, tagname: str, strict_tagname: bool = False, pythonify: bool = False) -> Union[Dict, List[MISPTag]]: + def search_tags(self, tagname: str, strict_tagname: bool = False, pythonify: bool = False) -> dict[str, Any] | list[MISPTag] | list[dict[str, Any]]: """Search for tags by name: https://www.misp-project.org/openapi/#tag/Tags/operation/searchTag :param tag_name: Name to search, use % for substrings matches. @@ -1127,9 +1149,9 @@ class PyMISP: query = {'tagname': tagname, 'strict_tagname': strict_tagname} response = self._prepare_request('POST', 'tags/search', data=query) normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response - to_return: List[MISPTag] = [] + to_return: list[MISPTag] = [] for tag in normalized_response: t = MISPTag() t.from_dict(**tag) @@ -1140,14 +1162,14 @@ class PyMISP: # ## BEGIN Taxonomies ### - def taxonomies(self, pythonify: bool = False) -> Union[Dict, List[MISPTaxonomy]]: + def taxonomies(self, pythonify: bool = False) -> dict[str, Any] | list[MISPTaxonomy] | list[dict[str, Any]]: """Get all the taxonomies: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/getTaxonomies :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'taxonomies/index') taxonomies = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in taxonomies: + if not (self.global_pythonify or pythonify) or isinstance(taxonomies, dict): return taxonomies to_return = [] for taxonomy in taxonomies: @@ -1156,7 +1178,7 @@ class PyMISP: to_return.append(t) return to_return - def get_taxonomy(self, taxonomy: Union[MISPTaxonomy, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPTaxonomy]: + def get_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPTaxonomy: """Get a taxonomy by id or namespace from a MISP instance: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/getTaxonomyById :param taxonomy: taxonomy to get @@ -1171,7 +1193,7 @@ class PyMISP: t.from_dict(**taxonomy_r) return t - def enable_taxonomy(self, taxonomy: Union[MISPTaxonomy, int, str, UUID]) -> Dict: + def enable_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Enable a taxonomy: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/enableTaxonomy :param taxonomy: taxonomy to enable @@ -1180,7 +1202,7 @@ class PyMISP: response = self._prepare_request('POST', f'taxonomies/enable/{taxonomy_id}') return self._check_json_response(response) - def disable_taxonomy(self, taxonomy: Union[MISPTaxonomy, int, str, UUID]) -> Dict: + def disable_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Disable a taxonomy: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/disableTaxonomy :param taxonomy: taxonomy to disable @@ -1188,9 +1210,12 @@ class PyMISP: taxonomy_id = get_uuid_or_id_from_abstract_misp(taxonomy) self.disable_taxonomy_tags(taxonomy_id) response = self._prepare_request('POST', f'taxonomies/disable/{taxonomy_id}') - return self._check_json_response(response) + try: + return self._check_json_response(response) + except PyMISPError: + return self._check_json_response(response) - def disable_taxonomy_tags(self, taxonomy: Union[MISPTaxonomy, int, str, UUID]) -> Dict: + def disable_taxonomy_tags(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Disable all the tags of a taxonomy :param taxonomy: taxonomy with tags to disable @@ -1199,7 +1224,7 @@ class PyMISP: response = self._prepare_request('POST', f'taxonomies/disableTag/{taxonomy_id}') return self._check_json_response(response) - def enable_taxonomy_tags(self, taxonomy: Union[MISPTaxonomy, int, str, UUID]) -> Dict: + def enable_taxonomy_tags(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Enable all the tags of a taxonomy. NOTE: this is automatically done when you call enable_taxonomy :param taxonomy: taxonomy with tags to enable @@ -1212,18 +1237,18 @@ class PyMISP: raise PyMISPError(f"The taxonomy {t.namespace} is not enabled.") elif not t['Taxonomy']['enabled']: raise PyMISPError(f"The taxonomy {t['Taxonomy']['namespace']} is not enabled.") - url = urljoin(self.root_url, 'taxonomies/addTag/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, f'taxonomies/addTag/{taxonomy_id}') response = self._prepare_request('POST', url) return self._check_json_response(response) - def update_taxonomies(self) -> Dict: + def update_taxonomies(self) -> dict[str, Any] | list[dict[str, Any]]: """Update all the taxonomies: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/updateTaxonomies""" response = self._prepare_request('POST', 'taxonomies/update') return self._check_json_response(response) - def set_taxonomy_required(self, taxonomy: Union[MISPTaxonomy, int, str], required: bool = False) -> Dict: + def set_taxonomy_required(self, taxonomy: MISPTaxonomy | int | str, required: bool = False) -> dict[str, Any] | list[dict[str, Any]]: taxonomy_id = get_uuid_or_id_from_abstract_misp(taxonomy) - url = urljoin(self.root_url, 'taxonomies/toggleRequired/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, f'taxonomies/toggleRequired/{taxonomy_id}') payload = { "Taxonomy": { "required": required @@ -1236,7 +1261,7 @@ class PyMISP: # ## BEGIN Warninglists ### - def warninglists(self, pythonify: bool = False) -> Union[Dict, List[MISPWarninglist]]: + def warninglists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPWarninglist]: """Get all the warninglists: https://www.misp-project.org/openapi/#tag/Warninglists/operation/getWarninglists :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM @@ -1252,7 +1277,7 @@ class PyMISP: to_return.append(w) return to_return - def get_warninglist(self, warninglist: Union[MISPWarninglist, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPWarninglist]: + def get_warninglist(self, warninglist: MISPWarninglist | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPWarninglist: """Get a warninglist by id: https://www.misp-project.org/openapi/#tag/Warninglists/operation/getWarninglistById :param warninglist: warninglist to get @@ -1267,16 +1292,16 @@ class PyMISP: w.from_dict(**wl) return w - def toggle_warninglist(self, warninglist_id: Optional[Union[str, int, List[int]]] = None, warninglist_name: Optional[Union[str, List[str]]] = None, force_enable: bool = False) -> Dict: + def toggle_warninglist(self, warninglist_id: str | int | list[int] | None = None, warninglist_name: str | list[str] | None = None, force_enable: bool | None = None) -> dict[str, Any] | list[dict[str, Any]]: '''Toggle (enable/disable) the status of a warninglist by id: https://www.misp-project.org/openapi/#tag/Warninglists/operation/toggleEnableWarninglist :param warninglist_id: ID of the WarningList :param warninglist_name: name of the WarningList - :param force_enable: Force the warning list in the enabled state (does nothing if already enabled) + :param force_enable: Force the warning list in the enabled state (does nothing if already enabled) - None means toggle. ''' if warninglist_id is None and warninglist_name is None: raise PyMISPError('Either warninglist_id or warninglist_name is required.') - query: Dict[str, Union[List[str], List[int], bool]] = {} + query: dict[str, list[str] | list[int] | bool] = {} if warninglist_id is not None: if isinstance(warninglist_id, list): query['id'] = warninglist_id @@ -1287,12 +1312,12 @@ class PyMISP: query['name'] = warninglist_name else: query['name'] = [warninglist_name] - if force_enable: + if force_enable is not None: query['enabled'] = force_enable response = self._prepare_request('POST', 'warninglists/toggleEnable', data=query) return self._check_json_response(response) - def enable_warninglist(self, warninglist: Union[MISPWarninglist, int, str, UUID]) -> Dict: + def enable_warninglist(self, warninglist: MISPWarninglist | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Enable a warninglist :param warninglist: warninglist to enable @@ -1300,7 +1325,7 @@ class PyMISP: warninglist_id = get_uuid_or_id_from_abstract_misp(warninglist) return self.toggle_warninglist(warninglist_id=warninglist_id, force_enable=True) - def disable_warninglist(self, warninglist: Union[MISPWarninglist, int, str, UUID]) -> Dict: + def disable_warninglist(self, warninglist: MISPWarninglist | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Disable a warninglist :param warninglist: warninglist to disable @@ -1308,15 +1333,18 @@ class PyMISP: warninglist_id = get_uuid_or_id_from_abstract_misp(warninglist) return self.toggle_warninglist(warninglist_id=warninglist_id, force_enable=False) - def values_in_warninglist(self, value: Iterable) -> Dict: + def values_in_warninglist(self, value: Iterable[str]) -> dict[str, Any] | list[dict[str, Any]]: """Check if IOC values are in warninglist :param value: iterator with values to check """ response = self._prepare_request('POST', 'warninglists/checkValue', data=value) - return self._check_json_response(response) + try: + return self._check_json_response(response) + except PyMISPError: + return self._check_json_response(response) - def update_warninglists(self) -> Dict: + def update_warninglists(self) -> dict[str, Any] | list[dict[str, Any]]: """Update all the warninglists: https://www.misp-project.org/openapi/#tag/Warninglists/operation/updateWarninglists""" response = self._prepare_request('POST', 'warninglists/update') return self._check_json_response(response) @@ -1325,14 +1353,14 @@ class PyMISP: # ## BEGIN Noticelist ### - def noticelists(self, pythonify: bool = False) -> Union[Dict, List[MISPNoticelist]]: + def noticelists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPNoticelist] | list[dict[str, Any]]: """Get all the noticelists: https://www.misp-project.org/openapi/#tag/Noticelists/operation/getNoticelists :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'noticelists/index') noticelists = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in noticelists: + if not (self.global_pythonify or pythonify) or isinstance(noticelists, dict): return noticelists to_return = [] for noticelist in noticelists: @@ -1341,7 +1369,7 @@ class PyMISP: to_return.append(n) return to_return - def get_noticelist(self, noticelist: Union[MISPNoticelist, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPNoticelist]: + def get_noticelist(self, noticelist: MISPNoticelist | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPNoticelist: """Get a noticelist by id: https://www.misp-project.org/openapi/#tag/Noticelists/operation/getNoticelistById :param notistlist: Noticelist to get @@ -1356,7 +1384,7 @@ class PyMISP: n.from_dict(**noticelist_j) return n - def enable_noticelist(self, noticelist: Union[MISPNoticelist, int, str, UUID]) -> Dict: + def enable_noticelist(self, noticelist: MISPNoticelist | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Enable a noticelist by id: https://www.misp-project.org/openapi/#tag/Noticelists/operation/toggleEnableNoticelist :param noticelist: Noticelist to enable @@ -1367,7 +1395,7 @@ class PyMISP: response = self._prepare_request('POST', f'noticelists/enableNoticelist/{noticelist_id}/true') return self._check_json_response(response) - def disable_noticelist(self, noticelist: Union[MISPNoticelist, int, str, UUID]) -> Dict: + def disable_noticelist(self, noticelist: MISPNoticelist | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Disable a noticelist by id :param noticelist: Noticelist to disable @@ -1378,7 +1406,7 @@ class PyMISP: response = self._prepare_request('POST', f'noticelists/enableNoticelist/{noticelist_id}') return self._check_json_response(response) - def update_noticelists(self) -> Dict: + def update_noticelists(self) -> dict[str, Any] | list[dict[str, Any]]: """Update all the noticelists: https://www.misp-project.org/openapi/#tag/Noticelists/operation/updateNoticelists""" response = self._prepare_request('POST', 'noticelists/update') return self._check_json_response(response) @@ -1387,14 +1415,14 @@ class PyMISP: # ## BEGIN Correlation Exclusions ### - def correlation_exclusions(self, pythonify: bool = False) -> Union[Dict, List[MISPCorrelationExclusion]]: + def correlation_exclusions(self, pythonify: bool = False) -> dict[str, Any] | list[MISPCorrelationExclusion] | list[dict[str, Any]]: """Get all the correlation exclusions :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'correlation_exclusions') correlation_exclusions = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in correlation_exclusions: + if not (self.global_pythonify or pythonify) or isinstance(correlation_exclusions, dict): return correlation_exclusions to_return = [] for correlation_exclusion in correlation_exclusions: @@ -1403,7 +1431,7 @@ class PyMISP: to_return.append(c) return to_return - def get_correlation_exclusion(self, correlation_exclusion: Union[MISPCorrelationExclusion, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPCorrelationExclusion]: + def get_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPCorrelationExclusion: """Get a correlation exclusion by ID :param correlation_exclusion: Correlation exclusion to get @@ -1418,7 +1446,7 @@ class PyMISP: c.from_dict(**correlation_exclusion_j) return c - def add_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion, pythonify: bool = False) -> Union[Dict, MISPCorrelationExclusion]: + def add_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion, pythonify: bool = False) -> dict[str, Any] | MISPCorrelationExclusion: """Add a new correlation exclusion :param correlation_exclusion: correlation exclusion to add @@ -1432,7 +1460,7 @@ class PyMISP: c.from_dict(**new_correlation_exclusion) return c - def delete_correlation_exclusion(self, correlation_exclusion: Union[MISPCorrelationExclusion, int, str, UUID]) -> Dict: + def delete_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a correlation exclusion :param correlation_exclusion: The MISPCorrelationExclusion you wish to delete from MISP @@ -1441,7 +1469,7 @@ class PyMISP: r = self._prepare_request('POST', f'correlation_exclusions/delete/{exclusion_id}') return self._check_json_response(r) - def clean_correlation_exclusions(self): + def clean_correlation_exclusions(self) -> dict[str, Any] | list[dict[str, Any]]: """Initiate correlation exclusions cleanup""" r = self._prepare_request('POST', 'correlation_exclusions/clean') return self._check_json_response(r) @@ -1454,14 +1482,14 @@ class PyMISP: self, withCluster: bool = False, pythonify: bool = False, - ) -> Union[Dict, List[MISPGalaxy]]: + ) -> dict[str, Any] | list[MISPGalaxy] | list[dict[str, Any]]: """Get all the galaxies: https://www.misp-project.org/openapi/#tag/Galaxies/operation/getGalaxies :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'galaxies/index') galaxies = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in galaxies: + if not (self.global_pythonify or pythonify) or isinstance(galaxies, dict): return galaxies to_return = [] for galaxy in galaxies: @@ -1475,11 +1503,11 @@ class PyMISP: value: str, withCluster: bool = False, pythonify: bool = False, - ) -> Union[Dict, List[MISPGalaxy]]: + ) -> dict[str, Any] | list[MISPGalaxy] | list[dict[str, Any]]: """Text search to find a matching galaxy name, namespace, description, or uuid.""" r = self._prepare_request("POST", "galaxies", data={"value": value}) galaxies = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or "errors" in galaxies: + if not (self.global_pythonify or pythonify) or isinstance(galaxies, dict): return galaxies to_return = [] for galaxy in galaxies: @@ -1488,7 +1516,7 @@ class PyMISP: to_return.append(g) return to_return - def get_galaxy(self, galaxy: Union[MISPGalaxy, int, str, UUID], withCluster: bool = False, pythonify: bool = False) -> Union[Dict, MISPGalaxy]: + def get_galaxy(self, galaxy: MISPGalaxy | int | str | UUID, withCluster: bool = False, pythonify: bool = False) -> dict[str, Any] | MISPGalaxy: """Get a galaxy by id: https://www.misp-project.org/openapi/#tag/Galaxies/operation/getGalaxyById :param galaxy: galaxy to get @@ -1504,7 +1532,7 @@ class PyMISP: g.from_dict(**galaxy_j, withCluster=withCluster) return g - def search_galaxy_clusters(self, galaxy: Union[MISPGalaxy, int, str, UUID], context: str = "all", searchall: Optional[str] = None, pythonify: bool = False) -> Union[Dict, List[MISPGalaxyCluster]]: + def search_galaxy_clusters(self, galaxy: MISPGalaxy | int | str | UUID, context: str = "all", searchall: str | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPGalaxyCluster] | list[dict[str, Any]]: """Searches the galaxy clusters within a specific galaxy: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/getGalaxyClusters and https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/getGalaxyClusterById :param galaxy: The MISPGalaxy you wish to search in @@ -1514,7 +1542,7 @@ class PyMISP: """ galaxy_id = get_uuid_or_id_from_abstract_misp(galaxy) - allowed_context_types: List[str] = ["all", "default", "custom", "org", "deleted"] + allowed_context_types: list[str] = ["all", "default", "custom", "org", "deleted"] if context not in allowed_context_types: raise PyMISPError(f"The context must be one of {', '.join(allowed_context_types)}") kw_params = {"context": context} @@ -1522,7 +1550,7 @@ class PyMISP: kw_params["searchall"] = searchall r = self._prepare_request('POST', f"galaxy_clusters/index/{galaxy_id}", data=kw_params) clusters_j = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in clusters_j: + if not (self.global_pythonify or pythonify) or isinstance(clusters_j, dict): return clusters_j response = [] for cluster in clusters_j: @@ -1531,12 +1559,12 @@ class PyMISP: response.append(c) return response - def update_galaxies(self) -> Dict: + def update_galaxies(self) -> dict[str, Any] | list[dict[str, Any]]: """Update all the galaxies: https://www.misp-project.org/openapi/#tag/Galaxies/operation/updateGalaxies""" response = self._prepare_request('POST', 'galaxies/update') return self._check_json_response(response) - def get_galaxy_cluster(self, galaxy_cluster: Union[MISPGalaxyCluster, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPGalaxyCluster]: + def get_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Gets a specific galaxy cluster :param galaxy_cluster: The MISPGalaxyCluster you want to get @@ -1552,7 +1580,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def add_galaxy_cluster(self, galaxy: Union[MISPGalaxy, str, UUID], galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> Union[Dict, MISPGalaxyCluster]: + def add_galaxy_cluster(self, galaxy: MISPGalaxy | str | UUID, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Add a new galaxy cluster to a MISP Galaxy: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/addGalaxyCluster :param galaxy: A MISPGalaxy (or UUID) where you wish to add the galaxy cluster @@ -1572,7 +1600,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def update_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> Union[Dict, MISPGalaxyCluster]: + def update_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Update a custom galaxy cluster: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/editGalaxyCluster ;param galaxy_cluster: The MISPGalaxyCluster you wish to update @@ -1591,7 +1619,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def publish_galaxy_cluster(self, galaxy_cluster: Union[MISPGalaxyCluster, int, str, UUID]) -> Dict: + def publish_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Publishes a galaxy cluster: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/publishGalaxyCluster :param galaxy_cluster: The galaxy cluster you wish to publish @@ -1603,7 +1631,7 @@ class PyMISP: response = self._check_json_response(r) return response - def fork_galaxy_cluster(self, galaxy: Union[MISPGalaxy, int, str, UUID], galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> Union[Dict, MISPGalaxyCluster]: + def fork_galaxy_cluster(self, galaxy: MISPGalaxy | int | str | UUID, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Forks an existing galaxy cluster, creating a new one with matching attributes :param galaxy: The galaxy (or galaxy ID) where the cluster you want to fork resides @@ -1627,7 +1655,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def delete_galaxy_cluster(self, galaxy_cluster: Union[MISPGalaxyCluster, int, str, UUID], hard=False) -> Dict: + def delete_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID, hard: bool=False) -> dict[str, Any] | list[dict[str, Any]]: """Deletes a galaxy cluster from MISP: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/deleteGalaxyCluster :param galaxy_cluster: The MISPGalaxyCluster you wish to delete from MISP @@ -1643,7 +1671,7 @@ class PyMISP: r = self._prepare_request('POST', f'galaxy_clusters/delete/{cluster_id}', data=data) return self._check_json_response(r) - def add_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> Dict: + def add_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> dict[str, Any] | list[dict[str, Any]]: """Add a galaxy cluster relation, cluster relation must include cluster UUIDs in both directions @@ -1653,7 +1681,7 @@ class PyMISP: cluster_rel_j = self._check_json_response(r) return cluster_rel_j - def update_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> Dict: + def update_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> dict[str, Any] | list[dict[str, Any]]: """Update a galaxy cluster relation :param galaxy_cluster_relation: The MISPGalaxyClusterRelation to update @@ -1663,7 +1691,7 @@ class PyMISP: cluster_rel_j = self._check_json_response(r) return cluster_rel_j - def delete_galaxy_cluster_relation(self, galaxy_cluster_relation: Union[MISPGalaxyClusterRelation, int, str, UUID]) -> Dict: + def delete_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a galaxy cluster relation :param galaxy_cluster_relation: The MISPGalaxyClusterRelation to delete @@ -1677,14 +1705,14 @@ class PyMISP: # ## BEGIN Feed ### - def feeds(self, pythonify: bool = False) -> Union[Dict, List[MISPFeed]]: + def feeds(self, pythonify: bool = False) -> dict[str, Any] | list[MISPFeed] | list[dict[str, Any]]: """Get the list of existing feeds: https://www.misp-project.org/openapi/#tag/Feeds/operation/getFeeds :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'feeds/index') feeds = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in feeds: + if not (self.global_pythonify or pythonify) or isinstance(feeds, dict): return feeds to_return = [] for feed in feeds: @@ -1693,7 +1721,7 @@ class PyMISP: to_return.append(f) return to_return - def get_feed(self, feed: Union[MISPFeed, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPFeed]: + def get_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Get a feed by id: https://www.misp-project.org/openapi/#tag/Feeds/operation/getFeedById :param feed: feed to get @@ -1708,7 +1736,7 @@ class PyMISP: f.from_dict(**feed_j) return f - def add_feed(self, feed: MISPFeed, pythonify: bool = False) -> Union[Dict, MISPFeed]: + def add_feed(self, feed: MISPFeed, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Add a new feed on a MISP instance: https://www.misp-project.org/openapi/#tag/Feeds/operation/addFeed :param feed: feed to add @@ -1723,7 +1751,7 @@ class PyMISP: f.from_dict(**new_feed) return f - def enable_feed(self, feed: Union[MISPFeed, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPFeed]: + def enable_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Enable a feed; fetching it will create event(s): https://www.misp-project.org/openapi/#tag/Feeds/operation/enableFeed :param feed: feed to enable @@ -1738,7 +1766,7 @@ class PyMISP: f.enabled = True return self.update_feed(feed=f, pythonify=pythonify) - def disable_feed(self, feed: Union[MISPFeed, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPFeed]: + def disable_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Disable a feed: https://www.misp-project.org/openapi/#tag/Feeds/operation/disableFeed :param feed: feed to disable @@ -1753,7 +1781,7 @@ class PyMISP: f.enabled = False return self.update_feed(feed=f, pythonify=pythonify) - def enable_feed_cache(self, feed: Union[MISPFeed, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPFeed]: + def enable_feed_cache(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Enable the caching of a feed :param feed: feed to enable caching @@ -1768,7 +1796,7 @@ class PyMISP: f.caching_enabled = True return self.update_feed(feed=f, pythonify=pythonify) - def disable_feed_cache(self, feed: Union[MISPFeed, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPFeed]: + def disable_feed_cache(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Disable the caching of a feed :param feed: feed to disable caching @@ -1783,7 +1811,7 @@ class PyMISP: f.caching_enabled = False return self.update_feed(feed=f, pythonify=pythonify) - def update_feed(self, feed: MISPFeed, feed_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPFeed]: + def update_feed(self, feed: MISPFeed, feed_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Update a feed on a MISP instance :param feed: feed to update @@ -1803,7 +1831,7 @@ class PyMISP: f.from_dict(**updated_feed) return f - def delete_feed(self, feed: Union[MISPFeed, int, str, UUID]) -> Dict: + def delete_feed(self, feed: MISPFeed | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a feed from a MISP instance :param feed: feed to delete @@ -1812,7 +1840,7 @@ class PyMISP: response = self._prepare_request('POST', f'feeds/delete/{feed_id}') return self._check_json_response(response) - def fetch_feed(self, feed: Union[MISPFeed, int, str, UUID]) -> Dict: + def fetch_feed(self, feed: MISPFeed | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Fetch one single feed by id: https://www.misp-project.org/openapi/#tag/Feeds/operation/fetchFromFeed :param feed: feed to fetch @@ -1821,12 +1849,12 @@ class PyMISP: response = self._prepare_request('GET', f'feeds/fetchFromFeed/{feed_id}') return self._check_json_response(response) - def cache_all_feeds(self) -> Dict: + def cache_all_feeds(self) -> dict[str, Any] | list[dict[str, Any]]: """ Cache all the feeds: https://www.misp-project.org/openapi/#tag/Feeds/operation/cacheFeeds""" response = self._prepare_request('GET', 'feeds/cacheFeeds/all') return self._check_json_response(response) - def cache_feed(self, feed: Union[MISPFeed, int, str, UUID]) -> Dict: + def cache_feed(self, feed: MISPFeed | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Cache a specific feed by id: https://www.misp-project.org/openapi/#tag/Feeds/operation/cacheFeeds :param feed: feed to cache @@ -1835,22 +1863,22 @@ class PyMISP: response = self._prepare_request('GET', f'feeds/cacheFeeds/{feed_id}') return self._check_json_response(response) - def cache_freetext_feeds(self) -> Dict: + def cache_freetext_feeds(self) -> dict[str, Any] | list[dict[str, Any]]: """Cache all the freetext feeds""" response = self._prepare_request('GET', 'feeds/cacheFeeds/freetext') return self._check_json_response(response) - def cache_misp_feeds(self) -> Dict: + def cache_misp_feeds(self) -> dict[str, Any] | list[dict[str, Any]]: """Cache all the MISP feeds""" response = self._prepare_request('GET', 'feeds/cacheFeeds/misp') return self._check_json_response(response) - def compare_feeds(self) -> Dict: + def compare_feeds(self) -> dict[str, Any] | list[dict[str, Any]]: """Generate the comparison matrix for all the MISP feeds""" response = self._prepare_request('GET', 'feeds/compareFeeds') return self._check_json_response(response) - def load_default_feeds(self) -> Dict: + def load_default_feeds(self) -> dict[str, Any] | list[dict[str, Any]]: """Load all the default feeds.""" response = self._prepare_request('POST', 'feeds/loadDefaultFeeds') return self._check_json_response(response) @@ -1859,14 +1887,14 @@ class PyMISP: # ## BEGIN Server ### - def servers(self, pythonify: bool = False) -> Union[Dict, List[MISPServer]]: + def servers(self, pythonify: bool = False) -> dict[str, Any] | list[MISPServer] | list[dict[str, Any]]: """Get the existing servers the MISP instance can synchronise with: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'servers/index') servers = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in servers: + if not (self.global_pythonify or pythonify) or isinstance(servers, dict): return servers to_return = [] for server in servers: @@ -1875,7 +1903,7 @@ class PyMISP: to_return.append(s) return to_return - def get_sync_config(self, pythonify: bool = False) -> Union[Dict, MISPServer]: + def get_sync_config(self, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Get the sync server config. WARNING: This method only works if the user calling it is a sync user @@ -1889,7 +1917,7 @@ class PyMISP: s.from_dict(**server) return s - def import_server(self, server: MISPServer, pythonify: bool = False) -> Union[Dict, MISPServer]: + def import_server(self, server: MISPServer, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Import a sync server config received from get_sync_config :param server: sync server config @@ -1903,7 +1931,7 @@ class PyMISP: s.from_dict(**server_j) return s - def add_server(self, server: MISPServer, pythonify: bool = False) -> Union[Dict, MISPServer]: + def add_server(self, server: MISPServer, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Add a server to synchronise with: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers Note: You probably want to use PyMISP.get_sync_config and PyMISP.import_server instead @@ -1918,7 +1946,7 @@ class PyMISP: s.from_dict(**server_j) return s - def update_server(self, server: MISPServer, server_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPServer]: + def update_server(self, server: MISPServer, server_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Update a server to synchronise with: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers :param server: sync server config @@ -1936,7 +1964,7 @@ class PyMISP: s.from_dict(**updated_server) return s - def delete_server(self, server: Union[MISPServer, int, str, UUID]) -> Dict: + def delete_server(self, server: MISPServer | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a sync server: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers :param server: sync server config @@ -1945,7 +1973,7 @@ class PyMISP: response = self._prepare_request('POST', f'servers/delete/{server_id}') return self._check_json_response(response) - def server_pull(self, server: Union[MISPServer, int, str, UUID], event: Optional[Union[MISPEvent, int, str, UUID]] = None) -> Dict: + def server_pull(self, server: MISPServer | int | str | UUID, event: MISPEvent | int | str | UUID | None = None) -> dict[str, Any] | list[dict[str, Any]]: """Initialize a pull from a sync server, optionally limited to one event: https://www.misp-project.org/openapi/#tag/Servers/operation/pullServer :param server: sync server config @@ -1961,7 +1989,7 @@ class PyMISP: # FIXME: can we pythonify? return self._check_json_response(response) - def server_push(self, server: Union[MISPServer, int, str, UUID], event: Optional[Union[MISPEvent, int, str, UUID]] = None) -> Dict: + def server_push(self, server: MISPServer | int | str | UUID, event: MISPEvent | int | str | UUID | None = None) -> dict[str, Any] | list[dict[str, Any]]: """Initialize a push to a sync server, optionally limited to one event: https://www.misp-project.org/openapi/#tag/Servers/operation/pushServer :param server: sync server config @@ -1977,7 +2005,7 @@ class PyMISP: # FIXME: can we pythonify? return self._check_json_response(response) - def test_server(self, server: Union[MISPServer, int, str, UUID]) -> Dict: + def test_server(self, server: MISPServer | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Test if a sync link is working as expected :param server: sync server config @@ -1990,14 +2018,14 @@ class PyMISP: # ## BEGIN Sharing group ### - def sharing_groups(self, pythonify: bool = False) -> Union[Dict, List[MISPSharingGroup]]: + def sharing_groups(self, pythonify: bool = False) -> dict[str, Any] | list[MISPSharingGroup] | list[dict[str, Any]]: """Get the existing sharing groups: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/getSharingGroup :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'sharingGroups/index') sharing_groups = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in sharing_groups: + if not (self.global_pythonify or pythonify) or isinstance(sharing_groups, dict): return sharing_groups to_return = [] for sharing_group in sharing_groups: @@ -2006,7 +2034,7 @@ class PyMISP: to_return.append(s) return to_return - def get_sharing_group(self, sharing_group: Union[MISPSharingGroup, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPSharingGroup]: + def get_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPSharingGroup: """Get a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/getSharingGroupById :param sharing_group: sharing group to find @@ -2021,7 +2049,7 @@ class PyMISP: s.from_dict(**sharing_group_resp) return s - def add_sharing_group(self, sharing_group: MISPSharingGroup, pythonify: bool = False) -> Union[Dict, MISPSharingGroup]: + def add_sharing_group(self, sharing_group: MISPSharingGroup, pythonify: bool = False) -> dict[str, Any] | MISPSharingGroup: """Add a new sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/addSharingGroup :param sharing_group: sharing group to add @@ -2035,7 +2063,7 @@ class PyMISP: s.from_dict(**sharing_group_j) return s - def update_sharing_group(self, sharing_group: Union[MISPSharingGroup, dict], sharing_group_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPSharingGroup]: + def update_sharing_group(self, sharing_group: MISPSharingGroup | dict[str, Any], sharing_group_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPSharingGroup: """Update sharing group parameters: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/editSharingGroup :param sharing_group: MISP Sharing Group @@ -2055,7 +2083,7 @@ class PyMISP: s.from_dict(**updated_sharing_group) return s - def sharing_group_exists(self, sharing_group: Union[MISPSharingGroup, int, str, UUID]) -> bool: + def sharing_group_exists(self, sharing_group: MISPSharingGroup | int | str | UUID) -> bool: """Fast check if sharing group exists. :param sharing_group: Sharing group to check @@ -2064,7 +2092,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'sharing_groups/view/{sharing_group_id}') return self._check_head_response(r) - def delete_sharing_group(self, sharing_group: Union[MISPSharingGroup, int, str, UUID]) -> Dict: + def delete_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/deleteSharingGroup :param sharing_group: sharing group to delete @@ -2073,8 +2101,8 @@ class PyMISP: response = self._prepare_request('POST', f'sharingGroups/delete/{sharing_group_id}') return self._check_json_response(response) - def add_org_to_sharing_group(self, sharing_group: Union[MISPSharingGroup, int, str, UUID], - organisation: Union[MISPOrganisation, int, str, UUID], extend: bool = False) -> Dict: + def add_org_to_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, + organisation: MISPOrganisation | int | str | UUID, extend: bool = False) -> dict[str, Any] | list[dict[str, Any]]: '''Add an organisation to a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/addOrganisationToSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2087,8 +2115,8 @@ class PyMISP: response = self._prepare_request('POST', 'sharingGroups/addOrg', data=to_jsonify) return self._check_json_response(response) - def remove_org_from_sharing_group(self, sharing_group: Union[MISPSharingGroup, int, str, UUID], - organisation: Union[MISPOrganisation, int, str, UUID]) -> Dict: + def remove_org_from_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, + organisation: MISPOrganisation | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: '''Remove an organisation from a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/removeOrganisationFromSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2100,8 +2128,8 @@ class PyMISP: response = self._prepare_request('POST', 'sharingGroups/removeOrg', data=to_jsonify) return self._check_json_response(response) - def add_server_to_sharing_group(self, sharing_group: Union[MISPSharingGroup, int, str, UUID], - server: Union[MISPServer, int, str, UUID], all_orgs: bool = False) -> Dict: + def add_server_to_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, + server: MISPServer | int | str | UUID, all_orgs: bool = False) -> dict[str, Any] | list[dict[str, Any]]: '''Add a server to a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/addServerToSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2114,8 +2142,8 @@ class PyMISP: response = self._prepare_request('POST', 'sharingGroups/addServer', data=to_jsonify) return self._check_json_response(response) - def remove_server_from_sharing_group(self, sharing_group: Union[MISPSharingGroup, int, str, UUID], - server: Union[MISPServer, int, str, UUID]) -> Dict: + def remove_server_from_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, + server: MISPServer | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: '''Remove a server from a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/removeServerFromSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2131,7 +2159,7 @@ class PyMISP: # ## BEGIN Organisation ### - def organisations(self, scope="local", search: Optional[str] = None, pythonify: bool = False) -> Union[Dict, List[MISPOrganisation]]: + def organisations(self, scope: str="local", search: str | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPOrganisation] | list[dict[str, Any]]: """Get all the organisations: https://www.misp-project.org/openapi/#tag/Organisations/operation/getOrganisations :param scope: scope of organizations to get @@ -2144,7 +2172,7 @@ class PyMISP: r = self._prepare_request('GET', url_path) organisations = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in organisations: + if not (self.global_pythonify or pythonify) or isinstance(organisations, dict): return organisations to_return = [] for organisation in organisations: @@ -2153,7 +2181,7 @@ class PyMISP: to_return.append(o) return to_return - def get_organisation(self, organisation: Union[MISPOrganisation, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPOrganisation]: + def get_organisation(self, organisation: MISPOrganisation | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPOrganisation: """Get an organisation by id: https://www.misp-project.org/openapi/#tag/Organisations/operation/getOrganisationById :param organisation: organization to get @@ -2168,7 +2196,7 @@ class PyMISP: o.from_dict(**organisation_j) return o - def organisation_exists(self, organisation: Union[MISPOrganisation, int, str, UUID]) -> bool: + def organisation_exists(self, organisation: MISPOrganisation | int | str | UUID) -> bool: """Fast check if organisation exists. :param organisation: Organisation to check @@ -2177,7 +2205,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'organisations/view/{organisation_id}') return self._check_head_response(r) - def add_organisation(self, organisation: MISPOrganisation, pythonify: bool = False) -> Union[Dict, MISPOrganisation]: + def add_organisation(self, organisation: MISPOrganisation, pythonify: bool = False) -> dict[str, Any] | MISPOrganisation: """Add an organisation: https://www.misp-project.org/openapi/#tag/Organisations/operation/addOrganisation :param organisation: organization to add @@ -2191,7 +2219,7 @@ class PyMISP: o.from_dict(**new_organisation) return o - def update_organisation(self, organisation: MISPOrganisation, organisation_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPOrganisation]: + def update_organisation(self, organisation: MISPOrganisation, organisation_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPOrganisation: """Update an organisation: https://www.misp-project.org/openapi/#tag/Organisations/operation/editOrganisation :param organisation: organization to update @@ -2210,7 +2238,7 @@ class PyMISP: o.from_dict(**organisation) return o - def delete_organisation(self, organisation: Union[MISPOrganisation, int, str, UUID]) -> Dict: + def delete_organisation(self, organisation: MISPOrganisation | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete an organisation by id: https://www.misp-project.org/openapi/#tag/Organisations/operation/deleteOrganisation :param organisation: organization to delete @@ -2224,7 +2252,7 @@ class PyMISP: # ## BEGIN User ### - def users(self, search: Optional[str] = None, organisation: Optional[int] = None, pythonify: bool = False) -> Union[Dict, List[MISPUser]]: + def users(self, search: str | None = None, organisation: int | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPUser] | list[dict[str, Any]]: """Get all the users, or a filtered set of users: https://www.misp-project.org/openapi/#tag/Users/operation/getUsers :param search: The search to make against the list of users @@ -2239,7 +2267,7 @@ class PyMISP: urlpath += f"/searchorg:{organisation_id}" r = self._prepare_request('GET', urlpath) users = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in users: + if not (self.global_pythonify or pythonify) or isinstance(users, dict): return users to_return = [] for user in users: @@ -2248,12 +2276,12 @@ class PyMISP: to_return.append(u) return to_return - def get_user(self, user: Union[MISPUser, int, str, UUID] = 'me', pythonify: bool = False, expanded: bool = False) -> Union[Dict, MISPUser, Tuple[MISPUser, MISPRole, List[MISPUserSetting]]]: + def get_user(self, user: MISPUser | int | str | UUID = 'me', pythonify: bool = False, expanded: bool = False) -> dict[str, Any] | MISPUser | tuple[MISPUser, MISPRole, list[MISPUserSetting]]: """Get a user by id: https://www.misp-project.org/openapi/#tag/Users/operation/getUsers :param user: user to get; `me` means the owner of the API key doing the query :param pythonify: Returns a PyMISP Object instead of the plain json output - :param expanded: Also returns a MISPRole and a MISPUserSetting + :param expanded: Also returns a MISPRole and a MISPUserSetting. Only taken in account if pythonify is True. """ user_id = get_uuid_or_id_from_abstract_misp(user) r = self._prepare_request('GET', f'users/view/{user_id}') @@ -2275,7 +2303,7 @@ class PyMISP: usersettings.append(us) return u, role, usersettings - def get_new_authkey(self, user: Union[MISPUser, int, str, UUID] = 'me') -> str: + def get_new_authkey(self, user: MISPUser | int | str | UUID = 'me') -> str: '''Get a new authorization key for a specific user, defaults to user doing the call: https://www.misp-project.org/openapi/#tag/AuthKeys/operation/addAuthKey :param user: The owner of the key @@ -2288,7 +2316,7 @@ class PyMISP: else: raise PyMISPUnexpectedResponse(f'Unable to get authkey: {authkey}') - def add_user(self, user: MISPUser, pythonify: bool = False) -> Union[Dict, MISPUser]: + def add_user(self, user: MISPUser, pythonify: bool = False) -> dict[str, Any] | MISPUser: """Add a new user: https://www.misp-project.org/openapi/#tag/Users/operation/addUser :param user: user to add @@ -2302,7 +2330,7 @@ class PyMISP: u.from_dict(**user_j) return u - def update_user(self, user: MISPUser, user_id: Optional[int] = None, pythonify: bool = False) -> Union[Dict, MISPUser]: + def update_user(self, user: MISPUser, user_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPUser: """Update a user on a MISP instance: https://www.misp-project.org/openapi/#tag/Users/operation/editUser :param user: user to update @@ -2324,7 +2352,7 @@ class PyMISP: e.from_dict(**updated_user) return e - def delete_user(self, user: Union[MISPUser, int, str, UUID]) -> Dict: + def delete_user(self, user: MISPUser | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a user by id: https://www.misp-project.org/openapi/#tag/Users/operation/deleteUser :param user: user to delete @@ -2334,7 +2362,7 @@ class PyMISP: response = self._prepare_request('POST', f'admin/users/delete/{user_id}') return self._check_json_response(response) - def change_user_password(self, new_password: str) -> Dict: + def change_user_password(self, new_password: str) -> dict[str, Any] | list[dict[str, Any]]: """Change the password of the curent user: :param new_password: password to set @@ -2342,14 +2370,14 @@ class PyMISP: response = self._prepare_request('POST', 'users/change_pw', data={'password': new_password}) return self._check_json_response(response) - def user_registrations(self, pythonify: bool = False) -> Union[Dict, List[MISPInbox]]: + def user_registrations(self, pythonify: bool = False) -> dict[str, Any] | list[MISPInbox] | list[dict[str, Any]]: """Get all the user registrations :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'users/registrations/index') registrations = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in registrations: + if not (self.global_pythonify or pythonify) or isinstance(registrations, dict): return registrations to_return = [] for registration in registrations: @@ -2358,11 +2386,12 @@ class PyMISP: to_return.append(i) return to_return - def accept_user_registration(self, registration: Union[MISPInbox, int, str, UUID], - organisation: Optional[Union[MISPOrganisation, int, str, UUID]] = None, - role: Optional[Union[MISPRole, int, str]] = None, - perm_sync: bool = False, perm_publish: bool = False, perm_admin: bool = False, - unsafe_fallback: bool = False): + def accept_user_registration(self, registration: MISPInbox | int | str | UUID, + organisation: MISPOrganisation | int | str | UUID | None = None, + role: MISPRole | int | str | None = None, + perm_sync: bool = False, perm_publish: bool = False, + perm_admin: bool = False, + unsafe_fallback: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Accept a user registration :param registration: the registration to accept @@ -2377,11 +2406,11 @@ class PyMISP: if role: role_id = role_id = get_uuid_or_id_from_abstract_misp(role) else: - for role in self.roles(pythonify=True): - if not isinstance(role, MISPRole): + for _r in self.roles(pythonify=True): + if not isinstance(_r, MISPRole): continue - if role.default_role: # type: ignore - role_id = get_uuid_or_id_from_abstract_misp(role) + if _r.default_role: # type: ignore + role_id = get_uuid_or_id_from_abstract_misp(_r) break else: raise PyMISPError('Unable to find default role') @@ -2409,7 +2438,7 @@ class PyMISP: r = self._prepare_request('POST', f'users/acceptRegistrations/{registration_id}', data=to_post) return self._check_json_response(r) - def discard_user_registration(self, registration: Union[MISPInbox, int, str, UUID]): + def discard_user_registration(self, registration: MISPInbox | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Discard a user registration :param registration: the registration to discard @@ -2422,14 +2451,14 @@ class PyMISP: # ## BEGIN Role ### - def roles(self, pythonify: bool = False) -> Union[Dict, List[MISPRole]]: + def roles(self, pythonify: bool = False) -> dict[str, Any] | list[MISPRole] | list[dict[str, Any]]: """Get the existing roles :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'roles/index') roles = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in roles: + if not (self.global_pythonify or pythonify) or isinstance(roles, dict): return roles to_return = [] for role in roles: @@ -2438,7 +2467,7 @@ class PyMISP: to_return.append(nr) return to_return - def set_default_role(self, role: Union[MISPRole, int, str, UUID]) -> Dict: + def set_default_role(self, role: MISPRole | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Set a default role for the new user accounts :param role: the default role to set @@ -2452,19 +2481,19 @@ class PyMISP: # ## BEGIN Decaying Models ### - def update_decaying_models(self) -> Dict: + def update_decaying_models(self) -> dict[str, Any] | list[dict[str, Any]]: """Update all the Decaying models""" response = self._prepare_request('POST', 'decayingModel/update') return self._check_json_response(response) - def decaying_models(self, pythonify: bool = False) -> Union[Dict, List[MISPDecayingModel]]: + def decaying_models(self, pythonify: bool = False) -> dict[str, Any] | list[MISPDecayingModel] | list[dict[str, Any]]: """Get all the decaying models :param pythonify: Returns a list of PyMISP Objects instead of the plain json output """ r = self._prepare_request('GET', 'decayingModel/index') models = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in models: + if not (self.global_pythonify or pythonify) or isinstance(models, dict): return models to_return = [] for model in models: @@ -2473,7 +2502,7 @@ class PyMISP: to_return.append(n) return to_return - def enable_decaying_model(self, decaying_model: Union[MISPDecayingModel, int, str]) -> Dict: + def enable_decaying_model(self, decaying_model: MISPDecayingModel | int | str) -> dict[str, Any] | list[dict[str, Any]]: """Enable a decaying Model""" if isinstance(decaying_model, MISPDecayingModel): decaying_model_id = decaying_model.id @@ -2482,7 +2511,7 @@ class PyMISP: response = self._prepare_request('POST', f'decayingModel/enable/{decaying_model_id}') return self._check_json_response(response) - def disable_decaying_model(self, decaying_model: Union[MISPDecayingModel, int, str]) -> Dict: + def disable_decaying_model(self, decaying_model: MISPDecayingModel | int | str) -> dict[str, Any] | list[dict[str, Any]]: """Disable a decaying Model""" if isinstance(decaying_model, MISPDecayingModel): decaying_model_id = decaying_model.id @@ -2495,54 +2524,54 @@ class PyMISP: # ## BEGIN Search methods ### - def search(self, controller: str = 'events', return_format: str = 'json', - limit: Optional[int] = None, page: Optional[int] = None, - value: Optional[SearchParameterTypes] = None, - type_attribute: Optional[SearchParameterTypes] = None, - category: Optional[SearchParameterTypes] = None, - org: Optional[SearchParameterTypes] = None, - tags: Optional[SearchParameterTypes] = None, - event_tags: Optional[SearchParameterTypes] = None, - quick_filter: Optional[str] = None, quickFilter: Optional[str] = None, - date_from: Optional[Union[datetime, date, int, str, float, None]] = None, - date_to: Optional[Union[datetime, date, int, str, float, None]] = None, - eventid: Optional[SearchType] = None, - with_attachments: Optional[bool] = None, withAttachments: Optional[bool] = None, - metadata: Optional[bool] = None, - uuid: Optional[str] = None, - publish_timestamp: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - last: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - timestamp: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - published: Optional[bool] = None, - enforce_warninglist: Optional[bool] = None, enforceWarninglist: Optional[bool] = None, - to_ids: Optional[Union[ToIDSType, List[ToIDSType]]] = None, - deleted: Optional[str] = None, - include_event_uuid: Optional[bool] = None, includeEventUuid: Optional[bool] = None, - include_event_tags: Optional[bool] = None, includeEventTags: Optional[bool] = None, - event_timestamp: Optional[Union[datetime, date, int, str, float, None]] = None, - sg_reference_only: Optional[bool] = None, - eventinfo: Optional[str] = None, - searchall: Optional[bool] = None, - requested_attributes: Optional[str] = None, - include_context: Optional[bool] = None, includeContext: Optional[bool] = None, - headerless: Optional[bool] = None, - include_sightings: Optional[bool] = None, includeSightings: Optional[bool] = None, - include_correlations: Optional[bool] = None, includeCorrelations: Optional[bool] = None, - include_decay_score: Optional[bool] = None, includeDecayScore: Optional[bool] = None, - object_name: Optional[str] = None, - exclude_decayed: Optional[bool] = None, - sharinggroup: Optional[Union[int, List[int]]] = None, - pythonify: Optional[bool] = False, - **kwargs) -> Union[Dict, str, List[Union[MISPEvent, MISPAttribute, MISPObject]]]: + def search(self, controller: str = 'events', return_format: str = 'json', # type: ignore[no-untyped-def] + limit: int | None = None, page: int | None = None, + value: SearchParameterTypes | None = None, + type_attribute: SearchParameterTypes | None = None, + category: SearchParameterTypes | None = None, + org: SearchParameterTypes | None = None, + tags: SearchParameterTypes | None = None, + event_tags: SearchParameterTypes | None = None, + quick_filter: str | None = None, quickFilter: str | None = None, + date_from: datetime | date | int | str | float | None | None = None, + date_to: datetime | date | int | str | float | None | None = None, + eventid: SearchType | None = None, + with_attachments: bool | None = None, withAttachments: bool | None = None, + metadata: bool | None = None, + uuid: str | None = None, + publish_timestamp: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + last: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + timestamp: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + published: bool | None = None, + enforce_warninglist: bool | None = None, enforceWarninglist: bool | None = None, + to_ids: ToIDSType | list[ToIDSType] | None = None, + deleted: str | None = None, + include_event_uuid: bool | None = None, includeEventUuid: bool | None = None, + include_event_tags: bool | None = None, includeEventTags: bool | None = None, + event_timestamp: datetime | date | int | str | float | None | None = None, + sg_reference_only: bool | None = None, + eventinfo: str | None = None, + searchall: bool | None = None, + requested_attributes: str | None = None, + include_context: bool | None = None, includeContext: bool | None = None, + headerless: bool | None = None, + include_sightings: bool | None = None, includeSightings: bool | None = None, + include_correlations: bool | None = None, includeCorrelations: bool | None = None, + include_decay_score: bool | None = None, includeDecayScore: bool | None = None, + object_name: str | None = None, + exclude_decayed: bool | None = None, + sharinggroup: int | list[int] | None = None, + pythonify: bool | None = False, + **kwargs) -> dict[str, Any] | str | list[MISPEvent | MISPAttribute | MISPObject] | list[dict[str, Any]]: '''Search in the MISP instance :param controller: Controller to search on, it can be `events`, `objects`, `attributes`. The response will either be a list of events, objects, or attributes. @@ -2603,10 +2632,10 @@ class PyMISP: ''' - return_formats = ['openioc', 'json', 'xml', 'suricata', 'snort', 'text', 'rpz', 'csv', 'cache', 'stix-xml', - 'stix', 'stix2', 'yara', 'yara-json', 'attack', 'attack-sightings', 'context', 'context-markdown'] + return_formats = ('openioc', 'json', 'xml', 'suricata', 'snort', 'text', 'rpz', 'csv', 'cache', 'stix-xml', + 'stix', 'stix2', 'yara', 'yara-json', 'attack', 'attack-sightings', 'context', 'context-markdown') - if controller not in ['events', 'attributes', 'objects']: + if controller not in ('events', 'attributes', 'objects'): raise ValueError('controller has to be in {}'.format(', '.join(['events', 'attributes', 'objects']))) # Deprecated stuff / synonyms @@ -2708,22 +2737,29 @@ class PyMISP: elif return_format not in ['json', 'yara-json']: return self._check_response(response) - normalized_response = self._check_json_response(response) + normalized_response: list[dict[str, Any]] | dict[str, Any] + if controller in ['events', 'objects']: + # This one is truly fucked: event returns a list, attributes doesn't. + normalized_response = self._check_json_response(response) + elif controller == 'attributes': + normalized_response = self._check_json_response(response) if 'errors' in normalized_response: return normalized_response if return_format == 'json' and self.global_pythonify or pythonify: # The response is in json, we can convert it to a list of pythonic MISP objects - to_return: List[Union[MISPEvent, MISPAttribute, MISPObject]] = [] + to_return: list[MISPEvent | MISPAttribute | MISPObject] = [] if controller == 'events': + if isinstance(normalized_response, dict): + return normalized_response for e in normalized_response: me = MISPEvent() me.load(e) to_return.append(me) elif controller == 'attributes': # FIXME: obvs, this is hurting my soul. We need something generic. - for a in normalized_response['Attribute']: + for a in normalized_response['Attribute']: # type: ignore[call-overload] ma = MISPAttribute() ma.from_dict(**a) if 'Event' in ma: @@ -2750,6 +2786,8 @@ class PyMISP: ma.Sighting = sightings to_return.append(ma) elif controller == 'objects': + if isinstance(normalized_response, dict): + return normalized_response for o in normalized_response: mo = MISPObject(o['Object']['name']) mo.from_dict(**o) @@ -2759,35 +2797,35 @@ class PyMISP: return normalized_response def search_index(self, - all: Optional[str] = None, - attribute: Optional[str] = None, - email: Optional[str] = None, - published: Optional[bool] = None, - hasproposal: Optional[bool] = None, - eventid: Optional[SearchType] = None, - tags: Optional[SearchParameterTypes] = None, - date_from: Optional[Union[datetime, date, int, str, float, None]] = None, - date_to: Optional[Union[datetime, date, int, str, float, None]] = None, - eventinfo: Optional[str] = None, - threatlevel: Optional[List[SearchType]] = None, - distribution: Optional[List[SearchType]] = None, - analysis: Optional[List[SearchType]] = None, - org: Optional[SearchParameterTypes] = None, - timestamp: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - publish_timestamp: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - sharinggroup: Optional[List[SearchType]] = None, - minimal: Optional[bool] = None, - sort: Optional[str] = None, - desc: Optional[bool] = None, - limit: Optional[int] = None, - page: Optional[int] = None, - pythonify: Optional[bool] = None) -> Union[Dict, List[MISPEvent]]: + all: str | None = None, + attribute: str | None = None, + email: str | None = None, + published: bool | None = None, + hasproposal: bool | None = None, + eventid: SearchType | None = None, + tags: SearchParameterTypes | None = None, + date_from: datetime | date | int | str | float | None | None = None, + date_to: datetime | date | int | str | float | None | None = None, + eventinfo: str | None = None, + threatlevel: list[SearchType] | None = None, + distribution: list[SearchType] | None = None, + analysis: list[SearchType] | None = None, + org: SearchParameterTypes | None = None, + timestamp: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + publish_timestamp: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + sharinggroup: list[SearchType] | None = None, + minimal: bool | None = None, + sort: str | None = None, + desc: bool | None = None, + limit: int | None = None, + page: int | None = None, + pythonify: bool | None = None) -> dict[str, Any] | list[MISPEvent] | list[dict[str, Any]]: """Search event metadata shown on the event index page. Using ! in front of a value means NOT, except for parameters date_from, date_to and timestamp which cannot be negated. Criteria are AND-ed together; values in lists are OR-ed together. Return matching events @@ -2846,7 +2884,7 @@ class PyMISP: response = self._prepare_request('POST', url, data=query) normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify): + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return = [] for e_meta in normalized_response: @@ -2855,25 +2893,25 @@ class PyMISP: to_return.append(me) return to_return - def search_sightings(self, context: Optional[str] = None, - context_id: Optional[SearchType] = None, - type_sighting: Optional[str] = None, - date_from: Optional[Union[datetime, date, int, str, float, None]] = None, - date_to: Optional[Union[datetime, date, int, str, float, None]] = None, - publish_timestamp: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - last: Optional[Union[Union[datetime, date, int, str, float, None], - Tuple[Union[datetime, date, int, str, float, None], - Union[datetime, date, int, str, float, None]] - ]] = None, - org: Optional[SearchType] = None, - source: Optional[str] = None, - include_attribute: Optional[bool] = None, - include_event_meta: Optional[bool] = None, - pythonify: Optional[bool] = False - ) -> Union[Dict, List[Dict[str, Union[MISPEvent, MISPAttribute, MISPSighting]]]]: + def search_sightings(self, context: str | None = None, + context_id: SearchType | None = None, + type_sighting: str | None = None, + date_from: datetime | date | int | str | float | None | None = None, + date_to: datetime | date | int | str | float | None | None = None, + publish_timestamp: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + last: None | (datetime | date | int | str | float | None + | tuple[datetime | date | int | str | float | None, + datetime | date | int | str | float | None] + ) = None, + org: SearchType | None = None, + source: str | None = None, + include_attribute: bool | None = None, + include_event_meta: bool | None = None, + pythonify: bool | None = False + ) -> dict[str, Any] | list[dict[str, MISPEvent | MISPAttribute | MISPSighting]]: '''Search sightings :param context: The context of the search. Can be either "attribute", "event", or nothing (will then match on events and attributes). @@ -2899,7 +2937,7 @@ class PyMISP: [ ... ] >>> misp.search_sightings(context='event', context_id=17, include_event_meta=True, org=2) # return list of sighting for event 17 filtered with org id 2 ''' - query: Dict[str, Any] = {'returnFormat': 'json'} + query: dict[str, Any] = {'returnFormat': 'json'} if context is not None: if context not in ['attribute', 'event']: raise ValueError('context has to be in {}'.format(', '.join(['attribute', 'event']))) @@ -2921,13 +2959,13 @@ class PyMISP: url = urljoin(self.root_url, url_path) response = self._prepare_request('POST', url, data=query) normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response if self.global_pythonify or pythonify: to_return = [] for s in normalized_response: - entries: Dict[str, Union[MISPEvent, MISPAttribute, MISPSighting]] = {} + entries: dict[str, MISPEvent | MISPAttribute | MISPSighting] = {} s_data = s['Sighting'] if include_event_meta: e = s_data.pop('Event') @@ -2946,13 +2984,13 @@ class PyMISP: return to_return return normalized_response - def search_logs(self, limit: Optional[int] = None, page: Optional[int] = None, - log_id: Optional[int] = None, title: Optional[str] = None, - created: Optional[Union[datetime, date, int, str, float, None]] = None, model: Optional[str] = None, - action: Optional[str] = None, user_id: Optional[int] = None, - change: Optional[str] = None, email: Optional[str] = None, - org: Optional[str] = None, description: Optional[str] = None, - ip: Optional[str] = None, pythonify: Optional[bool] = False) -> Union[Dict, List[MISPLog]]: + def search_logs(self, limit: int | None = None, page: int | None = None, + log_id: int | None = None, title: str | None = None, + created: datetime | date | int | str | float | None | None = None, model: str | None = None, + action: str | None = None, user_id: int | None = None, + change: str | None = None, email: str | None = None, + org: str | None = None, description: str | None = None, + ip: str | None = None, pythonify: bool | None = False) -> dict[str, Any] | list[MISPLog] | list[dict[str, Any]]: '''Search in logs Note: to run substring queries simply append/prepend/encapsulate the search term with % @@ -2977,12 +3015,12 @@ class PyMISP: query.pop('pythonify') if log_id is not None: query['id'] = query.pop('log_id') - if created is not None and isinstance(created, (datetime)): + if created is not None and isinstance(created, datetime): query['created'] = query.pop('created').timestamp() response = self._prepare_request('POST', 'admin/logs/index', data=query) normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return = [] @@ -2992,11 +3030,11 @@ class PyMISP: to_return.append(ml) return to_return - def search_feeds(self, value: Optional[SearchParameterTypes] = None, pythonify: Optional[bool] = False) -> Union[Dict, List[MISPFeed]]: + def search_feeds(self, value: SearchParameterTypes | None = None, pythonify: bool | None = False) -> dict[str, Any] | list[MISPFeed] | list[dict[str, Any]]: '''Search in the feeds cached on the servers''' response = self._prepare_request('POST', 'feeds/searchCaches', data={'value': value}) normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return = [] for feed in normalized_response: @@ -3009,14 +3047,14 @@ class PyMISP: # ## BEGIN Communities ### - def communities(self, pythonify: bool = False) -> Union[Dict, List[MISPCommunity]]: + def communities(self, pythonify: bool = False) -> dict[str, Any] | list[MISPCommunity] | list[dict[str, Any]]: """Get all the communities :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'communities/index') communities = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in communities: + if not (self.global_pythonify or pythonify) or isinstance(communities, dict): return communities to_return = [] for community in communities: @@ -3025,7 +3063,7 @@ class PyMISP: to_return.append(c) return to_return - def get_community(self, community: Union[MISPCommunity, int, str, UUID], pythonify: bool = False) -> Union[Dict, MISPCommunity]: + def get_community(self, community: MISPCommunity | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPCommunity: """Get a community by id from a MISP instance :param community: community to get @@ -3040,15 +3078,15 @@ class PyMISP: c.from_dict(**community_j) return c - def request_community_access(self, community: Union[MISPCommunity, int, str, UUID], - requestor_email_address: Optional[str] = None, - requestor_gpg_key: Optional[str] = None, - requestor_organisation_name: Optional[str] = None, - requestor_organisation_uuid: Optional[str] = None, - requestor_organisation_description: Optional[str] = None, - message: Optional[str] = None, sync: bool = False, + def request_community_access(self, community: MISPCommunity | int | str | UUID, + requestor_email_address: str | None = None, + requestor_gpg_key: str | None = None, + requestor_organisation_name: str | None = None, + requestor_organisation_uuid: str | None = None, + requestor_organisation_description: str | None = None, + message: str | None = None, sync: bool = False, anonymise_requestor_server: bool = False, - mock: bool = False) -> Dict: + mock: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Request the access to a community :param community: community to request access @@ -3076,14 +3114,14 @@ class PyMISP: # ## BEGIN Event Delegation ### - def event_delegations(self, pythonify: bool = False) -> Union[Dict, List[MISPEventDelegation]]: + def event_delegations(self, pythonify: bool = False) -> dict[str, Any] | list[MISPEventDelegation] | list[dict[str, Any]]: """Get all the event delegations :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'eventDelegations') delegations = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in delegations: + if not (self.global_pythonify or pythonify) or isinstance(delegations, dict): return delegations to_return = [] for delegation in delegations: @@ -3092,7 +3130,7 @@ class PyMISP: to_return.append(d) return to_return - def accept_event_delegation(self, delegation: Union[MISPEventDelegation, int, str], pythonify: bool = False) -> Dict: + def accept_event_delegation(self, delegation: MISPEventDelegation | int | str, pythonify: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Accept the delegation of an event :param delegation: event delegation to accept @@ -3102,7 +3140,7 @@ class PyMISP: r = self._prepare_request('POST', f'eventDelegations/acceptDelegation/{delegation_id}') return self._check_json_response(r) - def discard_event_delegation(self, delegation: Union[MISPEventDelegation, int, str], pythonify: bool = False) -> Dict: + def discard_event_delegation(self, delegation: MISPEventDelegation | int | str, pythonify: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Discard the delegation of an event :param delegation: event delegation to discard @@ -3112,10 +3150,10 @@ class PyMISP: r = self._prepare_request('POST', f'eventDelegations/deleteDelegation/{delegation_id}') return self._check_json_response(r) - def delegate_event(self, event: Optional[Union[MISPEvent, int, str, UUID]] = None, - organisation: Optional[Union[MISPOrganisation, int, str, UUID]] = None, - event_delegation: Optional[MISPEventDelegation] = None, - distribution: int = -1, message: str = '', pythonify: bool = False) -> Union[Dict, MISPEventDelegation]: + def delegate_event(self, event: MISPEvent | int | str | UUID | None = None, + organisation: MISPOrganisation | int | str | UUID | None = None, + event_delegation: MISPEventDelegation | None = None, + distribution: int = -1, message: str = '', pythonify: bool = False) -> dict[str, Any] | MISPEventDelegation: """Delegate an event. Either event and organisation OR event_delegation are required :param event: event to delegate @@ -3145,7 +3183,7 @@ class PyMISP: # ## BEGIN Others ### - def push_event_to_ZMQ(self, event: Union[MISPEvent, int, str, UUID]) -> Dict: + def push_event_to_ZMQ(self, event: MISPEvent | int | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Force push an event by id on ZMQ :param event: the event to push @@ -3154,7 +3192,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/pushEventToZMQ/{event_id}.json') return self._check_json_response(response) - def direct_call(self, url: str, data: Optional[Dict] = None, params: Mapping = {}, kw_params: Mapping = {}) -> Any: + def direct_call(self, url: str, data: dict[str, Any] | None = None, params: Mapping[str, Any] = {}, kw_params: Mapping[str, Any] = {}) -> Any: """Very lightweight call that posts a data blob (python dictionary or json string) on the URL :param url: URL to post to @@ -3168,8 +3206,8 @@ class PyMISP: response = self._prepare_request('POST', url, data=data, params=params, kw_params=kw_params) return self._check_response(response, lenient_response_type=True) - def freetext(self, event: Union[MISPEvent, int, str, UUID], string: str, adhereToWarninglists: Union[bool, str] = False, - distribution: Optional[int] = None, returnMetaAttributes: bool = False, pythonify: bool = False, **kwargs) -> Union[Dict, List[MISPAttribute]]: + def freetext(self, event: MISPEvent | int | str | UUID, string: str, adhereToWarninglists: bool | str = False, # type: ignore[no-untyped-def] + distribution: int | None = None, returnMetaAttributes: bool = False, pythonify: bool = False, **kwargs) -> dict[str, Any] | list[MISPAttribute] | list[dict[str, Any]]: """Pass a text to the freetext importer :param event: event @@ -3182,7 +3220,7 @@ class PyMISP: """ event_id = get_uuid_or_id_from_abstract_misp(event) - query: Dict[str, Any] = {"value": string} + query: dict[str, Any] = {"value": string} wl_params = [False, True, 'soft'] if adhereToWarninglists in wl_params: query['adhereToWarninglists'] = adhereToWarninglists @@ -3194,7 +3232,7 @@ class PyMISP: query['returnMetaAttributes'] = returnMetaAttributes r = self._prepare_request('POST', f'events/freeTextImport/{event_id}', data=query, **kwargs) attributes = self._check_json_response(r) - if returnMetaAttributes or not (self.global_pythonify or pythonify) or 'errors' in attributes: + if returnMetaAttributes or not (self.global_pythonify or pythonify) or isinstance(attributes, dict): return attributes to_return = [] for attribute in attributes: @@ -3203,14 +3241,15 @@ class PyMISP: to_return.append(a) return to_return - def upload_stix(self, path: Optional[Union[str, Path, BytesIO, StringIO]] = None, data: Optional[Union[str, bytes]] = None, version: str = '2'): + def upload_stix(self, path: str | Path | BytesIO | StringIO | None = None, + data: str | bytes | None = None, version: str = '2') -> requests.Response: """Upload a STIX file to MISP. :param path: Path to the STIX on the disk (can be a path-like object, or a pseudofile) :param data: stix object :param version: Can be 1 or 2 """ - to_post: Union[str, bytes] + to_post: str | bytes if path is not None: if isinstance(path, (str, Path)): with open(path, 'rb') as f: @@ -3224,17 +3263,17 @@ class PyMISP: if str(version) == '1': url = urljoin(self.root_url, 'events/upload_stix') - response = self._prepare_request('POST', url, data=to_post, output_type='xml', content_type='xml') # type: ignore + response = self._prepare_request('POST', url, data=to_post, output_type='xml', content_type='xml') else: url = urljoin(self.root_url, 'events/upload_stix/2') - response = self._prepare_request('POST', url, data=to_post) # type: ignore + response = self._prepare_request('POST', url, data=to_post) return response # ## END Others ### # ## BEGIN Statistics ### - def attributes_statistics(self, context: str = 'type', percentage: bool = False) -> Dict: + def attributes_statistics(self, context: str = 'type', percentage: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Get attribute statistics from the MISP instance :param context: "type" or "category" @@ -3250,7 +3289,7 @@ class PyMISP: response = self._prepare_request('GET', path) return self._check_json_response(response) - def tags_statistics(self, percentage: bool = False, name_sort: bool = False) -> Dict: + def tags_statistics(self, percentage: bool = False, name_sort: bool = False) -> dict[str, Any] | list[dict[str, Any]]: """Get tag statistics from the MISP instance :param percentage: get percentages @@ -3269,7 +3308,7 @@ class PyMISP: response = self._prepare_request('GET', f'tags/tagStatistics/{p}/{ns}') return self._check_json_response(response) - def users_statistics(self, context: str = 'data') -> Dict: + def users_statistics(self, context: str = 'data') -> dict[str, Any] | list[dict[str, Any]]: """Get user statistics from the MISP instance :param context: one of 'data', 'orgs', 'users', 'tags', 'attributehistogram', 'sightings', 'galaxyMatrix' @@ -3278,20 +3317,23 @@ class PyMISP: if context not in availables_contexts: raise PyMISPError("context can only be {','.join(availables_contexts)}") response = self._prepare_request('GET', f'users/statistics/{context}') - return self._check_json_response(response) + try: + return self._check_json_response(response) + except PyMISPError: + return self._check_json_response(response) # ## END Statistics ### # ## BEGIN User Settings ### - def user_settings(self, pythonify: bool = False) -> Union[Dict, List[MISPUserSetting]]: + def user_settings(self, pythonify: bool = False) -> dict[str, Any] | list[MISPUserSetting] | list[dict[str, Any]]: """Get all the user settings: https://www.misp-project.org/openapi/#tag/UserSettings/operation/getUserSettings :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'userSettings/index') user_settings = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in user_settings: + if not (self.global_pythonify or pythonify) or isinstance(user_settings, dict): return user_settings to_return = [] for user_setting in user_settings: @@ -3300,15 +3342,15 @@ class PyMISP: to_return.append(u) return to_return - def get_user_setting(self, user_setting: str, user: Optional[Union[MISPUser, int, str, UUID]] = None, - pythonify: bool = False) -> Union[Dict, MISPUserSetting]: + def get_user_setting(self, user_setting: str, user: MISPUser | int | str | UUID | None = None, + pythonify: bool = False) -> dict[str, Any] | MISPUserSetting: """Get a user setting: https://www.misp-project.org/openapi/#tag/UserSettings/operation/getUserSettingById :param user_setting: name of user setting :param user: user :param pythonify: Returns a PyMISP Object instead of the plain json output """ - query: Dict[str, Any] = {'setting': user_setting} + query: dict[str, Any] = {'setting': user_setting} if user: query['user_id'] = get_uuid_or_id_from_abstract_misp(user) response = self._prepare_request('POST', 'userSettings/getSetting', data=query) @@ -3319,8 +3361,8 @@ class PyMISP: u.from_dict(**user_setting_j) return u - def set_user_setting(self, user_setting: str, value: Union[str, dict], user: Optional[Union[MISPUser, int, str, UUID]] = None, - pythonify: bool = False) -> Union[Dict, MISPUserSetting]: + def set_user_setting(self, user_setting: str, value: str | dict[str, Any], user: MISPUser | int | str | UUID | None = None, + pythonify: bool = False) -> dict[str, Any] | MISPUserSetting: """Set a user setting: https://www.misp-project.org/openapi/#tag/UserSettings/operation/setUserSetting :param user_setting: name of user setting @@ -3328,9 +3370,9 @@ class PyMISP: :param user: user :param pythonify: Returns a PyMISP Object instead of the plain json output """ - query: Dict[str, Any] = {'setting': user_setting} + query: dict[str, Any] = {'setting': user_setting} if isinstance(value, dict): - value = json.dumps(value) + value = str(dumps(value)) if HAS_ORJSON else dumps(value) query['value'] = value if user: query['user_id'] = get_uuid_or_id_from_abstract_misp(user) @@ -3342,13 +3384,13 @@ class PyMISP: u.from_dict(**user_setting_j) return u - def delete_user_setting(self, user_setting: str, user: Optional[Union[MISPUser, int, str, UUID]] = None) -> Dict: + def delete_user_setting(self, user_setting: str, user: MISPUser | int | str | UUID | None = None) -> dict[str, Any] | list[dict[str, Any]]: """Delete a user setting: https://www.misp-project.org/openapi/#tag/UserSettings/operation/deleteUserSettingById :param user_setting: name of user setting :param user: user """ - query: Dict[str, Any] = {'setting': user_setting} + query: dict[str, Any] = {'setting': user_setting} if user: query['user_id'] = get_uuid_or_id_from_abstract_misp(user) response = self._prepare_request('POST', 'userSettings/delete', data=query) @@ -3358,14 +3400,14 @@ class PyMISP: # ## BEGIN Blocklists ### - def event_blocklists(self, pythonify: bool = False) -> Union[Dict, List[MISPEventBlocklist]]: + def event_blocklists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPEventBlocklist] | list[dict[str, Any]]: """Get all the blocklisted events :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'eventBlocklists/index') event_blocklists = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in event_blocklists: + if not (self.global_pythonify or pythonify) or isinstance(event_blocklists, dict): return event_blocklists to_return = [] for event_blocklist in event_blocklists: @@ -3374,14 +3416,14 @@ class PyMISP: to_return.append(ebl) return to_return - def organisation_blocklists(self, pythonify: bool = False) -> Union[Dict, List[MISPOrganisationBlocklist]]: + def organisation_blocklists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPOrganisationBlocklist] | list[dict[str, Any]]: """Get all the blocklisted organisations :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'orgBlocklists/index') organisation_blocklists = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in organisation_blocklists: + if not (self.global_pythonify or pythonify) or isinstance(organisation_blocklists, dict): return organisation_blocklists to_return = [] for organisation_blocklist in organisation_blocklists: @@ -3390,7 +3432,7 @@ class PyMISP: to_return.append(obl) return to_return - def _add_entries_to_blocklist(self, blocklist_type: str, uuids: Union[str, List[str]], **kwargs) -> Dict: + def _add_entries_to_blocklist(self, blocklist_type: str, uuids: str | list[str], **kwargs) -> dict[str, Any] | list[dict[str, Any]]: # type: ignore[no-untyped-def] if blocklist_type == 'event': url = 'eventBlocklists/add' elif blocklist_type == 'organisation': @@ -3405,8 +3447,8 @@ class PyMISP: r = self._prepare_request('POST', url, data=data) return self._check_json_response(r) - def add_event_blocklist(self, uuids: Union[str, List[str]], comment: Optional[str] = None, - event_info: Optional[str] = None, event_orgc: Optional[str] = None) -> Dict: + def add_event_blocklist(self, uuids: str | list[str], comment: str | None = None, + event_info: str | None = None, event_orgc: str | None = None) -> dict[str, Any] | list[dict[str, Any]]: """Add a new event in the blocklist :param uuids: UUIDs @@ -3416,8 +3458,8 @@ class PyMISP: """ return self._add_entries_to_blocklist('event', uuids=uuids, comment=comment, event_info=event_info, event_orgc=event_orgc) - def add_organisation_blocklist(self, uuids: Union[str, List[str]], comment: Optional[str] = None, - org_name: Optional[str] = None) -> Dict: + def add_organisation_blocklist(self, uuids: str | list[str], comment: str | None = None, + org_name: str | None = None) -> dict[str, Any] | list[dict[str, Any]]: """Add a new organisation in the blocklist :param uuids: UUIDs @@ -3426,7 +3468,7 @@ class PyMISP: """ return self._add_entries_to_blocklist('organisation', uuids=uuids, comment=comment, org_name=org_name) - def _update_entries_in_blocklist(self, blocklist_type: str, uuid, **kwargs) -> Dict: + def _update_entries_in_blocklist(self, blocklist_type: str, uuid, **kwargs) -> dict[str, Any] | list[dict[str, Any]]: # type: ignore[no-untyped-def] if blocklist_type == 'event': url = f'eventBlocklists/edit/{uuid}' elif blocklist_type == 'organisation': @@ -3437,7 +3479,7 @@ class PyMISP: r = self._prepare_request('POST', url, data=data) return self._check_json_response(r) - def update_event_blocklist(self, event_blocklist: MISPEventBlocklist, event_blocklist_id: Optional[Union[int, str, UUID]] = None, pythonify: bool = False) -> Union[Dict, MISPEventBlocklist]: + def update_event_blocklist(self, event_blocklist: MISPEventBlocklist, event_blocklist_id: int | str | UUID | None = None, pythonify: bool = False) -> dict[str, Any] | MISPEventBlocklist: """Update an event in the blocklist :param event_blocklist: event block list @@ -3455,7 +3497,7 @@ class PyMISP: e.from_dict(**updated_event_blocklist) return e - def update_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist, organisation_blocklist_id: Optional[Union[int, str, UUID]] = None, pythonify: bool = False) -> Union[Dict, MISPOrganisationBlocklist]: + def update_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist, organisation_blocklist_id: int | str | UUID | None = None, pythonify: bool = False) -> dict[str, Any] | MISPOrganisationBlocklist: """Update an organisation in the blocklist :param organisation_blocklist: organization block list @@ -3473,7 +3515,7 @@ class PyMISP: o.from_dict(**updated_organisation_blocklist) return o - def delete_event_blocklist(self, event_blocklist: Union[MISPEventBlocklist, str, UUID]) -> Dict: + def delete_event_blocklist(self, event_blocklist: MISPEventBlocklist | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a blocklisted event by id :param event_blocklist: event block list to delete @@ -3482,7 +3524,7 @@ class PyMISP: response = self._prepare_request('POST', f'eventBlocklists/delete/{event_blocklist_id}') return self._check_json_response(response) - def delete_organisation_blocklist(self, organisation_blocklist: Union[MISPOrganisationBlocklist, str, UUID]) -> Dict: + def delete_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist | str | UUID) -> dict[str, Any] | list[dict[str, Any]]: """Delete a blocklisted organisation by id :param organisation_blocklist: organization block list to delete @@ -3495,7 +3537,8 @@ class PyMISP: # ## BEGIN Global helpers ### - def change_sharing_group_on_entity(self, misp_entity: Union[MISPEvent, MISPAttribute, MISPObject], sharing_group_id, pythonify: bool = False) -> Union[Dict, MISPEvent, MISPObject, MISPAttribute, MISPShadowAttribute]: + def change_sharing_group_on_entity(self, misp_entity: MISPEvent | MISPAttribute | MISPObject, + sharing_group_id: int, pythonify: bool = False) -> dict[str, Any] | MISPEvent | MISPObject | MISPAttribute | MISPShadowAttribute: """Change the sharing group of an event, an attribute, or an object :param misp_entity: entity to change @@ -3517,12 +3560,14 @@ class PyMISP: raise PyMISPError('The misp_entity must be MISPEvent, MISPObject or MISPAttribute') - def tag(self, misp_entity: Union[AbstractMISP, str, dict], tag: Union[MISPTag, str], local: bool = False) -> Dict: + def tag(self, misp_entity: AbstractMISP | str | dict[str, Any], tag: MISPTag | str, + local: bool = False, relationship_type: str | None = None) -> dict[str, Any] | list[dict[str, Any]]: """Tag an event or an attribute. :param misp_entity: a MISPEvent, a MISP Attribute, or a UUID :param tag: tag to add :param local: whether to tag locally + :param relationship_type: Type of relationship between the tag and the attribute or event """ uuid = get_uuid_or_id_from_abstract_misp(misp_entity) if isinstance(tag, MISPTag): @@ -3530,10 +3575,12 @@ class PyMISP: else: tag_name = tag to_post = {'uuid': uuid, 'tag': tag_name, 'local': local} + if relationship_type: + to_post['relationship_type'] = relationship_type response = self._prepare_request('POST', 'tags/attachTagToObject', data=to_post) return self._check_json_response(response) - def untag(self, misp_entity: Union[AbstractMISP, str, dict], tag: Union[MISPTag, str]) -> Dict: + def untag(self, misp_entity: AbstractMISP | str | dict[str, Any], tag: MISPTag | str) -> dict[str, Any] | list[dict[str, Any]]: """Untag an event or an attribute :param misp_entity: misp_entity can be a UUID @@ -3548,9 +3595,9 @@ class PyMISP: response = self._prepare_request('POST', 'tags/removeTagFromObject', data=to_post) return self._check_json_response(response) - def build_complex_query(self, or_parameters: Optional[List[SearchType]] = None, - and_parameters: Optional[List[SearchType]] = None, - not_parameters: Optional[List[SearchType]] = None) -> Dict[str, List[SearchType]]: + def build_complex_query(self, or_parameters: list[SearchType] | None = None, + and_parameters: list[SearchType] | None = None, + not_parameters: list[SearchType] | None = None) -> dict[str, list[SearchType]]: '''Build a complex search query. MISP expects a dictionary with AND, OR and NOT keys.''' to_return = {} if and_parameters: @@ -3574,7 +3621,7 @@ class PyMISP: # ## MISP internal tasks ### - def get_all_functions(self, not_implemented: bool = False): + def get_all_functions(self, not_implemented: bool = False) -> list[str]: '''Get all methods available via the API, including ones that are not implemented.''' response = self._prepare_request('GET', 'servers/queryACL/printAllFunctionNames') functions = self._check_json_response(response) @@ -3591,12 +3638,12 @@ class PyMISP: paths.append(path) if not not_implemented: - return path + return [str(path)] with open(__file__) as f: content = f.read() - not_implemented_paths: List[str] = [] + not_implemented_paths: list[str] = [] for path in paths: if path not in content: not_implemented_paths.append(path) @@ -3605,7 +3652,7 @@ class PyMISP: # ## Internal methods ### - def _old_misp(self, minimal_version_required: tuple, removal_date: Union[str, date, datetime], method: Optional[str] = None, message: Optional[str] = None) -> bool: + def _old_misp(self, minimal_version_required: tuple[int], removal_date: str | date | datetime, method: str | None = None, message: str | None = None) -> bool: if self._misp_version >= minimal_version_required: return False if isinstance(removal_date, (datetime, date)): @@ -3616,13 +3663,13 @@ class PyMISP: warnings.warn(to_print, DeprecationWarning) return True - def _make_misp_bool(self, parameter: Optional[Union[bool, str]] = None) -> int: + def _make_misp_bool(self, parameter: bool | str | None = None) -> int: '''MISP wants 0 or 1 for bool, so we avoid True/False '0', '1' ''' if parameter is None: return 0 return 1 if int(parameter) else 0 - def _make_timestamp(self, value: Union[datetime, date, int, str, float, None]) -> Union[str, int, float, None]: + def _make_timestamp(self, value: datetime | date | int | str | float | None) -> str | int | float | None: '''Catch-all method to normalize anything that can be converted to a timestamp''' if not value: return None @@ -3643,11 +3690,12 @@ class PyMISP: return value return value - def _check_json_response(self, response: requests.Response) -> Dict: # type: ignore + def _check_json_response(self, response: requests.Response) -> dict[str, Any] | list[dict[str, Any]]: r = self._check_response(response, expect_json=True) if isinstance(r, (dict, list)): return r # Else: an exception was raised anyway + raise PyMISPUnexpectedResponse(f'A dict was expected, got a string: {r}') def _check_head_response(self, response: requests.Response) -> bool: if response.status_code == 200: @@ -3657,7 +3705,7 @@ class PyMISP: else: raise MISPServerError(f'Error code {response.status_code} for HEAD request') - def _check_response(self, response: requests.Response, lenient_response_type: bool = False, expect_json: bool = False) -> Union[Dict, str]: + def _check_response(self, response: requests.Response, lenient_response_type: bool = False, expect_json: bool = False) -> dict[str, Any] | str: """Check if the response from the server is not an unexpected error""" if response.status_code >= 500: headers_without_auth = {i: response.request.headers[i] for i in response.request.headers if i != 'Authorization'} @@ -3667,7 +3715,7 @@ class PyMISP: if 400 <= response.status_code < 500: # The server returns a json message with the error details try: - error_message = response.json() + error_message = loads(response.content) except Exception: raise MISPServerError(f'Error code {response.status_code}:\n{response.text}') @@ -3677,7 +3725,7 @@ class PyMISP: # At this point, we had no error. try: - response_json = response.json() + response_json = loads(response.content) logger.debug(response_json) if isinstance(response_json, dict) and response_json.get('response') is not None: # Cleanup. @@ -3696,21 +3744,21 @@ class PyMISP: return {'errors': 'The response is empty.'} return response.text - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(url={self.root_url})' - def _prepare_request(self, request_type: str, url: str, data: Optional[Union[Iterable, Mapping, AbstractMISP, bytes]] = None, - params: Mapping = {}, kw_params: Mapping = {}, + def _prepare_request(self, request_type: str, url: str, data: Iterable[Any] | Mapping[str, Any] | AbstractMISP | bytes | None = None, + params: Mapping[str, Any] = {}, kw_params: Mapping[str, Any] = {}, output_type: str = 'json', content_type: str = 'json') -> requests.Response: '''Prepare a request for python-requests''' if url[0] == '/': # strip it: it will fail if MISP is in a sub directory url = url[1:] - # Cake PHP being an idiot, it doesn't accepts %20 (space) in the URL path, + # Cake PHP being an idiot, it doesn't accept %20 (space) in the URL path, # so we need to make it a + instead and hope for the best url = url.replace(' ', '+') url = urljoin(self.root_url, url) - d: Optional[Union[bytes, str]] = None + d: bytes | str | None = None if data is not None: if isinstance(data, bytes): d = data @@ -3718,9 +3766,9 @@ class PyMISP: if isinstance(data, dict): # Remove None values. data = {k: v for k, v in data.items() if v is not None} - d = json.dumps(data, default=pymisp_json_default) + d = dumps(data, default=pymisp_json_default) - logger.debug(f'{request_type} - {url}') + logger.debug('%s - %s', request_type, url) if d is not None: logger.debug(d) @@ -3730,9 +3778,7 @@ class PyMISP: url = f'{url}/{to_append_url}' req = requests.Request(request_type, url, data=d, params=params) - user_agent = f'PyMISP {__version__} - Python {".".join(str(x) for x in sys.version_info[:2])}' - if self.tool: - user_agent = f'{user_agent} - {self.tool}' + user_agent = f'{self._user_agent} - {self.tool}' if self.tool else self._user_agent req.auth = self.auth prepped = self.__session.prepare_request(req) prepped.headers.update( @@ -3745,7 +3791,7 @@ class PyMISP: verify=self.ssl, cert=self.cert) return self.__session.send(prepped, timeout=self.timeout, **settings) - def _csv_to_dict(self, csv_content: str) -> List[dict]: + def _csv_to_dict(self, csv_content: str) -> list[dict[str, Any]]: '''Makes a list of dict out of a csv file (requires headers)''' fieldnames, lines = csv_content.split('\n', 1) fields = fieldnames.split(',') diff --git a/pymisp/data/misp-objects b/pymisp/data/misp-objects index ca371d4..3ac5099 160000 --- a/pymisp/data/misp-objects +++ b/pymisp/data/misp-objects @@ -1 +1 @@ -Subproject commit ca371d456712d0484a7585fbe1bcad4128272512 +Subproject commit 3ac509965fdbca06d8a027db22c0064588babd3c diff --git a/pymisp/exceptions.py b/pymisp/exceptions.py index 96f3544..a0dd736 100644 --- a/pymisp/exceptions.py +++ b/pymisp/exceptions.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations class PyMISPError(Exception): - def __init__(self, message): - super(PyMISPError, self).__init__(message) + def __init__(self, message: str) -> None: + super().__init__(message) self.message = message diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index d08024c..9bab316 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations from datetime import timezone, datetime, date import copy -import json import os import base64 import sys @@ -14,7 +13,14 @@ from collections import defaultdict import logging import hashlib from pathlib import Path -from typing import List, Optional, Union, IO, Dict, Any +from typing import IO, Any, Sequence +import warnings + +try: + # orjson is optional dependency that speedups parsing and encoding JSON + import orjson as json # type: ignore +except ImportError: + import json from .abstract import AbstractMISP, MISPTag from .exceptions import (UnknownMISPObjectTemplate, InvalidMISPGalaxy, InvalidMISPObject, @@ -29,50 +35,17 @@ try: except ImportError: logger.exception("Cannot import dateutil") -try: - import jsonschema # type: ignore -except ImportError: - logger.exception("Cannot import jsonschema") -try: - # pyme renamed to gpg the 2016-10-28 - import gpg # type: ignore - from gpg.constants.sig import mode # type: ignore - has_pyme = True -except ImportError: - try: - # pyme renamed to gpg the 2016-10-28 - import pyme as gpg # type: ignore - from pyme.constants.sig import mode # type: ignore - has_pyme = True - except ImportError: - has_pyme = False - - -def _make_datetime(value) -> datetime: +def _make_datetime(value: int | float | str | datetime | date) -> datetime: if isinstance(value, (int, float)): # Timestamp value = datetime.fromtimestamp(value) elif isinstance(value, str): - if sys.version_info >= (3, 7): - try: - # faster - value = datetime.fromisoformat(value) - except Exception: - value = parse(value) - else: - try: - # faster - if '+' in value or value.find('-', 10) > -1: # date contains `-` char - value = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f%z") - elif '.' in value: - value = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") - elif 'T' in value: - value = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S") - else: - value = datetime.strptime(value, "%Y-%m-%d") - except Exception: - value = parse(value) + try: + # faster + value = datetime.fromisoformat(value) + except Exception: + value = parse(value) # type: ignore[arg-type] elif isinstance(value, datetime): pass elif isinstance(value, date): # NOTE: date has to be *after* datetime, or it will be overwritten @@ -86,7 +59,7 @@ def _make_datetime(value) -> datetime: return value -def make_bool(value: Optional[Union[bool, int, str, dict, list]]) -> bool: +def make_bool(value: bool | int | str | dict[str, Any] | list[Any] | None) -> bool: """Converts the supplied value to a boolean. :param value: Value to interpret as a boolean. An empty string, dict @@ -104,22 +77,22 @@ def make_bool(value: Optional[Union[bool, int, str, dict, list]]) -> bool: return False return True else: - raise PyMISPError('Unable to convert {} to a boolean.'.format(value)) + raise PyMISPError(f'Unable to convert {value} to a boolean.') class MISPOrganisation(AbstractMISP): - _fields_for_feed: set = {'name', 'uuid'} + _fields_for_feed: set[str] = {'name', 'uuid'} def __init__(self) -> None: super().__init__() self.id: int self.name: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Organisation' in kwargs: kwargs = kwargs['Organisation'] - super(MISPOrganisation, self).from_dict(**kwargs) + super().from_dict(**kwargs) def __repr__(self) -> str: if hasattr(self, 'name'): @@ -128,14 +101,14 @@ class MISPOrganisation(AbstractMISP): class MISPSharingGroupOrg(AbstractMISP): - _fields_for_feed: set = {'extend', 'Organisation'} + _fields_for_feed: set[str] = {'extend', 'Organisation'} def __init__(self) -> None: super().__init__() self.extend: bool self.Organisation: MISPOrganisation - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'SharingGroupOrg' in kwargs: kwargs = kwargs['SharingGroupOrg'] if 'Organisation' in kwargs: @@ -148,38 +121,38 @@ class MISPSharingGroupOrg(AbstractMISP): return f'<{self.__class__.__name__}(Org={self.Organisation.name}, extend={self.extend})' return f'<{self.__class__.__name__}(NotInitialized)' - def _to_feed(self) -> Dict: + def _to_feed(self) -> dict[str, Any]: to_return = super()._to_feed() to_return['Organisation'] = self.Organisation._to_feed() return to_return class MISPSharingGroup(AbstractMISP): - _fields_for_feed: set = {'uuid', 'name', 'roaming', 'created', 'organisation_uuid', 'Organisation', 'SharingGroupOrg', 'SharingGroupServer'} + _fields_for_feed: set[str] = {'uuid', 'name', 'roaming', 'created', 'organisation_uuid', 'Organisation', 'SharingGroupOrg', 'SharingGroupServer'} def __init__(self) -> None: super().__init__() self.name: str - self.SharingGroupOrg: List[MISPSharingGroupOrg] = [] + self.SharingGroupOrg: list[MISPSharingGroupOrg] = [] @property - def sgorgs(self) -> List[MISPSharingGroupOrg]: + def sgorgs(self) -> list[MISPSharingGroupOrg]: return self.SharingGroupOrg @sgorgs.setter - def sgorgs(self, sgorgs: List[MISPSharingGroupOrg]): + def sgorgs(self, sgorgs: list[MISPSharingGroupOrg]) -> None: if all(isinstance(x, MISPSharingGroupOrg) for x in sgorgs): self.SharingGroupOrg = sgorgs else: raise PyMISPError('All the attributes have to be of type MISPSharingGroupOrg.') - def add_sgorg(self, sgorg): + def add_sgorg(self, sgorg: dict[str, Any]) -> MISPSharingGroupOrg: misp_sgorg = MISPSharingGroupOrg() misp_sgorg.from_dict(**sgorg) self.SharingGroupOrg.append(misp_sgorg) return misp_sgorg - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'SharingGroupOrg' in kwargs: [self.add_sgorg(sgorg) for sgorg in kwargs.pop('SharingGroupOrg')] if 'SharingGroup' in kwargs: @@ -191,7 +164,7 @@ class MISPSharingGroup(AbstractMISP): return f'<{self.__class__.__name__}(name={self.name})>' return f'<{self.__class__.__name__}(NotInitialized)>' - def _to_feed(self) -> Dict: + def _to_feed(self) -> dict[str, Any]: to_return = super()._to_feed() to_return['SharingGroupOrg'] = [sgorg._to_feed() for sgorg in self.SharingGroupOrg] to_return['Organisation'].pop('id', None) @@ -210,7 +183,7 @@ class MISPShadowAttribute(AbstractMISP): self.type: str self.value: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'ShadowAttribute' in kwargs: kwargs = kwargs['ShadowAttribute'] super().from_dict(**kwargs) @@ -228,7 +201,7 @@ class MISPSighting(AbstractMISP): self.id: int self.value: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Initialize the MISPSighting from a dictionary :param value: Value of the attribute the sighting is related too. Pushing this object @@ -241,7 +214,7 @@ class MISPSighting(AbstractMISP): """ if 'Sighting' in kwargs: kwargs = kwargs['Sighting'] - super(MISPSighting, self).from_dict(**kwargs) + super().from_dict(**kwargs) def __repr__(self) -> str: if hasattr(self, 'value'): @@ -250,15 +223,15 @@ class MISPSighting(AbstractMISP): return '<{self.__class__.__name__}(id={self.id})'.format(self=self) if hasattr(self, 'uuid'): return '<{self.__class__.__name__}(uuid={self.uuid})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPAttribute(AbstractMISP): - _fields_for_feed: set = {'uuid', 'value', 'category', 'type', 'comment', 'data', - 'deleted', 'timestamp', 'to_ids', 'disable_correlation', - 'first_seen', 'last_seen'} + _fields_for_feed: set[str] = {'uuid', 'value', 'category', 'type', 'comment', 'data', + 'deleted', 'timestamp', 'to_ids', 'disable_correlation', + 'first_seen', 'last_seen'} - def __init__(self, describe_types: Optional[Dict] = None, strict: bool = False): + def __init__(self, describe_types: dict[str, Any] | None = None, strict: bool = False): """Represents an Attribute :param describe_types: Use it if you want to overwrite the default describeTypes.json file (you don't) @@ -266,42 +239,45 @@ class MISPAttribute(AbstractMISP): """ super().__init__() if describe_types: - self.describe_types: Dict[str, Any] = describe_types - self.__categories: List[str] = self.describe_types['categories'] - self.__category_type_mapping: Dict[str, List[str]] = self.describe_types['category_type_mappings'] - self.__sane_default: Dict[str, Dict[str, Union[str, int]]] = self.describe_types['sane_defaults'] + self.describe_types: dict[str, Any] = describe_types + self.__categories: list[str] = self.describe_types['categories'] + self.__category_type_mapping: dict[str, list[str]] = self.describe_types['category_type_mappings'] + self.__sane_default: dict[str, dict[str, str | int]] = self.describe_types['sane_defaults'] self.__strict: bool = strict - self.data: Optional[BytesIO] = None + self.data: BytesIO | None = None self.first_seen: datetime self.last_seen: datetime self.uuid: str = str(uuid.uuid4()) - self.ShadowAttribute: List[MISPShadowAttribute] = [] + self.ShadowAttribute: list[MISPShadowAttribute] = [] self.SharingGroup: MISPSharingGroup - self.Sighting: List[MISPSighting] = [] - self.Tag: List[MISPTag] = [] - self.Galaxy: List[MISPGalaxy] = [] + self.Sighting: list[MISPSighting] = [] + self.Tag: list[MISPTag] = [] + self.Galaxy: list[MISPGalaxy] = [] + + self.expand: str + self.timestamp: float | int | datetime # For search self.Event: MISPEvent - self.RelatedAttribute: List[MISPAttribute] + self.RelatedAttribute: list[MISPAttribute] # For malware sample - self._malware_binary: Optional[BytesIO] + self._malware_binary: BytesIO | None - def add_tag(self, tag: Optional[Union[str, MISPTag, Dict]] = None, **kwargs) -> MISPTag: + def add_tag(self, tag: str | MISPTag | dict[str, Any] | None = None, **kwargs) -> MISPTag: # type: ignore[no-untyped-def] return super()._add_tag(tag, **kwargs) @property - def tags(self) -> List[MISPTag]: + def tags(self) -> list[MISPTag]: """Returns a list of tags associated to this Attribute""" return self.Tag @tags.setter - def tags(self, tags: List[MISPTag]): + def tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" super()._set_tags(tags) - def add_galaxy(self, galaxy: Union['MISPGalaxy', dict, None] = None, **kwargs) -> 'MISPGalaxy': + def add_galaxy(self, galaxy: MISPGalaxy | dict[str, Any] | None = None, **kwargs) -> MISPGalaxy: # type: ignore[no-untyped-def] """Add a galaxy to the Attribute, either by passing a MISPGalaxy or a dictionary""" if isinstance(galaxy, MISPGalaxy): self.galaxies.append(galaxy) @@ -318,11 +294,11 @@ class MISPAttribute(AbstractMISP): return misp_galaxy @property - def galaxies(self) -> List['MISPGalaxy']: + def galaxies(self) -> list[MISPGalaxy]: """Returns a list of galaxies associated to this Attribute""" return self.Galaxy - def _prepare_data(self, data: Optional[Union[Path, str, bytes, BytesIO]]): + def _prepare_data(self, data: Path | str | bytes | BytesIO | None) -> None: if not data: super().__setattr__('data', None) return @@ -354,7 +330,7 @@ class MISPAttribute(AbstractMISP): # not a encrypted zip file, assuming it is a new malware sample self._prepare_new_malware_sample() - def __setattr__(self, name: str, value: Any): + def __setattr__(self, name: str, value: Any) -> None: if name in ['first_seen', 'last_seen']: _datetime = _make_datetime(value) @@ -370,10 +346,10 @@ class MISPAttribute(AbstractMISP): else: super().__setattr__(name, value) - def hash_values(self, algorithm: str = 'sha512') -> List[str]: + def hash_values(self, algorithm: str = 'sha512') -> list[str]: """Compute the hash of every value for fast lookups""" if algorithm not in hashlib.algorithms_available: - raise PyMISPError('The algorithm {} is not available for hashing.'.format(algorithm)) + raise PyMISPError(f'The algorithm {algorithm} is not available for hashing.') if '|' in self.type or self.type == 'malware-sample': hashes = [] for v in self.value.split('|'): @@ -389,13 +365,13 @@ class MISPAttribute(AbstractMISP): h.update(to_encode.encode("utf-8")) return [h.hexdigest()] - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'comment'): self.comment = '' if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) - def _to_feed(self, with_distribution=False) -> Dict: + def _to_feed(self, with_distribution: bool=False) -> dict[str, Any]: if with_distribution: self._fields_for_feed.add('distribution') to_return = super()._to_feed() @@ -411,12 +387,12 @@ class MISPAttribute(AbstractMISP): return to_return @property - def known_types(self) -> List[str]: + def known_types(self) -> list[str]: """Returns a list of all the known MISP attributes types""" return self.describe_types['types'] @property - def malware_binary(self) -> Optional[BytesIO]: + def malware_binary(self) -> BytesIO | None: """Returns a BytesIO of the malware, if the attribute has one. Decrypts, unpacks and caches the binary on the first invocation, which may require some time for large attachments (~1s/MB). @@ -438,11 +414,11 @@ class MISPAttribute(AbstractMISP): return None @property - def shadow_attributes(self) -> List[MISPShadowAttribute]: + def shadow_attributes(self) -> list[MISPShadowAttribute]: return self.ShadowAttribute @shadow_attributes.setter - def shadow_attributes(self, shadow_attributes: List[MISPShadowAttribute]): + def shadow_attributes(self, shadow_attributes: list[MISPShadowAttribute]) -> None: """Set a list of prepared MISPShadowAttribute.""" if all(isinstance(x, MISPShadowAttribute) for x in shadow_attributes): self.ShadowAttribute = shadow_attributes @@ -450,26 +426,26 @@ class MISPAttribute(AbstractMISP): raise PyMISPError('All the attributes have to be of type MISPShadowAttribute.') @property - def sightings(self) -> List[MISPSighting]: + def sightings(self) -> list[MISPSighting]: return self.Sighting @sightings.setter - def sightings(self, sightings: List[MISPSighting]): + def sightings(self, sightings: list[MISPSighting]) -> None: """Set a list of prepared MISPSighting.""" if all(isinstance(x, MISPSighting) for x in sightings): self.Sighting = sightings else: raise PyMISPError('All the attributes have to be of type MISPSighting.') - def delete(self): + def delete(self) -> None: """Mark the attribute as deleted (soft delete)""" self.deleted = True - def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: + def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Alias for add_shadow_attribute""" return self.add_shadow_attribute(shadow_attribute, **kwargs) - def add_shadow_attribute(self, shadow_attribute: Optional[Union[MISPShadowAttribute, Dict]] = None, **kwargs) -> MISPShadowAttribute: + def add_shadow_attribute(self, shadow_attribute: MISPShadowAttribute | dict[str, Any] | None = None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Add a shadow attribute to the attribute (by name or a MISPShadowAttribute object)""" if isinstance(shadow_attribute, MISPShadowAttribute): misp_shadow_attribute = shadow_attribute @@ -480,12 +456,12 @@ class MISPAttribute(AbstractMISP): misp_shadow_attribute = MISPShadowAttribute() misp_shadow_attribute.from_dict(**kwargs) else: - raise PyMISPError("The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {}".format(shadow_attribute)) + raise PyMISPError(f"The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): {shadow_attribute}") self.shadow_attributes.append(misp_shadow_attribute) self.edited = True return misp_shadow_attribute - def add_sighting(self, sighting: Optional[Union[MISPSighting, dict]] = None, **kwargs) -> MISPSighting: + def add_sighting(self, sighting: MISPSighting | dict[str, Any] | None = None, **kwargs) -> MISPSighting: # type: ignore[no-untyped-def] """Add a sighting to the attribute (by name or a MISPSighting object)""" if isinstance(sighting, MISPSighting): misp_sighting = sighting @@ -496,12 +472,12 @@ class MISPAttribute(AbstractMISP): misp_sighting = MISPSighting() misp_sighting.from_dict(**kwargs) else: - raise PyMISPError("The sighting is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {}".format(sighting)) + raise PyMISPError(f"The sighting is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): {sighting}") self.sightings.append(misp_sighting) self.edited = True return misp_sighting - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Attribute' in kwargs: kwargs = kwargs['Attribute'] if kwargs.get('type') and kwargs.get('category'): @@ -554,13 +530,13 @@ class MISPAttribute(AbstractMISP): self.to_ids = make_bool(self.to_ids) if not isinstance(self.to_ids, bool): - raise NewAttributeError('{} is invalid, to_ids has to be True or False'.format(self.to_ids)) + raise NewAttributeError(f'{self.to_ids} is invalid, to_ids has to be True or False') self.distribution = kwargs.pop('distribution', None) if self.distribution is not None: self.distribution = int(self.distribution) if self.distribution not in [0, 1, 2, 3, 4, 5]: - raise NewAttributeError('{} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5'.format(self.distribution)) + raise NewAttributeError(f'{self.distribution} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5') # other possible values if kwargs.get('data'): @@ -579,10 +555,7 @@ class MISPAttribute(AbstractMISP): fs = kwargs.pop('first_seen') try: # Faster - if sys.version_info >= (3, 7): - self.first_seen = datetime.fromisoformat(fs) - else: - self.first_seen = datetime.strptime(fs, "%Y-%m-%dT%H:%M:%S.%f%z") + self.first_seen = datetime.fromisoformat(fs) except Exception: # Use __setattr__ self.first_seen = fs @@ -591,10 +564,7 @@ class MISPAttribute(AbstractMISP): ls = kwargs.pop('last_seen') try: # Faster - if sys.version_info >= (3, 7): - self.last_seen = datetime.fromisoformat(ls) - else: - self.last_seen = datetime.strptime(ls, "%Y-%m-%dT%H:%M:%S.%f%z") + self.last_seen = datetime.fromisoformat(ls) except Exception: # Use __setattr__ self.last_seen = ls @@ -608,7 +578,7 @@ class MISPAttribute(AbstractMISP): raise NewAttributeError('If the distribution is set to sharing group, a sharing group ID is required.') elif not self.sharing_group_id: # Cannot be None or 0 either. - raise NewAttributeError('If the distribution is set to sharing group, a sharing group ID is required (cannot be {}).'.format(self.sharing_group_id)) + raise NewAttributeError(f'If the distribution is set to sharing group, a sharing group ID is required (cannot be {self.sharing_group_id}).') if kwargs.get('Tag'): [self.add_tag(tag) for tag in kwargs.pop('Tag')] @@ -629,16 +599,16 @@ class MISPAttribute(AbstractMISP): super().from_dict(**kwargs) - def to_dict(self, json_format: bool = False) -> Dict: + def to_dict(self, json_format: bool = False) -> dict[str, Any]: to_return = super().to_dict(json_format) if self.data: to_return['data'] = base64.b64encode(self.data.getvalue()).decode() return to_return - def _prepare_new_malware_sample(self): + def _prepare_new_malware_sample(self) -> None: if '|' in self.value: # Get the filename, ignore the md5, because humans. - self.malware_filename, md5 = self.value.split('|') + self.malware_filename, md5 = self.value.rsplit('|', 1) else: # Assuming the user only passed the filename self.malware_filename = self.value @@ -646,7 +616,7 @@ class MISPAttribute(AbstractMISP): self._malware_binary = self.data self.encrypt = True - def __is_misp_encrypted_file(self, f) -> bool: + def __is_misp_encrypted_file(self, f: ZipFile) -> bool: files_list = f.namelist() if len(files_list) != 2: return False @@ -661,48 +631,16 @@ class MISPAttribute(AbstractMISP): return False return True - def __repr__(self): + def __repr__(self) -> str: if hasattr(self, 'value'): return '<{self.__class__.__name__}(type={self.type}, value={self.value})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) - - def verify(self, gpg_uid): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - signed_data = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - try: - c.verify(signed_data, signature=base64.b64decode(self.sig), verify=keys[:1]) - return {self.uuid: True} - except Exception: - return {self.uuid: False} - - def _serialize(self): # pragma: no cover - # Not used - return '{type}{category}{to_ids}{uuid}{timestamp}{comment}{deleted}{value}'.format( - type=self.type, category=self.category, to_ids=self.to_ids, uuid=self.uuid, timestamp=self.timestamp, - comment=self.comment, deleted=self.deleted, value=self.value).encode() - - def sign(self, gpg_uid, passphrase=None): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - to_sign = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - c.signers = keys[:1] - if passphrase: - c.set_passphrase_cb(lambda *args: passphrase) - signed, _ = c.sign(to_sign, mode=mode.DETACH) - self.sig = base64.b64encode(signed).decode() + return f'<{self.__class__.__name__}(NotInitialized)' class MISPObjectReference(AbstractMISP): - _fields_for_feed: set = {'uuid', 'timestamp', 'relationship_type', 'comment', - 'object_uuid', 'referenced_uuid'} + _fields_for_feed: set[str] = {'uuid', 'timestamp', 'relationship_type', 'comment', + 'object_uuid', 'referenced_uuid'} def __init__(self) -> None: super().__init__() @@ -711,30 +649,31 @@ class MISPObjectReference(AbstractMISP): self.referenced_uuid: str self.relationship_type: str - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'comment'): self.comment = '' if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'ObjectReference' in kwargs: kwargs = kwargs['ObjectReference'] - super(MISPObjectReference, self).from_dict(**kwargs) + super().from_dict(**kwargs) def __repr__(self) -> str: if hasattr(self, 'referenced_uuid') and hasattr(self, 'object_uuid'): return '<{self.__class__.__name__}(object_uuid={self.object_uuid}, referenced_uuid={self.referenced_uuid}, relationship_type={self.relationship_type})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPObject(AbstractMISP): - _fields_for_feed: set = {'name', 'meta-category', 'description', 'template_uuid', - 'template_version', 'uuid', 'timestamp', 'comment', - 'first_seen', 'last_seen', 'deleted'} + _fields_for_feed: set[str] = {'name', 'meta-category', 'description', 'template_uuid', + 'template_version', 'uuid', 'timestamp', 'comment', + 'first_seen', 'last_seen', 'deleted'} - def __init__(self, name: str, strict: bool = False, standalone: bool = True, default_attributes_parameters: Dict = {}, **kwargs): + def __init__(self, name: str, strict: bool = False, standalone: bool = True, # type: ignore[no-untyped-def] + default_attributes_parameters: dict[str, Any] = {}, **kwargs) -> None: ''' Master class representing a generic MISP object :param name: Name of the object @@ -750,7 +689,8 @@ class MISPObject(AbstractMISP): self.name: str = name self._known_template: bool = False self.id: int - self._definition: Optional[Dict] + self._definition: dict[str, Any] | None + self.timestamp: float | int | datetime misp_objects_template_custom = kwargs.pop('misp_objects_template_custom', None) misp_objects_path_custom = kwargs.pop('misp_objects_path_custom', None) @@ -763,12 +703,12 @@ class MISPObject(AbstractMISP): self.uuid: str = str(uuid.uuid4()) self.first_seen: datetime self.last_seen: datetime - self.__fast_attribute_access: dict = defaultdict(list) # Hashtable object_relation: [attributes] - self.ObjectReference: List[MISPObjectReference] = [] + self.__fast_attribute_access: dict[str, Any] = defaultdict(list) # Hashtable object_relation: [attributes] + self.ObjectReference: list[MISPObjectReference] = [] self._standalone: bool = False - self.Attribute: List[MISPObjectAttribute] = [] + self.Attribute: list[MISPObjectAttribute] = [] self.SharingGroup: MISPSharingGroup - self._default_attributes_parameters: dict + self._default_attributes_parameters: dict[str, Any] if isinstance(default_attributes_parameters, MISPAttribute): # Just make sure we're not modifying an existing MISPAttribute self._default_attributes_parameters = default_attributes_parameters.to_dict() @@ -795,7 +735,7 @@ class MISPObject(AbstractMISP): self.sharing_group_id = 0 self.standalone = standalone - def _load_template_path(self, template_path: Union[Path, str]) -> bool: + def _load_template_path(self, template_path: Path | str) -> bool: template = self._load_json(template_path) if not template: self._definition = None @@ -803,20 +743,20 @@ class MISPObject(AbstractMISP): self._load_template(template) return True - def _load_template(self, template: Dict) -> None: + def _load_template(self, template: dict[str, Any]) -> None: self._definition = template setattr(self, 'meta-category', self._definition['meta-category']) self.template_uuid = self._definition['uuid'] self.description = self._definition['description'] self.template_version = self._definition['version'] - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'comment'): self.comment = '' if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) - def _to_feed(self, with_distribution=False) -> Dict: + def _to_feed(self, with_distribution: bool=False) -> dict[str, Any]: if with_distribution: self._fields_for_feed.add('distribution') if not hasattr(self, 'template_uuid'): # workaround for old events where the template_uuid was not yet mandatory @@ -825,7 +765,7 @@ class MISPObject(AbstractMISP): self.description = '' if not hasattr(self, 'meta-category'): # workaround for old events where meta-category is not always set setattr(self, 'meta-category', 'misc') - to_return = super(MISPObject, self)._to_feed() + to_return = super()._to_feed() if self.references: to_return['ObjectReference'] = [reference._to_feed() for reference in self.references] if with_distribution: @@ -835,7 +775,7 @@ class MISPObject(AbstractMISP): pass return to_return - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any) -> None: if name in ['first_seen', 'last_seen']: value = _make_datetime(value) @@ -845,12 +785,12 @@ class MISPObject(AbstractMISP): logger.warning(f'first_seen ({value}) has to be before last_seen ({self.last_seen})') super().__setattr__(name, value) - def force_misp_objects_path_custom(self, misp_objects_path_custom: Union[Path, str], object_name: Optional[str] = None): + def force_misp_objects_path_custom(self, misp_objects_path_custom: Path | str, object_name: str | None = None) -> None: if object_name: self.name = object_name self._set_template(misp_objects_path_custom) - def _set_template(self, misp_objects_path_custom: Optional[Union[Path, str]] = None, misp_objects_template_custom: Optional[Dict] = None): + def _set_template(self, misp_objects_path_custom: Path | str | None = None, misp_objects_template_custom: dict[str, Any] | None = None) -> None: if misp_objects_template_custom: # A complete template was given to the constructor self._load_template(misp_objects_template_custom) @@ -867,26 +807,27 @@ class MISPObject(AbstractMISP): self._known_template = self._load_template_path(self.misp_objects_path / self.name / 'definition.json') if not self._known_template and self._strict: - raise UnknownMISPObjectTemplate('{} is unknown in the MISP object directory.'.format(self.name)) + raise UnknownMISPObjectTemplate(f'{self.name} is unknown in the MISP object directory.') else: # Then we have no meta-category, template_uuid, description and template_version pass - def delete(self): + def delete(self) -> None: """Mark the object as deleted (soft delete)""" self.deleted = True - [a.delete() for a in self.attributes] + for a in self.attributes: + a.delete() @property - def disable_validation(self): + def disable_validation(self) -> None: self._strict = False @property - def attributes(self) -> List['MISPObjectAttribute']: + def attributes(self) -> list[MISPObjectAttribute]: return self.Attribute @attributes.setter - def attributes(self, attributes: List['MISPObjectAttribute']): + def attributes(self, attributes: list[MISPObjectAttribute]) -> None: if all(isinstance(x, MISPObjectAttribute) for x in attributes): self.Attribute = attributes self.__fast_attribute_access = defaultdict(list) @@ -894,22 +835,22 @@ class MISPObject(AbstractMISP): raise PyMISPError('All the attributes have to be of type MISPObjectAttribute.') @property - def references(self) -> List[MISPObjectReference]: + def references(self) -> list[MISPObjectReference]: return self.ObjectReference @references.setter - def references(self, references: List[MISPObjectReference]): + def references(self, references: list[MISPObjectReference]) -> None: if all(isinstance(x, MISPObjectReference) for x in references): self.ObjectReference = references else: raise PyMISPError('All the attributes have to be of type MISPObjectReference.') @property - def standalone(self): + def standalone(self) -> bool: return self._standalone @standalone.setter - def standalone(self, new_standalone: bool): + def standalone(self, new_standalone: bool) -> None: if self._standalone != new_standalone: if new_standalone: self.update_not_jsonable("ObjectReference") @@ -919,7 +860,7 @@ class MISPObject(AbstractMISP): else: pass - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Object' in kwargs: kwargs = kwargs['Object'] if self._known_template: @@ -942,7 +883,7 @@ class MISPObject(AbstractMISP): self.distribution = kwargs.pop('distribution') self.distribution = int(self.distribution) if self.distribution not in [0, 1, 2, 3, 4, 5]: - raise NewAttributeError('{} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5'.format(self.distribution)) + raise NewAttributeError(f'{self.distribution} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5') if kwargs.get('timestamp'): ts = kwargs.pop('timestamp') @@ -955,10 +896,7 @@ class MISPObject(AbstractMISP): fs = kwargs.pop('first_seen') try: # Faster - if sys.version_info >= (3, 7): - self.first_seen = datetime.fromisoformat(fs) - else: - self.first_seen = datetime.strptime(fs, "%Y-%m-%dT%H:%M:%S.%f%z") + self.first_seen = datetime.fromisoformat(fs) except Exception: # Use __setattr__ self.first_seen = fs @@ -967,10 +905,7 @@ class MISPObject(AbstractMISP): ls = kwargs.pop('last_seen') try: # Faster - if sys.version_info >= (3, 7): - self.last_seen = datetime.fromisoformat(ls) - else: - self.last_seen = datetime.strptime(ls, "%Y-%m-%dT%H:%M:%S.%f%z") + self.last_seen = datetime.fromisoformat(ls) except Exception: # Use __setattr__ self.last_seen = ls @@ -990,7 +925,7 @@ class MISPObject(AbstractMISP): super().from_dict(**kwargs) - def add_reference(self, referenced_uuid: Union[AbstractMISP, str], relationship_type: str, comment: Optional[str] = None, **kwargs) -> MISPObjectReference: + def add_reference(self, referenced_uuid: AbstractMISP | str, relationship_type: str, comment: str | None = None, **kwargs) -> MISPObjectReference: # type: ignore[no-untyped-def] """Add a link (uuid) to another object""" if isinstance(referenced_uuid, AbstractMISP): # Allow to pass an object or an attribute instead of its UUID @@ -1012,22 +947,22 @@ class MISPObject(AbstractMISP): self.edited = True return reference - def get_attributes_by_relation(self, object_relation: str) -> List[MISPAttribute]: + def get_attributes_by_relation(self, object_relation: str) -> list[MISPAttribute]: '''Returns the list of attributes with the given object relation in the object''' return self._fast_attribute_access.get(object_relation, []) @property - def _fast_attribute_access(self) -> Dict: + def _fast_attribute_access(self) -> dict[str, Any]: if not self.__fast_attribute_access: for a in self.attributes: self.__fast_attribute_access[a.object_relation].append(a) return self.__fast_attribute_access - def has_attributes_by_relation(self, list_of_relations: List[str]) -> bool: + def has_attributes_by_relation(self, list_of_relations: list[str]) -> bool: '''True if all the relations in the list are defined in the object''' return all(relation in self._fast_attribute_access for relation in list_of_relations) - def add_attribute(self, object_relation: str, simple_value: Optional[Union[str, int, float]] = None, **value) -> Optional[MISPAttribute]: + def add_attribute(self, object_relation: str, simple_value: str | int | float | None = None, **value) -> MISPAttribute | None: # type: ignore[no-untyped-def] """Add an attribute. :param object_relation: The object relation of the attribute you're adding to the object :param simple_value: The value @@ -1041,7 +976,7 @@ class MISPObject(AbstractMISP): if simple_value is not None: # /!\ The value *can* be 0 value['value'] = simple_value if value.get('value') is None: - logger.warning("The value of the attribute you're trying to add is None, skipping it. Object relation: {}".format(object_relation)) + logger.warning(f"The value of the attribute you're trying to add is None, skipping it. Object relation: {object_relation}") return None else: if isinstance(value['value'], bytes): @@ -1057,14 +992,14 @@ class MISPObject(AbstractMISP): if isinstance(value['value'], str): value['value'] = value['value'].strip().strip('\x00') if value['value'] == '': - logger.warning("The value of the attribute you're trying to add is an empty string, skipping it. Object relation: {}".format(object_relation)) + logger.warning(f"The value of the attribute you're trying to add is an empty string, skipping it. Object relation: {object_relation}") return None if self._known_template and self._definition: if object_relation in self._definition['attributes']: attribute = MISPObjectAttribute(self._definition['attributes'][object_relation]) else: # Woopsie, this object_relation is unknown, no sane defaults for you. - logger.warning("The template ({}) doesn't have the object_relation ({}) you're trying to add. If you are creating a new event to push to MISP, please review your code so it matches the template.".format(self.name, object_relation)) + logger.warning(f"The template ({self.name}) doesn't have the object_relation ({object_relation}) you're trying to add. If you are creating a new event to push to MISP, please review your code so it matches the template.") attribute = MISPObjectAttribute({}) else: attribute = MISPObjectAttribute({}) @@ -1075,29 +1010,31 @@ class MISPObject(AbstractMISP): self.edited = True return attribute - def add_attributes(self, object_relation: str, *attributes) -> List[Optional[MISPAttribute]]: + def add_attributes(self, object_relation: str, *attributes: Sequence[str | dict[str, Any] | MISPAttribute]) -> list[MISPAttribute | None]: '''Add multiple attributes with the same object_relation. Helper for object_relation when multiple is True in the template. It is the same as calling multiple times add_attribute with the same object_relation. ''' to_return = [] for attribute in attributes: - if isinstance(attribute, dict): - a = self.add_attribute(object_relation, **attribute) + if isinstance(attribute, MISPAttribute): + a = self.add_attribute(object_relation, **attribute.to_dict()) + elif isinstance(attribute, dict): + a = self.add_attribute(object_relation, **attribute) # type: ignore[misc] else: a = self.add_attribute(object_relation, value=attribute) to_return.append(a) return to_return - def to_dict(self, json_format: bool = False, strict: bool = False) -> Dict: + def to_dict(self, json_format: bool = False, strict: bool = False) -> dict[str, Any]: if strict or self._strict and self._known_template: self._validate() - return super(MISPObject, self).to_dict(json_format) + return super().to_dict(json_format) - def to_json(self, sort_keys: bool = False, indent: Optional[int] = None, strict: bool = False): + def to_json(self, sort_keys: bool = False, indent: int | None = None, strict: bool = False) -> str: if strict or self._strict and self._known_template: self._validate() - return super(MISPObject, self).to_json(sort_keys=sort_keys, indent=indent) + return super().to_json(sort_keys=sort_keys, indent=indent) def _validate(self) -> bool: if not self._definition: @@ -1106,7 +1043,7 @@ class MISPObject(AbstractMISP): if self._definition.get('required'): required_missing = set(self._definition['required']) - set(self._fast_attribute_access.keys()) if required_missing: - raise InvalidMISPObject('{} are required.'.format(required_missing)) + raise InvalidMISPObject(f'{required_missing} are required.') if self._definition.get('requiredOneOf'): if not set(self._definition['requiredOneOf']) & set(self._fast_attribute_access.keys()): # We ecpect at least one of the object_relation in requiredOneOf, and it isn't the case @@ -1117,20 +1054,22 @@ class MISPObject(AbstractMISP): continue if not self._definition['attributes'][rel].get('multiple'): # object_relation's here more than once, but it isn't allowed in the template. - raise InvalidMISPObject('Multiple occurrences of {} is not allowed'.format(rel)) + raise InvalidMISPObject(f'Multiple occurrences of {rel} is not allowed') return True def __repr__(self) -> str: if hasattr(self, 'name'): return '<{self.__class__.__name__}(name={self.name})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPEventReport(AbstractMISP): - _fields_for_feed: set = {'uuid', 'name', 'content', 'timestamp', 'deleted'} + _fields_for_feed: set[str] = {'uuid', 'name', 'content', 'timestamp', 'deleted'} - def from_dict(self, **kwargs): + timestamp: float | int | datetime + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventReport' in kwargs: kwargs = kwargs['EventReport'] @@ -1138,7 +1077,7 @@ class MISPEventReport(AbstractMISP): if self.distribution is not None: self.distribution = int(self.distribution) if self.distribution not in [0, 1, 2, 3, 4, 5]: - raise NewEventReportError('{} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5'.format(self.distribution)) + raise NewEventReportError(f'{self.distribution} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5') if kwargs.get('sharing_group_id'): self.sharing_group_id = int(kwargs.pop('sharing_group_id')) @@ -1149,7 +1088,7 @@ class MISPEventReport(AbstractMISP): raise NewEventReportError('If the distribution is set to sharing group, a sharing group ID is required.') elif not self.sharing_group_id: # Cannot be None or 0 either. - raise NewEventReportError('If the distribution is set to sharing group, a sharing group ID is required (cannot be {}).'.format(self.sharing_group_id)) + raise NewEventReportError(f'If the distribution is set to sharing group, a sharing group ID is required (cannot be {self.sharing_group_id}).') self.name = kwargs.pop('name', None) if self.name is None: @@ -1177,9 +1116,9 @@ class MISPEventReport(AbstractMISP): def __repr__(self) -> str: if hasattr(self, 'name'): return '<{self.__class__.__name__}(name={self.name})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) if not hasattr(self, 'name'): @@ -1205,15 +1144,15 @@ class MISPGalaxyClusterElement(AbstractMISP): def __repr__(self) -> str: if hasattr(self, 'key') and hasattr(self, 'value'): return '<{self.__class__.__name__}(key={self.key}, value={self.value})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: Any) -> None: if key == "value" and isinstance(value, list): raise PyMISPError("You tried to set a list to a cluster element's value. " "Instead, create seperate elements for each value") super().__setattr__(key, value) - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if kwargs.get('id'): self.id = int(kwargs.pop('id')) if kwargs.get('galaxy_cluster_id'): @@ -1237,7 +1176,7 @@ class MISPGalaxyClusterRelation(AbstractMISP): def __repr__(self) -> str: if hasattr(self, "referenced_galaxy_cluster_type"): return '<{self.__class__.__name__}(referenced_galaxy_cluster_type={self.referenced_galaxy_cluster_type})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' def __init__(self) -> None: super().__init__() @@ -1245,9 +1184,9 @@ class MISPGalaxyClusterRelation(AbstractMISP): self.referenced_galaxy_cluster_uuid: str self.distribution: int = 0 self.referenced_galaxy_cluster_type: str - self.Tag: List[MISPTag] = [] + self.Tag: list[MISPTag] = [] - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] # Default values for a valid event to send to a MISP instance self.distribution = int(kwargs.pop('distribution', 0)) if self.distribution not in [0, 1, 2, 3, 4, 5]: @@ -1262,7 +1201,7 @@ class MISPGalaxyClusterRelation(AbstractMISP): raise NewGalaxyClusterRelationError('If the distribution is set to sharing group, a sharing group ID is required.') elif not self.sharing_group_id: # Cannot be None or 0 either. - raise NewGalaxyClusterRelationError('If the distribution is set to sharing group, a sharing group ID is required (cannot be {}).'.format(self.sharing_group_id)) + raise NewGalaxyClusterRelationError(f'If the distribution is set to sharing group, a sharing group ID is required (cannot be {self.sharing_group_id}).') if kwargs.get('id'): self.id = int(kwargs.pop('id')) @@ -1283,16 +1222,16 @@ class MISPGalaxyClusterRelation(AbstractMISP): self.SharingGroup.from_dict(**kwargs.pop('SharingGroup')) super().from_dict(**kwargs) - def add_tag(self, tag: Optional[Union[str, MISPTag, Dict]] = None, **kwargs) -> MISPTag: + def add_tag(self, tag: str | MISPTag | dict[str, Any] | None = None, **kwargs) -> MISPTag: # type: ignore[no-untyped-def] return super()._add_tag(tag, **kwargs) @property - def tags(self) -> List[MISPTag]: + def tags(self) -> list[MISPTag]: """Returns a list of tags associated to this Attribute""" return self.Tag @tags.setter - def tags(self, tags: List[MISPTag]): + def tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" super()._set_tags(tags) @@ -1319,12 +1258,16 @@ class MISPGalaxyCluster(AbstractMISP): :type cluster_relations: list[MISPGalaxyClusterRelation], optional """ + id: int | str | None + tag_name: str + galaxy_id: str | None + def __init__(self) -> None: super().__init__() self.Galaxy: MISPGalaxy - self.GalaxyElement: List[MISPGalaxyClusterElement] = [] - self.meta: Dict = {} - self.GalaxyClusterRelation: List[MISPGalaxyClusterRelation] = [] + self.GalaxyElement: list[MISPGalaxyClusterElement] = [] + self.meta: dict[str, Any] = {} + self.GalaxyClusterRelation: list[MISPGalaxyClusterRelation] = [] self.Org: MISPOrganisation self.Orgc: MISPOrganisation self.SharingGroup: MISPSharingGroup @@ -1333,22 +1276,22 @@ class MISPGalaxyCluster(AbstractMISP): self.default = False @property - def cluster_elements(self) -> List[MISPGalaxyClusterElement]: + def cluster_elements(self) -> list[MISPGalaxyClusterElement]: return self.GalaxyElement @cluster_elements.setter - def cluster_elements(self, cluster_elements: List[MISPGalaxyClusterElement]): + def cluster_elements(self, cluster_elements: list[MISPGalaxyClusterElement]) -> None: self.GalaxyElement = cluster_elements @property - def cluster_relations(self) -> List[MISPGalaxyClusterRelation]: + def cluster_relations(self) -> list[MISPGalaxyClusterRelation]: return self.GalaxyClusterRelation @cluster_relations.setter - def cluster_relations(self, cluster_relations: List[MISPGalaxyClusterRelation]): + def cluster_relations(self, cluster_relations: list[MISPGalaxyClusterRelation]) -> None: self.GalaxyClusterRelation = cluster_relations - def parse_meta_as_elements(self): + def parse_meta_as_elements(self) -> None: """Function to parse the meta field into GalaxyClusterElements""" # Parse the cluster elements from the kwargs meta fields for key, value in self.meta.items(): @@ -1359,7 +1302,7 @@ class MISPGalaxyCluster(AbstractMISP): self.add_cluster_element(key=key, value=v) @property - def elements_meta(self) -> Dict: + def elements_meta(self) -> dict[str, Any]: """Function to return the galaxy cluster elements as a dictionary structure of lists that comes from a MISPGalaxy within a MISPEvent. Lossy, you lose the element ID """ @@ -1368,7 +1311,7 @@ class MISPGalaxyCluster(AbstractMISP): response[element.key].append(element.value) return dict(response) - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'GalaxyCluster' in kwargs: kwargs = kwargs['GalaxyCluster'] self.default = kwargs.pop('default', False) @@ -1394,7 +1337,7 @@ class MISPGalaxyCluster(AbstractMISP): raise NewGalaxyClusterError('If the distribution is set to sharing group, a sharing group ID is required.') elif not self.sharing_group_id: # Cannot be None or 0 either. - raise NewGalaxyClusterError('If the distribution is set to sharing group, a sharing group ID is required (cannot be {}).'.format(self.sharing_group_id)) + raise NewGalaxyClusterError(f'If the distribution is set to sharing group, a sharing group ID is required (cannot be {self.sharing_group_id}).') if 'uuid' in kwargs: self.uuid = kwargs.pop('uuid') @@ -1418,7 +1361,7 @@ class MISPGalaxyCluster(AbstractMISP): self.SharingGroup.from_dict(**kwargs.pop('SharingGroup')) super().from_dict(**kwargs) - def add_cluster_element(self, key: str, value: str, **kwargs) -> MISPGalaxyClusterElement: + def add_cluster_element(self, key: str, value: str, **kwargs) -> MISPGalaxyClusterElement: # type: ignore[no-untyped-def] """Add a cluster relation to a MISPGalaxyCluster, key and value are required :param key: The key name of the element @@ -1432,7 +1375,7 @@ class MISPGalaxyCluster(AbstractMISP): self.cluster_elements.append(cluster_element) return cluster_element - def add_cluster_relation(self, referenced_galaxy_cluster_uuid: Union["MISPGalaxyCluster", str, UUID], referenced_galaxy_cluster_type: str, galaxy_cluster_uuid: Optional[str] = None, **kwargs: Dict) -> MISPGalaxyClusterRelation: + def add_cluster_relation(self, referenced_galaxy_cluster_uuid: MISPGalaxyCluster | str | UUID, referenced_galaxy_cluster_type: str, galaxy_cluster_uuid: str | None = None, **kwargs: dict[str, Any]) -> MISPGalaxyClusterRelation: """Add a cluster relation to a MISPGalaxyCluster. :param referenced_galaxy_cluster_uuid: UUID of the related cluster @@ -1462,18 +1405,20 @@ class MISPGalaxyCluster(AbstractMISP): def __repr__(self) -> str: if hasattr(self, 'value'): return '<{self.__class__.__name__}(value={self.value})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPGalaxy(AbstractMISP): """Galaxy class, used to view a galaxy and respective clusters""" + id: str | None + def __init__(self) -> None: super().__init__() - self.GalaxyCluster: List[MISPGalaxyCluster] = [] + self.GalaxyCluster: list[MISPGalaxyCluster] = [] self.name: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Galaxy could be in one of the following formats: {'Galaxy': {}, 'GalaxyCluster': []} {'Galaxy': {'GalaxyCluster': []}} @@ -1488,10 +1433,10 @@ class MISPGalaxy(AbstractMISP): super().from_dict(**kwargs) @property - def clusters(self) -> List[MISPGalaxyCluster]: + def clusters(self) -> list[MISPGalaxyCluster]: return self.GalaxyCluster - def add_galaxy_cluster(self, **kwargs) -> MISPGalaxyCluster: + def add_galaxy_cluster(self, **kwargs) -> MISPGalaxyCluster: # type: ignore[no-untyped-def] """Add a MISP galaxy cluster into a MISPGalaxy. Supports all other parameters supported by MISPGalaxyCluster""" @@ -1503,15 +1448,15 @@ class MISPGalaxy(AbstractMISP): def __repr__(self) -> str: if hasattr(self, 'name'): return '<{self.__class__.__name__}(name={self.name})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPEvent(AbstractMISP): - _fields_for_feed: set = {'uuid', 'info', 'threat_level_id', 'analysis', 'timestamp', - 'publish_timestamp', 'published', 'date', 'extends_uuid'} + _fields_for_feed: set[str] = {'uuid', 'info', 'threat_level_id', 'analysis', 'timestamp', + 'publish_timestamp', 'published', 'date', 'extends_uuid'} - def __init__(self, describe_types: Optional[Dict] = None, strict_validation: bool = False, **kwargs): + def __init__(self, describe_types: dict[str, Any] | None = None, strict_validation: bool = False, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__(**kwargs) self.__schema_file = 'schema.json' if strict_validation else 'schema-lax.json' @@ -1521,29 +1466,32 @@ class MISPEvent(AbstractMISP): self.uuid: str = str(uuid.uuid4()) self.date: date - self.Attribute: List[MISPAttribute] = [] - self.Object: List[MISPObject] = [] - self.RelatedEvent: List[MISPEvent] = [] - self.ShadowAttribute: List[MISPShadowAttribute] = [] + self.Attribute: list[MISPAttribute] = [] + self.Object: list[MISPObject] = [] + self.RelatedEvent: list[MISPEvent] = [] + self.ShadowAttribute: list[MISPShadowAttribute] = [] self.SharingGroup: MISPSharingGroup - self.EventReport: List[MISPEventReport] = [] - self.Tag: List[MISPTag] = [] - self.Galaxy: List[MISPGalaxy] = [] + self.EventReport: list[MISPEventReport] = [] + self.Tag: list[MISPTag] = [] + self.Galaxy: list[MISPGalaxy] = [] - def add_tag(self, tag: Optional[Union[str, MISPTag, dict]] = None, **kwargs) -> MISPTag: + self.publish_timestamp: float | int | datetime + self.timestamp: float | int | datetime + + def add_tag(self, tag: str | MISPTag | dict[str, Any] | None = None, **kwargs) -> MISPTag: # type: ignore[no-untyped-def] return super()._add_tag(tag, **kwargs) @property - def tags(self) -> List[MISPTag]: + def tags(self) -> list[MISPTag]: """Returns a list of tags associated to this Event""" return self.Tag @tags.setter - def tags(self, tags: List[MISPTag]): + def tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" super()._set_tags(tags) - def _set_default(self): + def _set_default(self) -> None: """There are a few keys that could, or need to be set by default for the feed generator""" if not hasattr(self, 'published'): self.published = True @@ -1565,7 +1513,7 @@ class MISPEvent(AbstractMISP): self.threat_level_id = 4 @property - def manifest(self) -> Dict: + def manifest(self) -> dict[str, Any]: required = ['info', 'Orgc'] for r in required: if not hasattr(self, r): @@ -1585,8 +1533,8 @@ class MISPEvent(AbstractMISP): } } - def attributes_hashes(self, algorithm: str = 'sha512') -> List[str]: - to_return: List[str] = [] + def attributes_hashes(self, algorithm: str = 'sha512') -> list[str]: + to_return: list[str] = [] for attribute in self.attributes: to_return += attribute.hash_values(algorithm) for obj in self.objects: @@ -1594,7 +1542,7 @@ class MISPEvent(AbstractMISP): to_return += attribute.hash_values(algorithm) return to_return - def to_feed(self, valid_distributions: List[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution=False, with_local_tags: bool = True, with_event_reports: bool = True) -> Dict: + def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution: bool=False, with_local_tags: bool = True, with_event_reports: bool = True) -> dict[str, Any]: """ Generate a json output for MISP Feed. :param valid_distributions: only makes sense if the distribution key is set; i.e., the event is exported from a MISP instance. @@ -1665,11 +1613,10 @@ class MISPEvent(AbstractMISP): event_report.pop('sharing_group_id', None) to_return['EventReport'].append(event_report.to_dict()) - return {'Event': to_return} @property - def known_types(self) -> List[str]: + def known_types(self) -> list[str]: return self.describe_types['types'] @property @@ -1681,75 +1628,75 @@ class MISPEvent(AbstractMISP): return self.Orgc @orgc.setter - def orgc(self, orgc: MISPOrganisation): + def orgc(self, orgc: MISPOrganisation) -> None: if isinstance(orgc, MISPOrganisation): self.Orgc = orgc else: raise PyMISPError('Orgc must be of type MISPOrganisation.') @property - def attributes(self) -> List[MISPAttribute]: + def attributes(self) -> list[MISPAttribute]: return self.Attribute @attributes.setter - def attributes(self, attributes: List[MISPAttribute]): + def attributes(self, attributes: list[MISPAttribute]) -> None: if all(isinstance(x, MISPAttribute) for x in attributes): self.Attribute = attributes else: raise PyMISPError('All the attributes have to be of type MISPAttribute.') @property - def event_reports(self) -> List[MISPEventReport]: + def event_reports(self) -> list[MISPEventReport]: return self.EventReport @property - def shadow_attributes(self) -> List[MISPShadowAttribute]: + def shadow_attributes(self) -> list[MISPShadowAttribute]: return self.ShadowAttribute @shadow_attributes.setter - def shadow_attributes(self, shadow_attributes: List[MISPShadowAttribute]): + def shadow_attributes(self, shadow_attributes: list[MISPShadowAttribute]) -> None: if all(isinstance(x, MISPShadowAttribute) for x in shadow_attributes): self.ShadowAttribute = shadow_attributes else: raise PyMISPError('All the attributes have to be of type MISPShadowAttribute.') @property - def related_events(self) -> List['MISPEvent']: + def related_events(self) -> list[MISPEvent]: return self.RelatedEvent @property - def galaxies(self) -> List[MISPGalaxy]: + def galaxies(self) -> list[MISPGalaxy]: return self.Galaxy @galaxies.setter - def galaxies(self, galaxies: List[MISPGalaxy]): + def galaxies(self, galaxies: list[MISPGalaxy]) -> None: if all(isinstance(x, MISPGalaxy) for x in galaxies): self.Galaxy = galaxies else: raise PyMISPError('All the attributes have to be of type MISPGalaxy.') @property - def objects(self) -> List[MISPObject]: + def objects(self) -> list[MISPObject]: return self.Object @objects.setter - def objects(self, objects: List[MISPObject]): + def objects(self, objects: list[MISPObject]) -> None: if all(isinstance(x, MISPObject) for x in objects): self.Object = objects else: raise PyMISPError('All the attributes have to be of type MISPObject.') - def load_file(self, event_path: Union[Path, str], validate: bool = False, metadata_only: bool = False): + def load_file(self, event_path: Path | str, validate: bool = False, metadata_only: bool = False) -> None: """Load a JSON dump from a file on the disk""" if not os.path.exists(event_path): raise PyMISPError('Invalid path, unable to load the event.') with open(event_path, 'rb') as f: self.load(f, validate, metadata_only) - def load(self, json_event: Union[IO, str, bytes, dict], validate: bool = False, metadata_only: bool = False): + def load(self, json_event: IO[bytes] | IO[str] | str | bytes | dict[str, Any], validate: bool = False, metadata_only: bool = False) -> None: """Load a JSON dump from a pseudo file or a JSON string""" if isinstance(json_event, (BufferedIOBase, TextIOBase)): - json_event = json_event.read() # type: ignore + json_event = json_event.read() if isinstance(json_event, (str, bytes)): json_event = json.loads(json_event) @@ -1765,21 +1712,17 @@ class MISPEvent(AbstractMISP): event.pop('Object', None) self.from_dict(**event) if validate: - json_schema = self._load_json(self.resources_path / self.__schema_file) - jsonschema.validate({"Event": self.jsonable()}, json_schema) + warnings.warn('The validate parameter is deprecated because PyMISP is more flexible at loading event than the schema') - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any) -> None: if name in ['date']: if isinstance(value, date): pass elif isinstance(value, str): - if sys.version_info >= (3, 7): - try: - # faster - value = date.fromisoformat(value) - except Exception: - value = parse(value).date() - else: + try: + # faster + value = date.fromisoformat(value) + except Exception: value = parse(value).date() elif isinstance(value, (int, float)): value = date.fromtimestamp(value) @@ -1789,7 +1732,7 @@ class MISPEvent(AbstractMISP): raise NewEventError(f'Invalid format for the date: {type(value)} - {value}') super().__setattr__(name, value) - def set_date(self, d: Optional[Union[str, int, float, datetime, date]] = None, ignore_invalid: bool = False): + def set_date(self, d: str | int | float | datetime | date | None = None, ignore_invalid: bool = False) -> None: """Set a date for the event :param d: String, datetime, or date object @@ -1802,7 +1745,7 @@ class MISPEvent(AbstractMISP): else: raise NewEventError(f'Invalid format for the date: {type(d)} - {d}') - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Event' in kwargs: kwargs = kwargs['Event'] # Required value @@ -1861,7 +1804,7 @@ class MISPEvent(AbstractMISP): for rel_event in kwargs.pop('RelatedEvent'): sub_event = MISPEvent() sub_event.load(rel_event) - self.RelatedEvent.append({'Event': sub_event}) + self.RelatedEvent.append({'Event': sub_event}) # type: ignore[arg-type] if kwargs.get('Tag'): [self.add_tag(tag) for tag in kwargs.pop('Tag')] if kwargs.get('Object'): @@ -1876,9 +1819,9 @@ class MISPEvent(AbstractMISP): self.SharingGroup = MISPSharingGroup() self.SharingGroup.from_dict(**kwargs.pop('SharingGroup')) - super(MISPEvent, self).from_dict(**kwargs) + super().from_dict(**kwargs) - def to_dict(self, json_format: bool = False) -> Dict: + def to_dict(self, json_format: bool = False) -> dict[str, Any]: to_return = super().to_dict(json_format) if to_return.get('date'): @@ -1886,17 +1829,17 @@ class MISPEvent(AbstractMISP): self.date = self.date.date() to_return['date'] = self.date.isoformat() if to_return.get('publish_timestamp'): - to_return['publish_timestamp'] = self._datetime_to_timestamp(self.publish_timestamp) + to_return['publish_timestamp'] = str(self._datetime_to_timestamp(self.publish_timestamp)) if to_return.get('sighting_timestamp'): - to_return['sighting_timestamp'] = self._datetime_to_timestamp(self.sighting_timestamp) + to_return['sighting_timestamp'] = str(self._datetime_to_timestamp(self.sighting_timestamp)) return to_return - def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: + def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Alias for add_shadow_attribute""" return self.add_shadow_attribute(shadow_attribute, **kwargs) - def add_shadow_attribute(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: + def add_shadow_attribute(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Add a tag to the attribute (by name or a MISPTag object)""" if isinstance(shadow_attribute, MISPShadowAttribute): misp_shadow_attribute = shadow_attribute @@ -1907,26 +1850,26 @@ class MISPEvent(AbstractMISP): misp_shadow_attribute = MISPShadowAttribute() misp_shadow_attribute.from_dict(**kwargs) else: - raise PyMISPError("The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {}".format(shadow_attribute)) + raise PyMISPError(f"The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): {shadow_attribute}") self.shadow_attributes.append(misp_shadow_attribute) self.edited = True return misp_shadow_attribute - def get_attribute_tag(self, attribute_identifier: str) -> List[MISPTag]: + def get_attribute_tag(self, attribute_identifier: str) -> list[MISPTag]: """Return the tags associated to an attribute or an object attribute. :param attribute_identifier: can be an ID, UUID, or the value. """ - tags: List[MISPTag] = [] + tags: list[MISPTag] = [] for a in self.attributes + [attribute for o in self.objects for attribute in o.attributes]: - if ((hasattr(a, 'id') and a.id == attribute_identifier) + if ((hasattr(a, 'id') and str(a.id) == attribute_identifier) or (hasattr(a, 'uuid') and a.uuid == attribute_identifier) or (hasattr(a, 'value') and attribute_identifier == a.value or (isinstance(a.value, str) and attribute_identifier in a.value.split('|')))): tags += a.tags return tags - def add_attribute_tag(self, tag: Union[MISPTag, str], attribute_identifier: str) -> List[MISPAttribute]: + def add_attribute_tag(self, tag: MISPTag | str, attribute_identifier: str) -> list[MISPAttribute]: """Add a tag to an existing attribute. Raise an Exception if the attribute doesn't exist. :param tag: Tag name as a string, MISPTag instance, or dictionary @@ -1934,7 +1877,7 @@ class MISPEvent(AbstractMISP): """ attributes = [] for a in self.attributes + [attribute for o in self.objects for attribute in o.attributes]: - if ((hasattr(a, 'id') and a.id == attribute_identifier) + if ((hasattr(a, 'id') and str(a.id) == attribute_identifier) or (hasattr(a, 'uuid') and a.uuid == attribute_identifier) or (hasattr(a, 'value') and attribute_identifier == a.value or (isinstance(a.value, str) and attribute_identifier in a.value.split('|')))): @@ -1942,35 +1885,35 @@ class MISPEvent(AbstractMISP): attributes.append(a) if not attributes: - raise PyMISPError('No attribute with identifier {} found.'.format(attribute_identifier)) + raise PyMISPError(f'No attribute with identifier {attribute_identifier} found.') self.edited = True return attributes - def publish(self): + def publish(self) -> None: """Mark the attribute as published""" self.published = True - def unpublish(self): + def unpublish(self) -> None: """Mark the attribute as un-published (set publish flag to false)""" self.published = False - def delete_attribute(self, attribute_id: str): + def delete_attribute(self, attribute_id: str) -> None: """Delete an attribute :param attribute_id: ID or UUID """ for a in self.attributes: - if ((hasattr(a, 'id') and a.id == attribute_id) + if ((hasattr(a, 'id') and str(a.id) == attribute_id) or (hasattr(a, 'uuid') and a.uuid == attribute_id)): a.delete() break else: - raise PyMISPError('No attribute with UUID/ID {} found.'.format(attribute_id)) + raise PyMISPError(f'No attribute with UUID/ID {attribute_id} found.') - def add_attribute(self, type: str, value: Union[str, int, float], **kwargs) -> Union[MISPAttribute, List[MISPAttribute]]: + def add_attribute(self, type: str, value: str | int | float, **kwargs) -> MISPAttribute | list[MISPAttribute]: # type: ignore[no-untyped-def] """Add an attribute. type and value are required but you can pass all other parameters supported by MISPAttribute""" - attr_list: List[MISPAttribute] = [] + attr_list: list[MISPAttribute] = [] if isinstance(value, list): attr_list = [self.add_attribute(type=type, value=a, **kwargs) for a in value] else: @@ -1982,7 +1925,7 @@ class MISPEvent(AbstractMISP): return attr_list return attribute - def add_event_report(self, name: str, content: str, **kwargs) -> MISPEventReport: + def add_event_report(self, name: str, content: str, **kwargs) -> MISPEventReport: # type: ignore[no-untyped-def] """Add an event report. name and value are requred but you can pass all other parameters supported by MISPEventReport""" event_report = MISPEventReport() @@ -1991,7 +1934,7 @@ class MISPEvent(AbstractMISP): self.edited = True return event_report - def add_galaxy(self, galaxy: Union[MISPGalaxy, dict, None] = None, **kwargs) -> MISPGalaxy: + def add_galaxy(self, galaxy: MISPGalaxy | dict[str, Any] | None = None, **kwargs) -> MISPGalaxy: # type: ignore[no-untyped-def] """Add a galaxy and sub-clusters into an event, either by passing a MISPGalaxy or a dictionary. Supports all other parameters supported by MISPGalaxy""" @@ -2009,14 +1952,14 @@ class MISPEvent(AbstractMISP): self.galaxies.append(misp_galaxy) return misp_galaxy - def get_object_by_id(self, object_id: Union[str, int]) -> MISPObject: + def get_object_by_id(self, object_id: str | int) -> MISPObject: """Get an object by ID :param object_id: the ID is the one set by the server when creating the new object""" for obj in self.objects: if hasattr(obj, 'id') and int(obj.id) == int(object_id): return obj - raise InvalidMISPObject('Object with {} does not exist in this event'.format(object_id)) + raise InvalidMISPObject(f'Object with {object_id} does not exist in this event') def get_object_by_uuid(self, object_uuid: str) -> MISPObject: """Get an object by UUID @@ -2025,9 +1968,9 @@ class MISPEvent(AbstractMISP): for obj in self.objects: if hasattr(obj, 'uuid') and obj.uuid == object_uuid: return obj - raise InvalidMISPObject('Object with {} does not exist in this event'.format(object_uuid)) + raise InvalidMISPObject(f'Object with {object_uuid} does not exist in this event') - def get_objects_by_name(self, object_name: str) -> List[MISPObject]: + def get_objects_by_name(self, object_name: str) -> list[MISPObject]: """Get objects by name :param object_name: name is set by the server when creating the new object""" @@ -2037,7 +1980,7 @@ class MISPEvent(AbstractMISP): objects.append(obj) return objects - def add_object(self, obj: Union[MISPObject, dict, None] = None, **kwargs) -> MISPObject: + def add_object(self, obj: MISPObject | dict[str, Any] | None = None, **kwargs) -> MISPObject: # type: ignore[no-untyped-def] """Add an object to the Event, either by passing a MISPObject, or a dictionary""" if isinstance(obj, MISPObject): misp_obj = obj @@ -2058,20 +2001,20 @@ class MISPEvent(AbstractMISP): self.edited = True return misp_obj - def delete_object(self, object_id: str): + def delete_object(self, object_id: str) -> None: """Delete an object :param object_id: ID or UUID """ for o in self.objects: - if ((hasattr(o, 'id') and o.id == object_id) + if ((hasattr(o, 'id') and object_id.isdigit() and int(o.id) == int(object_id)) or (hasattr(o, 'uuid') and o.uuid == object_id)): o.delete() break else: - raise PyMISPError('No object with UUID/ID {} found.'.format(object_id)) + raise PyMISPError(f'No object with UUID/ID {object_id} found.') - def run_expansions(self): + def run_expansions(self) -> None: for index, attribute in enumerate(self.attributes): if 'expand' not in attribute: continue @@ -2081,7 +2024,7 @@ class MISPEvent(AbstractMISP): try: from .tools import make_binary_objects except ImportError as e: - logger.info('Unable to load make_binary_objects: {}'.format(e)) + logger.info(f'Unable to load make_binary_objects: {e}') continue file_object, bin_type_object, bin_section_objects = make_binary_objects(pseudofile=attribute.malware_binary, filename=attribute.malware_filename) self.add_object(file_object) @@ -2092,107 +2035,52 @@ class MISPEvent(AbstractMISP): self.add_object(bin_section_object) self.attributes.pop(index) else: - logger.warning('No expansions for this data type ({}). Open an issue if needed.'.format(attribute.type)) + logger.warning(f'No expansions for this data type ({attribute.type}). Open an issue if needed.') def __repr__(self) -> str: if hasattr(self, 'info'): return '<{self.__class__.__name__}(info={self.info})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) - - def _serialize(self): # pragma: no cover - return '{date}{threat_level_id}{info}{uuid}{analysis}{timestamp}'.format( - date=self.date, threat_level_id=self.threat_level_id, info=self.info, - uuid=self.uuid, analysis=self.analysis, timestamp=self.timestamp).encode() - - def _serialize_sigs(self): # pragma: no cover - # Not used - all_sigs = self.sig - for a in self.attributes: - all_sigs += a.sig - return all_sigs.encode() - - def sign(self, gpg_uid, passphrase=None): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - to_sign = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - c.signers = keys[:1] - if passphrase: - c.set_passphrase_cb(lambda *args: passphrase) - signed, _ = c.sign(to_sign, mode=mode.DETACH) - self.sig = base64.b64encode(signed).decode() - for a in self.attributes: - a.sign(gpg_uid, passphrase) - to_sign_global = self._serialize_sigs() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - c.signers = keys[:1] - if passphrase: - c.set_passphrase_cb(lambda *args: passphrase) - signed, _ = c.sign(to_sign_global, mode=mode.DETACH) - self.global_sig = base64.b64encode(signed).decode() - - def verify(self, gpg_uid): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - to_return = {} - signed_data = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - try: - c.verify(signed_data, signature=base64.b64decode(self.sig), verify=keys[:1]) - to_return[self.uuid] = True - except Exception: - to_return[self.uuid] = False - for a in self.attributes: - to_return.update(a.verify(gpg_uid)) - to_verify_global = self._serialize_sigs() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - try: - c.verify(to_verify_global, signature=base64.b64decode(self.global_sig), verify=keys[:1]) - to_return['global'] = True - except Exception: - to_return['global'] = False - return to_return + return f'<{self.__class__.__name__}(NotInitialized)' class MISPObjectTemplate(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'ObjectTemplate' in kwargs: kwargs = kwargs['ObjectTemplate'] super().from_dict(**kwargs) def __repr__(self) -> str: - return '<{self.__class__.__name__}(self.name)'.format(self=self) + return f'<{self.__class__.__name__}(self.name)' class MISPUser(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + authkey: str + + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.email: str + self.password: str | None - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'User' in kwargs: kwargs = kwargs['User'] super().from_dict(**kwargs) - if hasattr(self, 'password') and set(self.password) == set(['*']): + if hasattr(self, 'password') and self.password and set(self.password) == {'*', }: self.password = None def __repr__(self) -> str: if hasattr(self, 'email'): return '<{self.__class__.__name__}(email={self.email})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPFeed(AbstractMISP): - def from_dict(self, **kwargs): + settings: str + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Feed' in kwargs: kwargs = kwargs['Feed'] super().from_dict(**kwargs) @@ -2200,13 +2088,13 @@ class MISPFeed(AbstractMISP): try: self.settings = json.loads(self.settings) except json.decoder.JSONDecodeError as e: - logger.error("Failed to parse feed settings: {}".format(self.settings)) + logger.error(f"Failed to parse feed settings: {self.settings}") raise e class MISPWarninglist(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Warninglist' in kwargs: kwargs = kwargs['Warninglist'] super().from_dict(**kwargs) @@ -2217,18 +2105,18 @@ class MISPTaxonomy(AbstractMISP): enabled: bool namespace: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Taxonomy' in kwargs: kwargs = kwargs['Taxonomy'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(namespace={self.namespace})>' class MISPNoticelist(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Noticelist' in kwargs: kwargs = kwargs['Noticelist'] super().from_dict(**kwargs) @@ -2236,7 +2124,7 @@ class MISPNoticelist(AbstractMISP): class MISPCorrelationExclusion(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'CorrelationExclusion' in kwargs: kwargs = kwargs['CorrelationExclusion'] super().from_dict(**kwargs) @@ -2244,12 +2132,12 @@ class MISPCorrelationExclusion(AbstractMISP): class MISPRole(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.perm_admin: int self.perm_site_admin: int - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Role' in kwargs: kwargs = kwargs['Role'] super().from_dict(**kwargs) @@ -2257,7 +2145,7 @@ class MISPRole(AbstractMISP): class MISPServer(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Server' in kwargs: kwargs = kwargs['Server'] super().from_dict(**kwargs) @@ -2265,13 +2153,13 @@ class MISPServer(AbstractMISP): class MISPLog(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.model: str self.action: str self.title: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Log' in kwargs: kwargs = kwargs['Log'] super().from_dict(**kwargs) @@ -2282,13 +2170,13 @@ class MISPLog(AbstractMISP): class MISPEventDelegation(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.org_id: int self.requester_org_id: int self.event_id: int - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventDelegation' in kwargs: kwargs = kwargs['EventDelegation'] super().from_dict(**kwargs) @@ -2299,15 +2187,15 @@ class MISPEventDelegation(AbstractMISP): class MISPObjectAttribute(MISPAttribute): - _fields_for_feed: set = {'uuid', 'value', 'category', 'type', 'comment', 'data', - 'deleted', 'timestamp', 'to_ids', 'disable_correlation', - 'first_seen', 'last_seen', 'object_relation'} + _fields_for_feed: set[str] = {'uuid', 'value', 'category', 'type', 'comment', 'data', + 'deleted', 'timestamp', 'to_ids', 'disable_correlation', + 'first_seen', 'last_seen', 'object_relation'} - def __init__(self, definition): + def __init__(self, definition: dict[str, Any]) -> None: super().__init__() self._definition = definition - def from_dict(self, object_relation: str, value: Union[str, int, float], **kwargs): # type: ignore + def from_dict(self, object_relation: str, value: str | int | float, **kwargs): # type: ignore # NOTE: Signature of "from_dict" incompatible with supertype "MISPAttribute" self.object_relation = object_relation self.value = value @@ -2334,90 +2222,99 @@ class MISPObjectAttribute(MISPAttribute): raise NewAttributeError("The type of the attribute is required. Is the object template missing?") super().from_dict(**{**self, **kwargs}) - def __repr__(self): + def __repr__(self) -> str: if hasattr(self, 'value'): return '<{self.__class__.__name__}(object_relation={self.object_relation}, value={self.value})'.format(self=self) - return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) + return f'<{self.__class__.__name__}(NotInitialized)' class MISPCommunity(AbstractMISP): - def from_dict(self, **kwargs): + def __init__(self, **kwargs: dict[str, Any]) -> None: + super().__init__(**kwargs) + self.name: str + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Community' in kwargs: kwargs = kwargs['Community'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(name={self.name}, uuid={self.uuid})' class MISPUserSetting(AbstractMISP): - def from_dict(self, **kwargs): + def __init__(self, **kwargs: dict[str, Any]) -> None: + super().__init__(**kwargs) + self.setting: str + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'UserSetting' in kwargs: kwargs = kwargs['UserSetting'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(name={self.setting}' class MISPInbox(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) - self.data: Dict + self.data: dict[str, Any] + self.type: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Inbox' in kwargs: kwargs = kwargs['Inbox'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(name={self.type})>' class MISPEventBlocklist(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.event_uuid: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventBlocklist' in kwargs: kwargs = kwargs['EventBlocklist'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(event_uuid={self.event_uuid}' class MISPOrganisationBlocklist(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.org_uuid: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'OrgBlocklist' in kwargs: kwargs = kwargs['OrgBlocklist'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(org_uuid={self.org_uuid}' class MISPDecayingModel(AbstractMISP): - def __init__(self, **kwargs: Dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.uuid: str self.id: int - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'DecayingModel' in kwargs: kwargs = kwargs['DecayingModel'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(uuid={self.uuid})>' diff --git a/pymisp/tools/__init__.py b/pymisp/tools/__init__.py index cd5c1c9..45907b3 100644 --- a/pymisp/tools/__init__.py +++ b/pymisp/tools/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .vtreportobject import VTReportObject # noqa from .neo4j import Neo4j # noqa from .fileobject import FileObject # noqa @@ -41,3 +43,13 @@ try: except ImportError: # Requires lief, optional [fileobjects] pass + +__all__ = ['VTReportObject', 'Neo4j', 'FileObject', 'make_binary_objects', + 'AbstractMISPObjectGenerator', 'GenericObjectGenerator', + 'load_openioc', 'load_openioc_file', 'SBSignatureObject', + 'Fail2BanObject', 'DomainIPObject', 'ASNObject', 'GeolocationObject', + 'GitVulnFinderObject', 'VehicleObject', 'CSVLoader', + 'SSHAuthorizedKeysObject', 'feed_meta_generator', 'update_objects', + 'EMailObject', 'URLObject', 'PEObject', 'PESectionObject', 'ELFObject', + 'ELFSectionObject', 'MachOObject', 'MachOSectionObject' + ] diff --git a/pymisp/tools/_psl_faup.py b/pymisp/tools/_psl_faup.py index 18365a0..388fed4 100644 --- a/pymisp/tools/_psl_faup.py +++ b/pymisp/tools/_psl_faup.py @@ -1,36 +1,37 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations import ipaddress import socket import idna from publicsuffixlist import PublicSuffixList # type: ignore -from urllib.parse import urlparse, urlunparse +from urllib.parse import urlparse, urlunparse, ParseResult class UrlNotDecoded(Exception): pass -class PSLFaup(object): +class PSLFaup: """ Fake Faup Python Library using PSL for Windows support """ - def __init__(self): + def __init__(self) -> None: self.decoded = False self.psl = PublicSuffixList() - self._url = None - self._retval = {} - self.ip_as_host = False + self._url: ParseResult | None = None + self._retval: dict[str, str | int | None | bytes] = {} + self.ip_as_host = '' - def _clear(self): + def _clear(self) -> None: self.decoded = False self._url = None self._retval = {} - self.ip_as_host = False + self.ip_as_host = '' - def decode(self, url) -> None: + def decode(self, url: str) -> None: """ This function creates a dict of all the url fields. :param url: The URL to normalize @@ -42,10 +43,15 @@ class PSLFaup(object): url = '//' + url self._url = urlparse(url) - self.ip_as_host = False + if self._url is None: + raise UrlNotDecoded("Unable to parse URL") + + self.ip_as_host = '' + if self._url.hostname is None: + raise UrlNotDecoded("Unable to parse URL") hostname = _ensure_str(self._url.hostname) try: - ipv4_bytes = socket.inet_aton(_ensure_str(hostname)) + ipv4_bytes = socket.inet_aton(hostname) ipv4 = ipaddress.IPv4Address(ipv4_bytes) self.ip_as_host = ipv4.compressed except (OSError, ValueError): @@ -60,61 +66,70 @@ class PSLFaup(object): self._retval = {} @property - def url(self): - if not self.decoded: + def url(self) -> bytes | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") - netloc = self.get_host() + ('' if self.get_port() is None else ':{}'.format(self.get_port())) - return _ensure_bytes( - urlunparse( - (self.get_scheme(), netloc, self.get_resource_path(), - '', self.get_query_string(), self.get_fragment(),) + if host := self.get_host(): + netloc = host + ('' if self.get_port() is None else f':{self.get_port()}') + return _ensure_bytes( + urlunparse( + (self.get_scheme(), netloc, self.get_resource_path(), + '', self.get_query_string(), self.get_fragment(),) + ) ) - ) + return None - def get_scheme(self): + def get_scheme(self) -> str: """ Get the scheme of the url given in the decode function :returns: The URL scheme """ - if not self.decoded: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") - return _ensure_str(self._url.scheme) + return _ensure_str(self._url.scheme if self._url.scheme else '') - def get_credential(self): - if not self.decoded: + def get_credential(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") - if self._url.password: + if self._url.username and self._url.password: return _ensure_str(self._url.username) + ':' + _ensure_str(self._url.password) if self._url.username: return _ensure_str(self._url.username) + return None - def get_subdomain(self): - if not self.decoded: + def get_subdomain(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") if self.get_host() is not None and not self.ip_as_host: - if self.get_domain() in self.get_host(): - return self.get_host().rsplit(self.get_domain(), 1)[0].rstrip('.') or None + domain = self.get_domain() + host = self.get_host() + if domain and host and domain in host: + return host.rsplit(domain, 1)[0].rstrip('.') or None + return None - def get_domain(self): - if not self.decoded: + def get_domain(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") if self.get_host() is not None and not self.ip_as_host: return self.psl.privatesuffix(self.get_host()) + return None - def get_domain_without_tld(self): - if not self.decoded: + def get_domain_without_tld(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") if self.get_tld() is not None and not self.ip_as_host: - return self.get_domain().rsplit(self.get_tld(), 1)[0].rstrip('.') + if domain := self.get_domain(): + return domain.rsplit(self.get_tld(), 1)[0].rstrip('.') + return None - def get_host(self): - if not self.decoded: + def get_host(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") if self._url.hostname is None: @@ -124,45 +139,48 @@ class PSLFaup(object): else: return _ensure_str(idna.encode(self._url.hostname, uts46=True)) - def get_unicode_host(self): - if not self.decoded: + def get_unicode_host(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") if not self.ip_as_host: - return idna.decode(self.get_host(), uts46=True) + if host := self.get_host(): + return idna.decode(host, uts46=True) + return None - def get_tld(self): - if not self.decoded: + def get_tld(self) -> str | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") if self.get_host() is not None and not self.ip_as_host: return self.psl.publicsuffix(self.get_host()) + return None - def get_port(self): - if not self.decoded: + def get_port(self) -> int | None: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") return self._url.port - def get_resource_path(self): - if not self.decoded: + def get_resource_path(self) -> str: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") return _ensure_str(self._url.path) - def get_query_string(self): - if not self.decoded: + def get_query_string(self) -> str: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") return _ensure_str(self._url.query) - def get_fragment(self): - if not self.decoded: + def get_fragment(self) -> str: + if not self.decoded or not self._url: raise UrlNotDecoded("You must call faup.decode() first") return _ensure_str(self._url.fragment) - def get(self): + def get(self) -> dict[str, str | int | None | bytes]: self._retval["scheme"] = self.get_scheme() self._retval["tld"] = self.get_tld() self._retval["domain"] = self.get_domain() @@ -177,14 +195,14 @@ class PSLFaup(object): return self._retval -def _ensure_bytes(binary) -> bytes: +def _ensure_bytes(binary: str | bytes) -> bytes: if isinstance(binary, bytes): return binary else: return binary.encode('utf-8') -def _ensure_str(string) -> str: +def _ensure_str(string: str | bytes) -> str: if isinstance(string, str): return string else: diff --git a/pymisp/tools/abstractgenerator.py b/pymisp/tools/abstractgenerator.py index 582356e..a3ca26f 100644 --- a/pymisp/tools/abstractgenerator.py +++ b/pymisp/tools/abstractgenerator.py @@ -1,16 +1,19 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +from datetime import datetime, date +from dateutil.parser import parse + +from typing import Any from .. import MISPObject from ..exceptions import InvalidMISPObject -from datetime import datetime, date -from dateutil.parser import parse -from typing import Union, Optional class AbstractMISPObjectGenerator(MISPObject): - def _detect_epoch(self, timestamp: Union[str, int, float]) -> bool: + def _detect_epoch(self, timestamp: str | int | float) -> bool: try: tmp = float(timestamp) if tmp < 30000000: @@ -21,7 +24,7 @@ class AbstractMISPObjectGenerator(MISPObject): except ValueError: return False - def _sanitize_timestamp(self, timestamp: Optional[Union[datetime, date, dict, str, int, float]] = None) -> datetime: + def _sanitize_timestamp(self, timestamp: datetime | date | dict[str, Any] | str | int | float | None = None) -> datetime: if not timestamp: return datetime.now() @@ -42,9 +45,9 @@ class AbstractMISPObjectGenerator(MISPObject): else: raise Exception(f'Unable to convert {timestamp} to a datetime.') - def generate_attributes(self): + def generate_attributes(self) -> None: """Contains the logic where all the values of the object are gathered""" - if hasattr(self, '_parameters'): + if hasattr(self, '_parameters') and self._definition is not None: for object_relation in self._definition['attributes']: value = self._parameters.pop(object_relation, None) if not value: diff --git a/pymisp/tools/asnobject.py b/pymisp/tools/asnobject.py index 909d06b..685da7a 100644 --- a/pymisp/tools/asnobject.py +++ b/pymisp/tools/asnobject.py @@ -1,20 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from typing import Any from .abstractgenerator import AbstractMISPObjectGenerator -import logging logger = logging.getLogger('pymisp') class ASNObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('asn', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: first = self._sanitize_timestamp(self._parameters.pop('first-seen', None)) self._parameters['first-seen'] = first last = self._sanitize_timestamp(self._parameters.pop('last-seen', None)) diff --git a/pymisp/tools/create_misp_object.py b/pymisp/tools/create_misp_object.py index 60a0ae8..0ccacf9 100644 --- a/pymisp/tools/create_misp_object.py +++ b/pymisp/tools/create_misp_object.py @@ -1,23 +1,25 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging from io import BytesIO +from typing import Any, TYPE_CHECKING -from . import FileObject from ..exceptions import MISPObjectException -import logging -from typing import Optional - logger = logging.getLogger('pymisp') try: import lief + import lief.logging lief.logging.disable() HAS_LIEF = True from .peobject import make_pe_objects from .elfobject import make_elf_objects from .machoobject import make_macho_objects + from . import FileObject except AttributeError: HAS_LIEF = False @@ -26,19 +28,29 @@ except AttributeError: except ImportError: HAS_LIEF = False +if TYPE_CHECKING: + from . import PEObject, ELFObject, MachOObject, PESectionObject, ELFSectionObject, MachOSectionObject + class FileTypeNotImplemented(MISPObjectException): pass -def make_binary_objects(filepath: Optional[str] = None, pseudofile: Optional[BytesIO] = None, filename: Optional[str] = None, standalone: bool = True, default_attributes_parameters: dict = {}): +def make_binary_objects(filepath: str | None = None, + pseudofile: BytesIO | bytes | None = None, + filename: str | None = None, + standalone: bool = True, + default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, PEObject | ELFObject | MachOObject | None, list[PESectionObject] | list[ELFSectionObject] | list[MachOSectionObject]]: misp_file = FileObject(filepath=filepath, pseudofile=pseudofile, filename=filename, standalone=standalone, default_attributes_parameters=default_attributes_parameters) - if HAS_LIEF and (filepath or (pseudofile and filename)): + if HAS_LIEF and (filepath or pseudofile): if filepath: lief_parsed = lief.parse(filepath=filepath) - elif pseudofile and filename: - lief_parsed = lief.parse(raw=pseudofile.getvalue(), name=filename) + elif pseudofile: + if isinstance(pseudofile, bytes): + lief_parsed = lief.parse(raw=pseudofile) + else: # BytesIO + lief_parsed = lief.parse(obj=pseudofile) else: logger.critical('You need either a filepath, or a pseudofile and a filename.') lief_parsed = None diff --git a/pymisp/tools/csvloader.py b/pymisp/tools/csvloader.py index c5880ac..e0452ec 100644 --- a/pymisp/tools/csvloader.py +++ b/pymisp/tools/csvloader.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations from pathlib import Path -from typing import List, Optional import csv from pymisp import MISPObject @@ -10,8 +10,9 @@ from pymisp import MISPObject class CSVLoader(): - def __init__(self, template_name: str, csv_path: Path, fieldnames: Optional[List[str]] = None, has_fieldnames=False, - delimiter: str = ',', quotechar: str = '"'): + def __init__(self, template_name: str, csv_path: Path, + fieldnames: list[str] | None = None, has_fieldnames: bool=False, + delimiter: str = ',', quotechar: str = '"') -> None: self.template_name = template_name self.delimiter = delimiter self.quotechar = quotechar @@ -25,7 +26,7 @@ class CSVLoader(): else: self.has_fieldnames = has_fieldnames - def load(self): + def load(self) -> list[MISPObject]: objects = [] @@ -43,7 +44,7 @@ class CSVLoader(): # Check if the CSV file has a header, and if it matches with the object template tmp_object = MISPObject(self.template_name) - if not tmp_object._definition['attributes']: + if not tmp_object._definition or not tmp_object._definition['attributes']: raise Exception(f'Unable to find the object template ({self.template_name}), impossible to create objects.') allowed_fieldnames = list(tmp_object._definition['attributes'].keys()) for fieldname in self.fieldnames: diff --git a/pymisp/tools/domainipobject.py b/pymisp/tools/domainipobject.py index 2fe9a3e..2269342 100644 --- a/pymisp/tools/domainipobject.py +++ b/pymisp/tools/domainipobject.py @@ -1,20 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from typing import Any from .abstractgenerator import AbstractMISPObjectGenerator -import logging logger = logging.getLogger('pymisp') class DomainIPObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('domain-ip', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: first = self._sanitize_timestamp(self._parameters.pop('first-seen', None)) self._parameters['first-seen'] = first last = self._sanitize_timestamp(self._parameters.pop('last-seen', None)) diff --git a/pymisp/tools/elfobject.py b/pymisp/tools/elfobject.py index a26734b..c0b6ceb 100644 --- a/pymisp/tools/elfobject.py +++ b/pymisp/tools/elfobject.py @@ -1,14 +1,17 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- +from __future__ import annotations + +import logging + +from hashlib import md5, sha1, sha256, sha512 +from io import BytesIO +from pathlib import Path +from typing import Any + +from . import FileObject from .abstractgenerator import AbstractMISPObjectGenerator from ..exceptions import InvalidMISPObject -from io import BytesIO -from hashlib import md5, sha1, sha256, sha512 -import logging -from typing import Union, Optional -from pathlib import Path -from . import FileObject import lief @@ -21,7 +24,10 @@ except ImportError: logger = logging.getLogger('pymisp') -def make_elf_objects(lief_parsed: lief.ELF.Binary, misp_file: FileObject, standalone: bool = True, default_attributes_parameters: dict = {}): +def make_elf_objects(lief_parsed: lief.ELF.Binary, + misp_file: FileObject, + standalone: bool = True, + default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, ELFObject, list[ELFSectionObject]]: elf_object = ELFObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters) misp_file.add_reference(elf_object.uuid, 'includes', 'ELF indicators') elf_sections = [] @@ -32,29 +38,39 @@ def make_elf_objects(lief_parsed: lief.ELF.Binary, misp_file: FileObject, standa class ELFObject(AbstractMISPObjectGenerator): - def __init__(self, parsed: Optional[lief.ELF.Binary] = None, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, **kwargs): + __elf: lief.ELF.Binary + + def __init__(self, parsed: lief.ELF.Binary | None = None, # type: ignore[no-untyped-def] + filepath: Path | str | None = None, + pseudofile: BytesIO | bytes | list[int] | None = None, **kwargs) -> None: """Creates an ELF object, with lief""" super().__init__('elf', **kwargs) if not HAS_PYDEEP: logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]") if pseudofile: if isinstance(pseudofile, BytesIO): - self.__elf = lief.ELF.parse(io=pseudofile) + e = lief.ELF.parse(obj=pseudofile) elif isinstance(pseudofile, bytes): - self.__elf = lief.ELF.parse(raw=pseudofile) + e = lief.ELF.parse(raw=list(pseudofile)) + elif isinstance(pseudofile, list): + e = lief.ELF.parse(raw=pseudofile) else: - raise InvalidMISPObject('Pseudo file can be BytesIO or bytes got {}'.format(type(pseudofile))) + raise InvalidMISPObject(f'Pseudo file can be BytesIO or bytes got {type(pseudofile)}') + if not e: + raise InvalidMISPObject('Unable to parse pseudofile') + self.__elf = e elif filepath: - self.__elf = lief.ELF.parse(filepath) + if e := lief.ELF.parse(filepath): + self.__elf = e elif parsed: # Got an already parsed blob if isinstance(parsed, lief.ELF.Binary): self.__elf = parsed else: - raise InvalidMISPObject('Not a lief.ELF.Binary: {}'.format(type(parsed))) + raise InvalidMISPObject(f'Not a lief.ELF.Binary: {type(parsed)}') self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: # General information self.add_attribute('type', value=str(self.__elf.header.file_type).split('.')[1]) self.add_attribute('entrypoint-address', value=self.__elf.entrypoint) @@ -68,7 +84,7 @@ class ELFObject(AbstractMISPObjectGenerator): if not section.name: continue s = ELFSectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters) - self.add_reference(s.uuid, 'includes', 'Section {} of ELF'.format(pos)) + self.add_reference(s.uuid, 'includes', f'Section {pos} of ELF') pos += 1 self.sections.append(s) self.add_attribute('number-sections', value=len(self.sections)) @@ -76,22 +92,22 @@ class ELFObject(AbstractMISPObjectGenerator): class ELFSectionObject(AbstractMISPObjectGenerator): - def __init__(self, section: lief.ELF.Section, **kwargs): + def __init__(self, section: lief.ELF.Section, **kwargs) -> None: # type: ignore[no-untyped-def] """Creates an ELF Section object. Object generated by ELFObject.""" # Python3 way # super().__init__('pe-section') - super(ELFSectionObject, self).__init__('elf-section', **kwargs) + super().__init__('elf-section', **kwargs) self.__section = section self.__data = bytes(self.__section.content) self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('name', value=self.__section.name) self.add_attribute('type', value=str(self.__section.type).split('.')[1]) for flag in self.__section.flags_list: self.add_attribute('flag', value=str(flag).split('.')[1]) - size = self.add_attribute('size-in-bytes', value=self.__section.size) - if int(size.value) > 0: + self.add_attribute('size-in-bytes', value=self.__section.size) + if int(self.__section.size) > 0: self.add_attribute('entropy', value=self.__section.entropy) self.add_attribute('md5', value=md5(self.__data).hexdigest()) self.add_attribute('sha1', value=sha1(self.__data).hexdigest()) diff --git a/pymisp/tools/emailobject.py b/pymisp/tools/emailobject.py index 8093872..4042f6e 100644 --- a/pymisp/tools/emailobject.py +++ b/pymisp/tools/emailobject.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations import re import logging @@ -9,16 +10,17 @@ from email import policy, message_from_bytes from email.message import EmailMessage from io import BytesIO from pathlib import Path -from typing import Union, List, Tuple, Dict, cast, Any, Optional +from typing import cast, Any from extract_msg import openMsg from extract_msg.msg_classes import MessageBase +from extract_msg.attachments import AttachmentBase, SignedAttachment from extract_msg.properties import FixedLengthProp from RTFDE.exceptions import MalformedEncapsulatedRtf, NotEncapsulatedRtf # type: ignore from RTFDE.deencapsulate import DeEncapsulator # type: ignore from oletools.common.codepages import codepage2codec # type: ignore -from ..exceptions import InvalidMISPObject, PyMISPNotImplementedYet, MISPObjectException, NewAttributeError +from ..exceptions import InvalidMISPObject, MISPObjectException, NewAttributeError from .abstractgenerator import AbstractMISPObjectGenerator logger = logging.getLogger('pymisp') @@ -29,15 +31,14 @@ class MISPMsgConverstionError(MISPObjectException): class EMailObject(AbstractMISPObjectGenerator): - def __init__(self, filepath: Optional[Union[Path, str]]=None, pseudofile: Optional[BytesIO]=None, - attach_original_email: bool = True, **kwargs): + def __init__(self, filepath: Path | str | None=None, pseudofile: BytesIO | bytes | None=None, # type: ignore[no-untyped-def] + attach_original_email: bool = True, **kwargs) -> None: super().__init__('email', **kwargs) self.attach_original_email = attach_original_email - self.encapsulated_body: Union[str, None] = None - self.eml_from_msg: Union[bool, None] = None - self.raw_emails: Dict[str, Union[BytesIO, None]] = {'msg': None, - 'eml': None} + self.encapsulated_body: str | None = None + self.eml_from_msg: bool | None = None + self.raw_emails: dict[str, BytesIO | None] = {'msg': None, 'eml': None} self.__pseudofile = self.create_pseudofile(filepath, pseudofile) self.email = self.parse_email() @@ -66,7 +67,7 @@ class EMailObject(AbstractMISPObjectGenerator): return message except ValueError as _e: # Exception logger.debug("Email not in .msg format or is a corrupted .msg. Attempting to decode email from other formats.") - logger.debug("Error: {} ".format(_e)) + logger.debug(f"Error: {_e} ") try: if content_in_bytes[:3] == b'\xef\xbb\xbf': # utf-8-sig byte-order mark (BOM) eml_bytes = content_in_bytes.decode("utf_8_sig").encode("utf-8") @@ -78,11 +79,11 @@ class EMailObject(AbstractMISPObjectGenerator): return eml except UnicodeDecodeError: pass - raise PyMISPNotImplementedYet("EmailObject does not know how to decode data passed to it. Object may not be an email. If this is an email please submit it as an issue to PyMISP so we can add support.") + raise InvalidMISPObject("EmailObject does not know how to decode data passed to it. Object may not be an email. If this is an email please submit it as an issue to PyMISP so we can add support.") @staticmethod - def create_pseudofile(filepath: Optional[Union[Path, str]] = None, - pseudofile: Optional[BytesIO] = None) -> BytesIO: + def create_pseudofile(filepath: Path | str | None = None, + pseudofile: BytesIO | bytes | None = None) -> BytesIO: """Creates a pseudofile using directly passed data or data loaded from file path. """ if filepath: @@ -90,6 +91,8 @@ class EMailObject(AbstractMISPObjectGenerator): return BytesIO(f.read()) elif pseudofile and isinstance(pseudofile, BytesIO): return pseudofile + elif pseudofile and isinstance(pseudofile, bytes): + return BytesIO(pseudofile) else: raise InvalidMISPObject('File buffer (BytesIO) or a path is required.') @@ -102,7 +105,7 @@ class EMailObject(AbstractMISPObjectGenerator): eml = self._build_eml(message, body, attachments) return eml - def _extract_msg_objects(self, msg_obj: MessageBase) -> Tuple[EmailMessage, Dict, List[Any]]: + def _extract_msg_objects(self, msg_obj: MessageBase) -> tuple[EmailMessage, dict[str, Any], list[AttachmentBase] | list[SignedAttachment]]: """Extracts email objects needed to construct an eml from a msg.""" message: EmailMessage = email.message_from_string(msg_obj.header.as_string(), policy=policy.default) # type: ignore body = {} @@ -150,17 +153,16 @@ class EMailObject(AbstractMISPObjectGenerator): attachments = msg_obj.attachments return message, body, attachments - def _build_eml(self, message: EmailMessage, body: dict, attachments: list) -> EmailMessage: + def _build_eml(self, message: EmailMessage, body: dict[str, Any], attachments: list[Any]) -> EmailMessage: """Constructs an eml file from objects extracted from a msg.""" # Order the body objects by increasing complexity and toss any missing objects - body_objects: List[dict] = [body.get('text', {}), - body.get('html', {}), - body.get('rtf', {})] - body_objects = [i for i in body_objects if i != {}] + body_objects: list[dict[str, Any]] = [i for i in [body.get('text'), + body.get('html'), + body.get('rtf')] if i is not None] # If this a non-multipart email then we only need to attach the payload if message.get_content_maintype() != 'multipart': for _body in body_objects: - if "text/{0}".format(_body['subtype']) == message.get_content_type(): + if "text/{}".format(_body['subtype']) == message.get_content_type(): message.set_content(**_body) return message raise MISPMsgConverstionError("Unable to find appropriate eml payload in message body.") @@ -172,8 +174,8 @@ class EMailObject(AbstractMISPObjectGenerator): if isinstance(body.get('html', None), dict): _html = body.get('html', {}).get('obj') for attch in attachments: - if _html.find("cid:{0}".format(attch.cid)) != -1: - _content_type = attch._getStringStream('__substg1.0_370E') + if _html.find(f"cid:{attch.cid}") != -1: + _content_type = attch.getStringStream('__substg1.0_370E') maintype, subtype = _content_type.split("/", 1) related_content[attch.cid] = (attch, {'obj': attch.data, @@ -210,7 +212,7 @@ class EMailObject(AbstractMISPObjectGenerator): message.add_alternative(**mime_dict) for attch in attachments: # Add attachments at the end. if attch.cid not in related_content.keys(): - _content_type = attch._getStringStream('__substg1.0_370E') + _content_type = attch.getStringStream('__substg1.0_370E') maintype, subtype = _content_type.split("/", 1) message.add_attachment(attch.data, maintype=maintype, @@ -224,7 +226,7 @@ class EMailObject(AbstractMISPObjectGenerator): return message @staticmethod - def _update_content_disp_properties(msg_attch, eml_attch): + def _update_content_disp_properties(msg_attch: AttachmentBase, eml_attch: EmailMessage) -> None: """Set Content-Disposition params on binary eml objects You currently have to set non-filename content-disp params by hand in python. @@ -234,14 +236,14 @@ class EMailObject(AbstractMISPObjectGenerator): for num, name in attch_cont_disp_props.items(): try: eml_attch.set_param(name, - email.utils.format_datetime(msg_attch.props[num].value), + email.utils.format_datetime(msg_attch.props.getValue(num)), header='Content-Disposition') except KeyError: # It's fine if they don't have those values pass @property - def attachments(self) -> List[Tuple[Optional[str], BytesIO]]: + def attachments(self) -> list[tuple[str | None, BytesIO]]: to_return = [] try: for attachment in self.email.iter_attachments(): @@ -255,7 +257,7 @@ class EMailObject(AbstractMISPObjectGenerator): pass return to_return - def generate_attributes(self): + def generate_attributes(self) -> None: # Attach original & Converted if self.attach_original_email is not None: @@ -267,21 +269,30 @@ class EMailObject(AbstractMISPObjectGenerator): data=self.raw_emails.get('msg')) message = self.email + body: EmailMessage - for _pref, body in message._find_body(message, preferencelist=['plain', 'html']): - comment = "{0} body".format(body.get_content_type()) + if body := message.get_body(preferencelist=['plain']): + comment = f"{body.get_content_type()} body" if self.encapsulated_body == body.get_content_type(): comment += " De-Encapsulated from RTF in original msg." self.add_attribute("email-body", body.get_content(), comment=comment) - headers = ["{}: {}".format(k, v) for k, v in message.items()] + if body := message.get_body(preferencelist=['html']): + comment = f"{body.get_content_type()} body" + if self.encapsulated_body == body.get_content_type(): + comment += " De-Encapsulated from RTF in original msg." + self.add_attribute("email-body", + body.get_content(), + comment=comment) + + headers = [f"{k}: {v}" for k, v in message.items()] if headers: self.add_attribute("header", "\n".join(headers)) - if "Date" in message and message.get('date').datetime is not None: - self.add_attribute("send-date", message.get('date').datetime) + if "Date" in message and message['date'].datetime is not None: + self.add_attribute("send-date", message['date'].datetime) if "To" in message: self.__add_emails("to", message["To"]) @@ -325,31 +336,32 @@ class EMailObject(AbstractMISPObjectGenerator): self.__generate_received() - def __add_emails(self, typ: str, data: str, insert_display_names: bool = True): - addresses = [] - display_names = [] + def __add_emails(self, typ: str, data: str, insert_display_names: bool = True) -> None: + addresses: list[dict[str, str]] = [] + display_names: list[dict[str, str]] = [] for realname, address in email.utils.getaddresses([data]): if address and realname: - addresses.append({"value": address, "comment": "{} <{}>".format(realname, address)}) + addresses.append({"value": address, "comment": f"{realname} <{address}>"}) elif address: addresses.append({"value": address}) else: # parsing failed, skip continue if realname: - display_names.append({"value": realname, "comment": "{} <{}>".format(realname, address)}) + display_names.append({"value": realname, "comment": f"{realname} <{address}>"}) - if addresses: - self.add_attributes(typ, *addresses) + for a in addresses: + self.add_attribute(typ, **a) if insert_display_names and display_names: try: - self.add_attributes("{}-display-name".format(typ), *display_names) + for d in display_names: + self.add_attribute(f"{typ}-display-name", **d) except NewAttributeError: # email object doesn't support display name for all email addrs pass - def __generate_received(self): + def __generate_received(self) -> None: """ Extract IP addresses from received headers that are not private. Also extract hostnames or domains. """ diff --git a/pymisp/tools/ext_lookups.py b/pymisp/tools/ext_lookups.py index 75ca0e1..c4e81ac 100644 --- a/pymisp/tools/ext_lookups.py +++ b/pymisp/tools/ext_lookups.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations try: from pymispgalaxies import Clusters # type: ignore @@ -14,7 +15,7 @@ except ImportError: has_pymispgalaxies = False -def revert_tag_from_galaxies(tag): +def revert_tag_from_galaxies(tag: str) -> list[str]: clusters = Clusters() try: return clusters.revert_machinetag(tag) @@ -22,7 +23,7 @@ def revert_tag_from_galaxies(tag): return [] -def revert_tag_from_taxonomies(tag): +def revert_tag_from_taxonomies(tag: str) -> list[str]: taxonomies = Taxonomies() try: return taxonomies.revert_machinetag(tag) @@ -30,7 +31,7 @@ def revert_tag_from_taxonomies(tag): return [] -def search_taxonomies(query): +def search_taxonomies(query: str) -> list[str]: taxonomies = Taxonomies() found = taxonomies.search(query) if not found: @@ -38,6 +39,6 @@ def search_taxonomies(query): return found -def search_galaxies(query): +def search_galaxies(query: str) -> list[str]: clusters = Clusters() return clusters.search(query) diff --git a/pymisp/tools/fail2banobject.py b/pymisp/tools/fail2banobject.py index 5a5a5b3..a8e3fda 100644 --- a/pymisp/tools/fail2banobject.py +++ b/pymisp/tools/fail2banobject.py @@ -1,20 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from typing import Any from .abstractgenerator import AbstractMISPObjectGenerator -import logging logger = logging.getLogger('pymisp') class Fail2BanObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs): # type: ignore[no-untyped-def] super().__init__('fail2ban', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: timestamp = self._sanitize_timestamp(self._parameters.pop('processing-timestamp', None)) self._parameters['processing-timestamp'] = timestamp super().generate_attributes() diff --git a/pymisp/tools/feed.py b/pymisp/tools/feed.py index f3d937a..0452a25 100644 --- a/pymisp/tools/feed.py +++ b/pymisp/tools/feed.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations from pathlib import Path from pymisp import MISPEvent import json -from typing import List -def feed_meta_generator(path: Path): +def feed_meta_generator(path: Path) -> None: manifests = {} - hashes: List[str] = [] + hashes: list[str] = [] for f_name in path.glob('*.json'): if str(f_name.name) == 'manifest.json': diff --git a/pymisp/tools/fileobject.py b/pymisp/tools/fileobject.py index ad2862b..f8b277e 100644 --- a/pymisp/tools/fileobject.py +++ b/pymisp/tools/fileobject.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations from ..exceptions import InvalidMISPObject from .abstractgenerator import AbstractMISPObjectGenerator @@ -10,7 +11,6 @@ import math from collections import Counter import logging from pathlib import Path -from typing import Union, Optional logger = logging.getLogger('pymisp') @@ -22,7 +22,7 @@ except ImportError: HAS_PYDEEP = False try: - import magic # type: ignore + import magic HAS_MAGIC = True except ImportError: HAS_MAGIC = False @@ -30,7 +30,9 @@ except ImportError: class FileObject(AbstractMISPObjectGenerator): - def __init__(self, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, filename: Optional[str] = None, **kwargs) -> None: + def __init__(self, filepath: Path | str | None = None, # type: ignore[no-untyped-def] + pseudofile: BytesIO | bytes | None = None, + filename: str | None = None, **kwargs) -> None: super().__init__('file', **kwargs) if not HAS_PYDEEP: logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]") @@ -55,10 +57,10 @@ class FileObject(AbstractMISPObjectGenerator): self.__data = self.__pseudofile.getvalue() self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('filename', value=self.__filename) - size = self.add_attribute('size-in-bytes', value=len(self.__data)) - if int(size.value) > 0: + self.add_attribute('size-in-bytes', value=len(self.__data)) + if len(self.__data) > 0: self.add_attribute('entropy', value=self.__entropy_H(self.__data)) self.add_attribute('md5', value=md5(self.__data).hexdigest()) self.add_attribute('sha1', value=sha1(self.__data).hexdigest()) diff --git a/pymisp/tools/genericgenerator.py b/pymisp/tools/genericgenerator.py index eeed742..811f604 100644 --- a/pymisp/tools/genericgenerator.py +++ b/pymisp/tools/genericgenerator.py @@ -1,14 +1,16 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +from typing import Any from .abstractgenerator import AbstractMISPObjectGenerator -from typing import List class GenericObjectGenerator(AbstractMISPObjectGenerator): # FIXME: this method is different from the master one, and that's probably not a good idea. - def generate_attributes(self, attributes: List[dict]): # type: ignore + def generate_attributes(self, attributes: list[dict[str, Any]]) -> None: # type: ignore[override] """Generates MISPObjectAttributes from a list of dictionaries. Each entry if the list must be in one of the two following formats: * {: } diff --git a/pymisp/tools/geolocationobject.py b/pymisp/tools/geolocationobject.py index 9ecb460..fc995c9 100644 --- a/pymisp/tools/geolocationobject.py +++ b/pymisp/tools/geolocationobject.py @@ -1,20 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from typing import Any from .abstractgenerator import AbstractMISPObjectGenerator -import logging logger = logging.getLogger('pymisp') class GeolocationObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('geolocation', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: first = self._sanitize_timestamp(self._parameters.pop('first-seen', None)) self._parameters['first-seen'] = first last = self._sanitize_timestamp(self._parameters.pop('last-seen', None)) diff --git a/pymisp/tools/git_vuln_finder_object.py b/pymisp/tools/git_vuln_finder_object.py index 451d62a..21ec512 100644 --- a/pymisp/tools/git_vuln_finder_object.py +++ b/pymisp/tools/git_vuln_finder_object.py @@ -1,20 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from typing import Any from .abstractgenerator import AbstractMISPObjectGenerator -import logging logger = logging.getLogger('pymisp') class GitVulnFinderObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('git-vuln-finder', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: authored_date = self._sanitize_timestamp(self._parameters.pop('authored_date', None)) self._parameters['authored_date'] = authored_date committed_date = self._sanitize_timestamp(self._parameters.pop('committed_date', None)) diff --git a/pymisp/tools/load_warninglists.py b/pymisp/tools/load_warninglists.py index 89ec192..feb5ea3 100644 --- a/pymisp/tools/load_warninglists.py +++ b/pymisp/tools/load_warninglists.py @@ -1,26 +1,30 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +from ..api import PyMISP try: - from pymispwarninglists import WarningLists # type: ignore + from pymispwarninglists import WarningLists, WarningList # type: ignore has_pymispwarninglists = True except ImportError: has_pymispwarninglists = False -def from_instance(pymisp_instance, slow_search=False): +def from_instance(pymisp_instance: PyMISP, slow_search: bool=False) -> WarningLists: """Load the warnindlist from an existing MISP instance :pymisp_instance: Already instantialized PyMISP instance.""" - warninglists_index = pymisp_instance.get_warninglists()['Warninglists'] + warninglists_index = pymisp_instance.warninglists(pythonify=True) all_warningslists = [] for warninglist in warninglists_index: - wl = pymisp_instance.get_warninglist(warninglist['Warninglist']['id'])['Warninglist'] - wl['list'] = wl.pop('WarninglistEntry') - all_warningslists.append(wl) + if isinstance(warninglist, WarningList): + wl = pymisp_instance.get_warninglist(warninglist['Warninglist']['id'])['Warninglist'] + wl['list'] = wl.pop('WarninglistEntry') + all_warningslists.append(wl) return WarningLists(slow_search, all_warningslists) -def from_package(slow_search=False): +def from_package(slow_search: bool=False) -> WarningLists: return WarningLists(slow_search) diff --git a/pymisp/tools/machoobject.py b/pymisp/tools/machoobject.py index 95f1b87..ad68e46 100644 --- a/pymisp/tools/machoobject.py +++ b/pymisp/tools/machoobject.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from hashlib import md5, sha1, sha256, sha512 +from io import BytesIO +from pathlib import Path +from typing import Any from ..exceptions import InvalidMISPObject -from .abstractgenerator import AbstractMISPObjectGenerator -from io import BytesIO -from hashlib import md5, sha1, sha256, sha512 -import logging -from typing import Optional, Union -from pathlib import Path + from . import FileObject +from .abstractgenerator import AbstractMISPObjectGenerator import lief @@ -21,7 +25,10 @@ except ImportError: logger = logging.getLogger('pymisp') -def make_macho_objects(lief_parsed: lief.MachO.Binary, misp_file: FileObject, standalone: bool = True, default_attributes_parameters: dict = {}): +def make_macho_objects(lief_parsed: lief.MachO.Binary, + misp_file: FileObject, + standalone: bool = True, + default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, MachOObject, list[MachOSectionObject]]: macho_object = MachOObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters) misp_file.add_reference(macho_object.uuid, 'includes', 'MachO indicators') macho_sections = [] @@ -32,31 +39,43 @@ def make_macho_objects(lief_parsed: lief.MachO.Binary, misp_file: FileObject, st class MachOObject(AbstractMISPObjectGenerator): - def __init__(self, parsed: Optional[lief.MachO.Binary] = None, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, **kwargs): + __macho: lief.MachO.Binary + + def __init__(self, parsed: lief.MachO.Binary | lief.MachO.FatBinary | None = None, # type: ignore[no-untyped-def] + filepath: Path | str | None = None, + pseudofile: BytesIO | list[int] | None = None, + **kwargs) -> None: """Creates an MachO object, with lief""" super().__init__('macho', **kwargs) if not HAS_PYDEEP: logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]") if pseudofile: if isinstance(pseudofile, BytesIO): - self.__macho = lief.MachO.parse(io=pseudofile) + m = lief.MachO.parse(obj=pseudofile) elif isinstance(pseudofile, bytes): - self.__macho = lief.MachO.parse(raw=pseudofile) + m = lief.MachO.parse(raw=list(pseudofile)) + elif isinstance(pseudofile, list): + m = lief.MachO.parse(raw=pseudofile) else: - raise InvalidMISPObject('Pseudo file can be BytesIO or bytes got {}'.format(type(pseudofile))) + raise InvalidMISPObject(f'Pseudo file can be BytesIO or bytes got {type(pseudofile)}') + if not m: + raise InvalidMISPObject('Unable to parse pseudofile') + self.__macho = m.at(0) elif filepath: - self.__macho = lief.MachO.parse(filepath) + if m := lief.MachO.parse(filepath): + self.__macho = m.at(0) elif parsed: # Got an already parsed blob - if isinstance(parsed, lief.MachO.Binary): + if isinstance(parsed, lief.MachO.FatBinary): + self.__macho = parsed.at(0) + elif isinstance(parsed, lief.MachO.Binary): self.__macho = parsed else: - raise InvalidMISPObject('Not a lief.MachO.Binary: {}'.format(type(parsed))) + raise InvalidMISPObject(f'Not a lief.MachO.Binary: {type(parsed)}') self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('type', value=str(self.__macho.header.file_type).split('.')[1]) - self.add_attribute('name', value=self.__macho.name) # General information if self.__macho.has_entrypoint: self.add_attribute('entrypoint-address', value=self.__macho.entrypoint) @@ -66,7 +85,7 @@ class MachOObject(AbstractMISPObjectGenerator): pos = 0 for section in self.__macho.sections: s = MachOSectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters) - self.add_reference(s.uuid, 'includes', 'Section {} of MachO'.format(pos)) + self.add_reference(s.uuid, 'includes', f'Section {pos} of MachO') pos += 1 self.sections.append(s) self.add_attribute('number-sections', value=len(self.sections)) @@ -74,19 +93,19 @@ class MachOObject(AbstractMISPObjectGenerator): class MachOSectionObject(AbstractMISPObjectGenerator): - def __init__(self, section: lief.MachO.Section, **kwargs): + def __init__(self, section: lief.MachO.Section, **kwargs) -> None: # type: ignore[no-untyped-def] """Creates an MachO Section object. Object generated by MachOObject.""" # Python3 way # super().__init__('pe-section') - super(MachOSectionObject, self).__init__('macho-section', **kwargs) + super().__init__('macho-section', **kwargs) self.__section = section self.__data = bytes(self.__section.content) self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('name', value=self.__section.name) - size = self.add_attribute('size-in-bytes', value=self.__section.size) - if int(size.value) > 0: + self.add_attribute('size-in-bytes', value=self.__section.size) + if int(self.__section.size) > 0: self.add_attribute('entropy', value=self.__section.entropy) self.add_attribute('md5', value=md5(self.__data).hexdigest()) self.add_attribute('sha1', value=sha1(self.__data).hexdigest()) diff --git a/pymisp/tools/microblogobject.py b/pymisp/tools/microblogobject.py index 14adfb0..63d20a1 100644 --- a/pymisp/tools/microblogobject.py +++ b/pymisp/tools/microblogobject.py @@ -1,22 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- +from __future__ import annotations + +import logging +from typing import Any # NOTE: Reference on how this module is used: https://vvx7.io/posts/2020/05/misp-slack-bot/ from .abstractgenerator import AbstractMISPObjectGenerator -import logging logger = logging.getLogger('pymisp') class MicroblogObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs): # type: ignore[no-untyped-def] super().__init__('microblog', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: # Raw post. if 'post' in self._parameters: self.add_attribute('post', value=self._parameters['post']) @@ -32,7 +34,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): # Original URL location of the microblog post (potentially malicious. if 'url' in self._parameters: if isinstance(self._parameters.get('url'), list): - for i in self._parameters.get('url'): + for i in self._parameters['url']: self.add_attribute('url', value=i) else: self.add_attribute('url', value=self._parameters['url']) @@ -40,7 +42,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): # Archive of the original document (Internet Archive, Archive.is, etc). if 'archive' in self._parameters: if isinstance(self._parameters.get('archive'), list): - for i in self._parameters.get('archive'): + for i in self._parameters['archive']: self.add_attribute('archive', value=i) else: self.add_attribute('archive', value=self._parameters['archive']) @@ -74,7 +76,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): "Instagram", "Forum", "Other"] if 'type' in self._parameters: if isinstance(self._parameters.get('type'), list): - for i in self._parameters.get('type'): + for i in self._parameters['type']: if i in type_allowed_values: self.add_attribute('type', value=i) else: @@ -85,7 +87,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): type_allowed_values = ["Informative", "Malicious", "Misinformation", "Disinformation", "Unknown"] if 'state' in self._parameters: if isinstance(self._parameters.get('state'), list): - for i in self._parameters.get('state'): + for i in self._parameters['state']: if i in type_allowed_values: self.add_attribute('state', value=i) else: @@ -100,7 +102,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): type_allowed_values = ["Verified", "Unverified", "Unknown"] if 'verified-username' in self._parameters: if isinstance(self._parameters.get('verified-username'), list): - for i in self._parameters.get('verified-username'): + for i in self._parameters['verified-username']: if i in type_allowed_values: self.add_attribute('verified-username', value=i) else: @@ -110,7 +112,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): # embedded-link. if 'embedded-link' in self._parameters: if isinstance(self._parameters.get('embedded-link'), list): - for i in self._parameters.get('embedded-link'): + for i in self._parameters['embedded-link']: self.add_attribute('embedded-link', value=i) else: self.add_attribute('embedded-link', value=self._parameters['embedded-link']) @@ -118,7 +120,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): # embedded-safe-link if 'embedded-safe-link' in self._parameters: if isinstance(self._parameters.get('embedded-safe-link'), list): - for i in self._parameters.get('embedded-safe-link'): + for i in self._parameters['embedded-safe-link']: self.add_attribute('embedded-safe-link', value=i) else: self.add_attribute('embedded-safe-link', value=self._parameters['embedded-safe-link']) @@ -126,7 +128,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): # Hashtag into the microblog post. if 'hashtag' in self._parameters: if isinstance(self._parameters.get('hashtag'), list): - for i in self._parameters.get('hashtag'): + for i in self._parameters['hashtag']: self.add_attribute('hashtag', value=i) else: self.add_attribute('hashtag', value=self._parameters['hashtag']) @@ -134,7 +136,7 @@ class MicroblogObject(AbstractMISPObjectGenerator): # username quoted if 'username-quoted' in self._parameters: if isinstance(self._parameters.get('username-quoted'), list): - for i in self._parameters.get('username-quoted'): + for i in self._parameters['username-quoted']: self.add_attribute('username-quoted', value=i) else: self.add_attribute('username-quoted', value=self._parameters['username-quoted']) diff --git a/pymisp/tools/neo4j.py b/pymisp/tools/neo4j.py index 2656a5b..7a71978 100644 --- a/pymisp/tools/neo4j.py +++ b/pymisp/tools/neo4j.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations import glob import os + from .. import MISPEvent try: @@ -13,23 +14,23 @@ except ImportError: class Neo4j(): - def __init__(self, host='localhost:7474', username='neo4j', password='neo4j'): + def __init__(self, host: str='localhost:7474', username: str='neo4j', password: str='neo4j') -> None: if not has_py2neo: raise Exception('py2neo is required, please install: pip install py2neo') authenticate(host, username, password) - self.graph = Graph("http://{}/db/data/".format(host)) + self.graph = Graph(f"http://{host}/db/data/") - def load_events_directory(self, directory): - self.events = [] + def load_events_directory(self, directory: str) -> None: + self.events: list[MISPEvent] = [] for path in glob.glob(os.path.join(directory, '*.json')): e = MISPEvent() e.load(path) self.import_event(e) - def del_all(self): + def del_all(self) -> None: self.graph.delete_all() - def import_event(self, event): + def import_event(self, event: MISPEvent) -> None: tx = self.graph.begin() event_node = Node('Event', uuid=event.uuid, name=event.info) # event_node['distribution'] = event.distribution diff --git a/pymisp/tools/openioc.py b/pymisp/tools/openioc.py index 662b444..488c5b0 100755 --- a/pymisp/tools/openioc.py +++ b/pymisp/tools/openioc.py @@ -1,5 +1,4 @@ - -# -*- coding: utf-8 -*- +from __future__ import annotations import os @@ -156,7 +155,7 @@ def extract_field(report, field_name): def load_openioc_file(openioc_path): if not os.path.exists(openioc_path): raise Exception("Path doesn't exists.") - with open(openioc_path, 'r') as f: + with open(openioc_path) as f: return load_openioc(f) diff --git a/pymisp/tools/peobject.py b/pymisp/tools/peobject.py index 167cfbf..08f35cf 100644 --- a/pymisp/tools/peobject.py +++ b/pymisp/tools/peobject.py @@ -1,19 +1,22 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from ..exceptions import InvalidMISPObject -from .abstractgenerator import AbstractMISPObjectGenerator -from io import BytesIO -from hashlib import md5, sha1, sha256, sha512 -from datetime import datetime +from __future__ import annotations + import logging -from typing import Optional, Union -from pathlib import Path + from base64 import b64encode +from datetime import datetime +from hashlib import md5, sha1, sha256, sha512 +from io import BytesIO +from pathlib import Path +from typing import Any from . import FileObject +from .abstractgenerator import AbstractMISPObjectGenerator +from ..exceptions import InvalidMISPObject import lief +import lief.PE try: import pydeep # type: ignore @@ -24,7 +27,10 @@ except ImportError: logger = logging.getLogger('pymisp') -def make_pe_objects(lief_parsed: lief.PE.Binary, misp_file: FileObject, standalone: bool = True, default_attributes_parameters: dict = {}): +def make_pe_objects(lief_parsed: lief.PE.Binary, + misp_file: FileObject, + standalone: bool = True, + default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, PEObject, list[PESectionObject]]: pe_object = PEObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters) misp_file.add_reference(pe_object.uuid, 'includes', 'PE indicators') pe_sections = [] @@ -35,44 +41,57 @@ def make_pe_objects(lief_parsed: lief.PE.Binary, misp_file: FileObject, standalo class PEObject(AbstractMISPObjectGenerator): - def __init__(self, parsed: Optional[lief.PE.Binary] = None, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, **kwargs): + __pe: lief.PE.Binary + + def __init__(self, parsed: lief.PE.Binary | None = None, # type: ignore[no-untyped-def] + filepath: Path | str | None = None, + pseudofile: BytesIO | list[int] | None = None, + **kwargs) -> None: """Creates an PE object, with lief""" super().__init__('pe', **kwargs) if not HAS_PYDEEP: logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]") if pseudofile: if isinstance(pseudofile, BytesIO): - self.__pe = lief.PE.parse(io=pseudofile) + p = lief.PE.parse(obj=pseudofile) elif isinstance(pseudofile, bytes): - self.__pe = lief.PE.parse(raw=pseudofile) + p = lief.PE.parse(raw=list(pseudofile)) + elif isinstance(pseudofile, list): + p = lief.PE.parse(raw=pseudofile) else: - raise InvalidMISPObject('Pseudo file can be BytesIO or bytes got {}'.format(type(pseudofile))) + raise InvalidMISPObject(f'Pseudo file can be BytesIO or bytes got {type(pseudofile)}') + if not p: + raise InvalidMISPObject('Unable to parse pseudofile') + self.__pe = p elif filepath: - self.__pe = lief.PE.parse(filepath) + if p := lief.PE.parse(filepath): + self.__pe = p + else: + raise InvalidMISPObject(f'Unable to parse {filepath}') elif parsed: # Got an already parsed blob if isinstance(parsed, lief.PE.Binary): self.__pe = parsed else: - raise InvalidMISPObject('Not a lief.PE.Binary: {}'.format(type(parsed))) + raise InvalidMISPObject(f'Not a lief.PE.Binary: {type(parsed)}') self.generate_attributes() - def _is_exe(self): + def _is_exe(self) -> bool: if not self._is_dll() and not self._is_driver(): - return self.__pe.header.has_characteristic(lief.PE.HEADER_CHARACTERISTICS.EXECUTABLE_IMAGE) + return self.__pe.header.has_characteristic(lief.PE.Header.CHARACTERISTICS.EXECUTABLE_IMAGE) return False - def _is_dll(self): - return self.__pe.header.has_characteristic(lief.PE.HEADER_CHARACTERISTICS.DLL) + def _is_dll(self) -> bool: + return self.__pe.header.has_characteristic(lief.PE.Header.CHARACTERISTICS.DLL) - def _is_driver(self): + def _is_driver(self) -> bool: # List from pefile - system_DLLs = set(('ntoskrnl.exe', 'hal.dll', 'ndis.sys', 'bootvid.dll', 'kdcom.dll')) + system_DLLs = {'ntoskrnl.exe', 'hal.dll', 'ndis.sys', 'bootvid.dll', 'kdcom.dll'} if system_DLLs.intersection([imp.lower() for imp in self.__pe.libraries]): return True return False - def _get_pe_type(self): + def _get_pe_type(self) -> str: if self._is_dll(): return 'dll' elif self._is_driver(): @@ -82,31 +101,27 @@ class PEObject(AbstractMISPObjectGenerator): else: return 'unknown' - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('type', value=self._get_pe_type()) # General information self.add_attribute('entrypoint-address', value=self.__pe.entrypoint) self.add_attribute('compilation-timestamp', value=datetime.utcfromtimestamp(self.__pe.header.time_date_stamps).isoformat()) self.add_attribute('imphash', value=lief.PE.get_imphash(self.__pe, lief.PE.IMPHASH_MODE.PEFILE)) self.add_attribute('authentihash', value=self.__pe.authentihash_sha256.hex()) - try: - if (self.__pe.has_resources - and self.__pe.resources_manager.has_version - and self.__pe.resources_manager.version.has_string_file_info - and self.__pe.resources_manager.version.string_file_info.langcode_items): - fileinfo = dict(self.__pe.resources_manager.version.string_file_info.langcode_items[0].items.items()) + r_manager = self.__pe.resources_manager + if isinstance(r_manager, lief.PE.ResourcesManager): + version = r_manager.version + if isinstance(version, lief.PE.ResourceVersion) and version.string_file_info is not None: + fileinfo = dict(version.string_file_info.langcode_items[0].items.items()) self.add_attribute('original-filename', value=fileinfo.get('OriginalFilename')) self.add_attribute('internal-filename', value=fileinfo.get('InternalName')) self.add_attribute('file-description', value=fileinfo.get('FileDescription')) self.add_attribute('file-version', value=fileinfo.get('FileVersion')) - self.add_attribute('lang-id', value=self.__pe.resources_manager.version.string_file_info.langcode_items[0].key) self.add_attribute('product-name', value=fileinfo.get('ProductName')) self.add_attribute('product-version', value=fileinfo.get('ProductVersion')) self.add_attribute('company-name', value=fileinfo.get('CompanyName')) self.add_attribute('legal-copyright', value=fileinfo.get('LegalCopyright')) - except lief.read_out_of_bound: - # The file is corrupted - pass + self.add_attribute('lang-id', value=version.string_file_info.langcode_items[0].key) # Sections self.sections = [] if self.__pe.sections: @@ -116,10 +131,14 @@ class PEObject(AbstractMISPObjectGenerator): # Skip section if name is none AND size is 0. continue s = PESectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters) - self.add_reference(s.uuid, 'includes', 'Section {} of PE'.format(pos)) + self.add_reference(s.uuid, 'includes', f'Section {pos} of PE') if ((self.__pe.entrypoint >= section.virtual_address) and (self.__pe.entrypoint < (section.virtual_address + section.virtual_size))): - self.add_attribute('entrypoint-section-at-position', value='{}|{}'.format(section.name, pos)) + if isinstance(section.name, bytes): + section_name = section.name.decode() + else: + section_name = section.name + self.add_attribute('entrypoint-section-at-position', value=f'{section_name}|{pos}') pos += 1 self.sections.append(s) self.add_attribute('number-sections', value=len(self.sections)) @@ -139,16 +158,30 @@ class PEObject(AbstractMISPObjectGenerator): class PECertificate(AbstractMISPObjectGenerator): - def __init__(self, certificate: lief.PE.x509, **kwargs): + def __init__(self, certificate: lief.PE.x509, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('x509') self.__certificate = certificate self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('issuer', value=self.__certificate.issuer) self.add_attribute('serial-number', value=self.__certificate.serial_number) - self.add_attribute('validity-not-before', value=datetime(*self.__certificate.valid_from)) - self.add_attribute('validity-not-after', value=datetime(*self.__certificate.valid_to)) + if len(self.__certificate.valid_from) == 6: + self.add_attribute('validity-not-before', + value=datetime(year=self.__certificate.valid_from[0], + month=self.__certificate.valid_from[1], + day=self.__certificate.valid_from[2], + hour=self.__certificate.valid_from[3], + minute=self.__certificate.valid_from[4], + second=self.__certificate.valid_from[5])) + if len(self.__certificate.valid_to) == 6: + self.add_attribute('validity-not-after', + value=datetime(year=self.__certificate.valid_to[0], + month=self.__certificate.valid_to[1], + day=self.__certificate.valid_to[2], + hour=self.__certificate.valid_to[3], + minute=self.__certificate.valid_to[4], + second=self.__certificate.valid_to[5])) self.add_attribute('version', value=self.__certificate.version) self.add_attribute('subject', value=self.__certificate.subject) self.add_attribute('signature_algorithm', value=self.__certificate.signature_algorithm) @@ -157,19 +190,19 @@ class PECertificate(AbstractMISPObjectGenerator): class PESigners(AbstractMISPObjectGenerator): - def __init__(self, signer: lief.PE.SignerInfo, **kwargs): + def __init__(self, signer: lief.PE.SignerInfo, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('authenticode-signerinfo') self.__signer = signer self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('issuer', value=self.__signer.issuer) self.add_attribute('serial-number', value=self.__signer.serial_number) self.add_attribute('version', value=self.__signer.version) - self.add_attribute('digest_algorithm', value=self.__signer.digest_algorithm.name) - self.add_attribute('encryption_algorithm', value=self.__signer.encryption_algorithm.name) + self.add_attribute('digest_algorithm', value=str(self.__signer.digest_algorithm)) + self.add_attribute('encryption_algorithm', value=str(self.__signer.encryption_algorithm)) self.add_attribute('digest-base64', value=b64encode(self.__signer.encrypted_digest)) - info = self.__signer.get_attribute(lief.PE.SIG_ATTRIBUTE_TYPES.SPC_SP_OPUS_INFO) + info: lief.PE.SpcSpOpusInfo = self.__signer.get_attribute(lief.PE.Attribute.TYPE.SPC_SP_OPUS_INFO) # type: ignore[assignment] if info: self.add_attribute('program-name', value=info.program_name) self.add_attribute('url', value=info.more_info) @@ -177,17 +210,17 @@ class PESigners(AbstractMISPObjectGenerator): class PESectionObject(AbstractMISPObjectGenerator): - def __init__(self, section: lief.PE.Section, **kwargs): + def __init__(self, section: lief.PE.Section, **kwargs) -> None: # type: ignore[no-untyped-def] """Creates an PE Section object. Object generated by PEObject.""" super().__init__('pe-section') self.__section = section self.__data = bytes(self.__section.content) self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('name', value=self.__section.name) - size = self.add_attribute('size-in-bytes', value=self.__section.size) - if int(size.value) > 0: + self.add_attribute('size-in-bytes', value=self.__section.size) + if int(self.__section.size) > 0: # zero-filled sections can create too many correlations to_ids = float(self.__section.entropy) > 0 disable_correlation = not to_ids diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index e3caf42..dc4f507 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations # Standard imports import base64 @@ -49,7 +50,7 @@ def create_flowable_tag(misp_tag): return [Flowable_Tag(text=misp_tag.name, color=misp_tag.colour, custom_style=col1_style)] -class Flowable_Tag(Flowable): +class Flowable_Tag(Flowable): # type: ignore[misc] """ Custom flowable to handle tags. Draw one Tag with the webview formatting Modified from : http://two.pairlist.net/pipermail/reportlab-users/2005-February/003695.html @@ -108,7 +109,7 @@ class Flowable_Tag(Flowable): LEFT_INTERNAL_PADDING = 2 ELONGATION = LEFT_INTERNAL_PADDING * 2 - p = Paragraph("{}".format(self.choose_good_text_color(), self.text), style=self.custom_style) + p = Paragraph(f"{self.text}", style=self.custom_style) string_width = stringWidth(self.text, self.custom_style.fontName, self.custom_style.fontSize) self.width = string_width + ELONGATION @@ -615,7 +616,7 @@ class Value_Formatter(): curr_uuid = str(is_safe_value(uuid)) curr_baseurl = self.config[moduleconfig[0]] curr_url = uuid_to_url(curr_baseurl, curr_uuid) - html_url = "{}".format(curr_url, safe_string(text)) + html_url = f"{safe_string(text)}" if color: # They want fancy colors @@ -744,7 +745,7 @@ class Value_Formatter(): answer = YES_ANSWER if is_safe_value(published_timestamp): # Published and have published date - answer += '({})'.format(published_timestamp.strftime(EXPORT_DATE_FORMAT)) + answer += f'({published_timestamp.strftime(EXPORT_DATE_FORMAT)})' else: # Published without published date answer += "(no date)" diff --git a/pymisp/tools/sbsignatureobject.py b/pymisp/tools/sbsignatureobject.py index ca7ad6e..a7356a3 100644 --- a/pymisp/tools/sbsignatureobject.py +++ b/pymisp/tools/sbsignatureobject.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations from .abstractgenerator import AbstractMISPObjectGenerator @@ -8,13 +9,13 @@ class SBSignatureObject(AbstractMISPObjectGenerator): ''' Sandbox Analyzer ''' - def __init__(self, software: str, report: list, **kwargs): + def __init__(self, software: str, report: list[tuple[str, str]], **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('sb-signature', **kwargs) self._software = software self._report = report self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: ''' Parse the report for relevant attributes ''' self.add_attribute("software", value=self._software) for (signature_name, description) in self._report: diff --git a/pymisp/tools/sshauthkeyobject.py b/pymisp/tools/sshauthkeyobject.py index 8d7d74c..ce6c2fb 100644 --- a/pymisp/tools/sshauthkeyobject.py +++ b/pymisp/tools/sshauthkeyobject.py @@ -1,23 +1,25 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from io import StringIO +from pathlib import Path from ..exceptions import InvalidMISPObject from .abstractgenerator import AbstractMISPObjectGenerator -from io import StringIO -import logging -from typing import Optional, Union -from pathlib import Path logger = logging.getLogger('pymisp') class SSHAuthorizedKeysObject(AbstractMISPObjectGenerator): - def __init__(self, authorized_keys_path: Optional[Union[Path, str]] = None, authorized_keys_pseudofile: Optional[StringIO] = None, **kwargs): - # PY3 way: + def __init__(self, authorized_keys_path: Path | str | None = None, # type: ignore[no-untyped-def] + authorized_keys_pseudofile: StringIO | None = None, **kwargs): super().__init__('ssh-authorized-keys', **kwargs) if authorized_keys_path: - with open(authorized_keys_path, 'r') as f: + with open(authorized_keys_path) as f: self.__pseudofile = StringIO(f.read()) elif authorized_keys_pseudofile and isinstance(authorized_keys_pseudofile, StringIO): self.__pseudofile = authorized_keys_pseudofile @@ -26,7 +28,7 @@ class SSHAuthorizedKeysObject(AbstractMISPObjectGenerator): self.__data = self.__pseudofile.getvalue() self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: for line in self.__pseudofile: if line.startswith('ssh') or line.startswith('ecdsa'): key = line.split(' ')[1] diff --git a/pymisp/tools/stix.py b/pymisp/tools/stix.py deleted file mode 100644 index 0c0f605..0000000 --- a/pymisp/tools/stix.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- - -try: - from misp_stix_converter.converters.buildMISPAttribute import buildEvent # type: ignore - from misp_stix_converter.converters import convert # type: ignore - from misp_stix_converter.converters.convert import MISPtoSTIX # type: ignore - has_misp_stix_converter = True -except ImportError: - has_misp_stix_converter = False - - -def load_stix(stix, distribution: int = 3, threat_level_id: int = 2, analysis: int = 0): - '''Returns a MISPEvent object from a STIX package''' - if not has_misp_stix_converter: - raise Exception('You need to install misp_stix_converter: pip install git+https://github.com/MISP/MISP-STIX-Converter.git') - stix = convert.load_stix(stix) - return buildEvent(stix, distribution=distribution, - threat_level_id=threat_level_id, analysis=analysis) - - -def make_stix_package(misp_event, to_json: bool = False, to_xml: bool = False): - '''Returns a STIXPackage from a MISPEvent. - - Optionally can return the package in json or xml. - - ''' - if not has_misp_stix_converter: - raise Exception('You need to install misp_stix_converter: pip install git+https://github.com/MISP/MISP-STIX-Converter.git') - package = MISPtoSTIX(misp_event) - if to_json: - return package.to_json() - elif to_xml: - return package.to_xml() - else: - return package diff --git a/pymisp/tools/update_objects.py b/pymisp/tools/update_objects.py index 2bcb6c7..7f1dd25 100644 --- a/pymisp/tools/update_objects.py +++ b/pymisp/tools/update_objects.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations import zipfile from io import BytesIO @@ -12,7 +13,7 @@ from ..abstract import resources_path static_repo = "https://github.com/MISP/misp-objects/archive/main.zip" -def update_objects(): +def update_objects() -> None: r = requests.get(static_repo) zipped_repo = BytesIO(r.content) diff --git a/pymisp/tools/urlobject.py b/pymisp/tools/urlobject.py index 485dfb7..3465541 100644 --- a/pymisp/tools/urlobject.py +++ b/pymisp/tools/urlobject.py @@ -1,9 +1,12 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations + +import logging + +from urllib.parse import unquote_plus from .abstractgenerator import AbstractMISPObjectGenerator -import logging -from urllib.parse import unquote_plus try: from pyfaup.faup import Faup # type: ignore @@ -17,13 +20,13 @@ faup = Faup() class URLObject(AbstractMISPObjectGenerator): - def __init__(self, url: str, generate_all=False, **kwargs): + def __init__(self, url: str, generate_all=False, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('url', **kwargs) self._generate_all = True if generate_all is True else False faup.decode(unquote_plus(url)) self.generate_attributes() - def generate_attributes(self): + def generate_attributes(self) -> None: self.add_attribute('url', value=faup.url.decode()) if faup.get_host(): self.add_attribute('host', value=faup.get_host()) diff --git a/pymisp/tools/vehicleobject.py b/pymisp/tools/vehicleobject.py index 7d5bc95..75c9a4a 100644 --- a/pymisp/tools/vehicleobject.py +++ b/pymisp/tools/vehicleobject.py @@ -1,8 +1,12 @@ #!/usr/bin/python3 +from __future__ import annotations + import requests import json +from typing import Any + from .abstractgenerator import AbstractMISPObjectGenerator # Original sourcecode: https://github.com/hayk57/MISP_registration_check @@ -11,14 +15,16 @@ from .abstractgenerator import AbstractMISPObjectGenerator class VehicleObject(AbstractMISPObjectGenerator): '''Vehicle object generator out of regcheck.org.uk''' - country_urls = { + country_urls: dict[str, str] = { 'fr': "http://www.regcheck.org.uk/api/reg.asmx/CheckFrance", 'es': "http://www.regcheck.org.uk/api/reg.asmx/CheckSpain", 'uk': "http://www.regcheck.org.uk/api/reg.asmx/Check" } - def __init__(self, country: str, registration: str, username: str, **kwargs): + def __init__(self, country: str, registration: str, username: str, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('vehicle', **kwargs) + if country not in self.country_urls: + raise ValueError(f"Country {country} not supportet, must be one of {self.country_urls.keys()}") self._country = country self._registration = registration self._username = username @@ -26,10 +32,10 @@ class VehicleObject(AbstractMISPObjectGenerator): self.generate_attributes() @property - def report(self): + def report(self) -> dict[str, Any]: return self._report - def generate_attributes(self): + def generate_attributes(self) -> None: carDescription = self._report["Description"] carMake = self._report["CarMake"]["CurrentTextValue"] carModel = self._report["CarModel"]["CurrentTextValue"] @@ -65,14 +71,14 @@ class VehicleObject(AbstractMISPObjectGenerator): self.add_attribute('date-first-registration', type='text', value=firstRegistration) self.add_attribute('image-url', type='text', value=ImageUrl) - def _query(self): - payload = "RegistrationNumber={}&username={}".format(self._registration, self._username) + def _query(self) -> dict[str, Any]: + payload = f"RegistrationNumber={self._registration}&username={self._username}" headers = { 'Content-Type': "application/x-www-form-urlencoded", 'cache-control': "no-cache", } - response = requests.request("POST", self.country_urls.get(self._country), data=payload, headers=headers) + response = requests.request("POST", self.country_urls[self._country], data=payload, headers=headers) # FIXME: Clean that up. for item in response.text.split(""): if "" in item: diff --git a/pymisp/tools/vtreportobject.py b/pymisp/tools/vtreportobject.py index 4974fdb..47f917d 100644 --- a/pymisp/tools/vtreportobject.py +++ b/pymisp/tools/vtreportobject.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations import re -from typing import Optional +from typing import Any import requests try: @@ -24,7 +25,7 @@ class VTReportObject(AbstractMISPObjectGenerator): :indicator: IOC to search VirusTotal for ''' - def __init__(self, apikey: str, indicator: str, vt_proxies: Optional[dict] = None, **kwargs): + def __init__(self, apikey: str, indicator: str, vt_proxies: dict[str, str] | None = None, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__('virustotal-report', **kwargs) indicator = indicator.strip() self._resource_type = self.__validate_resource(indicator) @@ -33,20 +34,20 @@ class VTReportObject(AbstractMISPObjectGenerator): self._report = self.__query_virustotal(apikey, indicator) self.generate_attributes() else: - error_msg = "A valid indicator is required. (One of type url, md5, sha1, sha256). Received '{}' instead".format(indicator) + error_msg = f"A valid indicator is required. (One of type url, md5, sha1, sha256). Received '{indicator}' instead" raise InvalidMISPObject(error_msg) - def get_report(self): + def get_report(self) -> dict[str, Any]: return self._report - def generate_attributes(self): + def generate_attributes(self) -> None: ''' Parse the VirusTotal report for relevant attributes ''' self.add_attribute("last-submission", value=self._report["scan_date"]) self.add_attribute("permalink", value=self._report["permalink"]) ratio = "{}/{}".format(self._report["positives"], self._report["total"]) self.add_attribute("detection-ratio", value=ratio) - def __validate_resource(self, ioc: str): + def __validate_resource(self, ioc: str) -> str | bool: ''' Validate the data type of an indicator. Domains and IP addresses aren't supported because @@ -62,7 +63,7 @@ class VTReportObject(AbstractMISPObjectGenerator): return "file" return False - def __query_virustotal(self, apikey: str, resource: str): + def __query_virustotal(self, apikey: str, resource: str) -> dict[str, Any]: ''' Query VirusTotal for information about an indicator @@ -70,7 +71,7 @@ class VTReportObject(AbstractMISPObjectGenerator): :resource: Indicator to search in VirusTotal ''' - url = "https://www.virustotal.com/vtapi/v2/{}/report".format(self._resource_type) + url = f"https://www.virustotal.com/vtapi/v2/{self._resource_type}/report" params = {"apikey": apikey, "resource": resource} # for now assume we're using a public API key - we'll figure out private keys later if self._proxies: diff --git a/pyproject.toml b/pyproject.toml index 1258197..0b8a208 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pymisp" -version = "2.4.178" +version = "2.4.185" description = "Python API for MISP." authors = ["Raphaël Vinot "] license = "BSD-2-Clause" @@ -21,6 +21,7 @@ classifiers=[ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Security', 'Topic :: Internet' ] @@ -43,21 +44,20 @@ include = [ python = "^3.8" requests = "^2.31.0" python-dateutil = "^2.8.2" -jsonschema = ">=4.17.3" deprecated = "^1.2.14" -extract_msg = {version = "^0.45.0", optional = true} -RTFDE = {version = "^0.1.0", optional = true} +extract_msg = {version = "^0.47.0", optional = true} +RTFDE = {version = "^0.1.1", optional = true} oletools = {version = "^0.60.1", optional = true} python-magic = {version = "^0.4.27", optional = true} pydeep2 = {version = "^0.5.1", optional = true} -lief = {version = "^0.13.2", optional = true} -beautifulsoup4 = {version = "^4.12.2", optional = true} +lief = {version = "^0.14.1", optional = true} +beautifulsoup4 = {version = "^4.12.3", optional = true} validators = {version = "^0.22.0", optional = true} -sphinx-autodoc-typehints = {version = "^1.24.0", optional = true} +sphinx-autodoc-typehints = {version = "^2.0.0", optional = true} recommonmark = {version = "^0.7.1", optional = true} -reportlab = {version = "^4.0.6", optional = true} +reportlab = {version = "^4.1.0", optional = true} pyfaup = {version = "^1.2", optional = true} -publicsuffixlist = {version = "^0.10.0.20231022", optional = true} +publicsuffixlist = {version = "^0.10.0.20231214", optional = true} urllib3 = {extras = ["brotli"], version = "*", optional = true} Sphinx = [ {version = "<7.2", python = "<3.9", optional = true}, @@ -76,15 +76,16 @@ brotli = ['urllib3'] [tool.poetry.group.dev.dependencies] requests-mock = "^1.11.0" -mypy = "^1.6.1" +mypy = "^1.8.0" ipython = [ {version = "<8.13.0", python = "<3.9"}, - {version = "^8.13.0", python = ">=3.9"} + {version = "^8.18.0", python = ">=3.9"}, + {version = "^8.19.0", python = ">=3.10"} ] -jupyterlab = "^4.0.7" -types-requests = "^2.31.0.10" -types-python-dateutil = "^2.8.19.14" -types-redis = "^4.6.0.7" +jupyterlab = "^4.1.2" +types-requests = "^2.31.0.20240218" +types-python-dateutil = "^2.8.19.20240106" +types-redis = "^4.6.0.20240218" types-Flask = "^1.1.6" pytest-cov = "^4.1.0" diff --git a/tests/email_testfiles/mail_1.eml b/tests/email_testfiles/mail_1.eml deleted file mode 100644 index d2ae7e9..0000000 --- a/tests/email_testfiles/mail_1.eml +++ /dev/null @@ -1,858 +0,0 @@ -Return-Path: -Delivered-To: kinney@noth.com -Received: (qmail 11769 invoked from network); 22 Aug 2016 14:23:01 -0000 -Received: from smtprelay0207.b.hostedemail.com (HELO smtprelay.b.hostedemail.com) (64.98.42.207) - by smtp.server.net with SMTP; 22 Aug 2016 14:23:01 -0000 -Received: from filter.hostedemail.com (10.5.19.248.rfc1918.com [10.5.19.248]) - by smtprelay06.b.hostedemail.com (Postfix) with ESMTP id 2CC378D014 - for ; Mon, 22 Aug 2016 14:22:58 +0000 (UTC) -Received: from DM6PR06MB4475.namprd06.prod.outlook.com (2603:10b6:207:3d::31) - by BL0PR06MB4465.namprd06.prod.outlook.com with HTTPS id 12345 via - BL0PR02CA0054.NAMPRD02.PROD.OUTLOOK.COM; Mon, 1 Oct 2018 09:49:22 +0000 -Received: from DM3NAM03FT035.eop-NAM03.prod.protection.outlook.com - (2a01:111:f400:7e49::205) by CY4PR0601CA0051.outlook.office365.com - (2603:10b6:910:89::28) with Microsoft SMTP Server (version=TLS1_2, - cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) id 15.20.1185.23 via Frontend - Transport; Mon, 1 Oct 2018 09:49:21 +0000 -X-Session-Marker: 6A64617A657940616C6578616E646572736D6974682E636F6D -X-Spam-Summary: 69,4.5,0,,d41d8cd98f00b204,suvorov.s@nalg.ru,:,RULES_HIT:46:150:152:379:553:871:967:989:1000:1254:1260:1263:1313:1381:1516:1517:1520:1575:1594:1605:1676:1699:1730:1747:1764:1777:1792:1823:2044:2197:2199:2393:2525:2560:2563:2682:2685:2827:2859:2911:2933:2937:2939:2942:2945:2947:2951:2954:3022:3867:3872:3890:3934:3936:3938:3941:3944:3947:3950:3953:3956:3959:4425:5007:6001:6261:6506:6678:6747:6748:7281:7398:7688:8599:8824:8957:9009:9025:9388:10004:10848:11604:11638:11639:11783:11914:12043:12185:12445:12517:12519:12740:13026:14149:14381:14658:14659:14687:21080:21221:30054:30055:30065:30066,0,RBL:none,CacheIP:none,Bayesian:0.5,0.5,0.5,Netcheck:none,DomainCache:0,MSF:not bulk,SPF:fn,MSBL:0,DNSBL:none,Custom_rules:0:0:0,LFtime:5,LUA_SUMMARY:none -X-HE-Tag: print38_7083d7fd63e24 -X-Filterd-Recvd-Size: 64695 -Received: from computer_3436 (unknown [43.230.105.145]) - (Authenticated sender: jdazey@alexandersmith.com) - by omf06.b.hostedemail.com (Postfix) with ESMTPA - for ; Mon, 22 Aug 2016 14:22:52 +0000 (UTC) -From: =?UTF-8?B?0YHQu9GD0LbQsdCwINCk0J3QoSDQlNCw0L3QuNC40Lsg0KHRg9Cy0L7RgNC+0LI=?= -To: kinney@noth.com -Subject: =?UTF-8?B?0L/QuNGB0YzQvNC+INGD0LLQtdC00L7QvC3QtQ==?= -Content-Type: multipart/mixed; boundary="2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7" - ---2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: base64 - -0J3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LohPGJyPg0K0JjQvdGE0L7RgNC80LjRgNGD -0LXQvCDQktCw0YEg0L7QsSDQuNC80LXRjtGJ0LXQudGB0Y8g0LfQsNC00L7Qu9C20LXQvdC90L7R -gdGC0LguPHA+DQrQn9GA0L7RgdGM0LHQsCDQvtC30L3QsNC60L7QvNC40YLRjNGB0Y8g0LIg0L/R -gNC40LvQvtC20LXQvdC40LguPHA+DQo8YnI+DQo8YnI+DQrQoSDRg9Cy0LDQttC10L3QuNC10Lws -PHA+DQrQuNC90YHQv9C10LrRgtC+0YAg0KTQndChINCg0KQg0JXQs9C+0YAg0KHRg9Cy0L7RgNC+ -0LIuPGJyPg0KPHA+DQo8YnI+DQo8cD4NCjxwPg0K0JjQvdGE0L7RgNC80LDRhtC40L7QvdC90YvQ -uSDRgNCw0LfQtNC10Ls8YnI+DQo8YnI+DQrQn9GA0LXQt9C40LTQtdC90YIg0L/QvtGA0YPRh9C4 -0Lsg0L/RgNCw0LLQuNGC0LXQu9GM0YHRgtCy0YMg0LfQsNC60YDQtdC/0LjRgtGMINCyINC30LDQ -utC+0L3QtSDRgNC40YHQui3QvtGA0LjQtdC90YLQuNGA0L7QstCw0L3QvdGL0Lkg0L/QvtC00YXQ -vtC0INC6INC/0YDQvtCy0LXRgNC60LDQvDxwPg0KPHA+DQoxNSDQsNCy0LPRg9GB0YLQsCAyMDE2 -PGJyPg0K0JLQsNC70LXRgNC40Y8g0JfQtdC90L7QstC40L3QsDxicj4NCjxwPg0K0J/RgNC10LfQ -uNC00LXQvdGCINC/0L7RgNGD0YfQuNC7INC/0YDQsNCy0LjRgtC10LvRjNGB0YLQstGDINC30LDQ -utGA0LXQv9C40YLRjCDQsiDQt9Cw0LrQvtC90LUg0YDQuNGB0Lot0L7RgNC40LXQvdGC0LjRgNC+ -0LLQsNC90L3Ri9C5INC/0L7QtNGF0L7QtCDQuiDQv9GA0L7QstC10YDQutCw0LzQktCy0LXRgdGC -0Lgg0YLQsNC60L7QuSDQv9C+0LTRhdC+0LQg0L/RgNC4INC+0YDQs9Cw0L3QuNC30LDRhtC40Lgg -0LrQvtC90YLRgNC+0LvRjNC90L4t0L3QsNC00LfQvtGA0L3Ri9GFINC80LXRgNC+0L/RgNC40Y/R -gtC40Lkg0Lgg0LIg0YbQtdC70L7QvCDQvtC/0YLQuNC80LjQt9C40YDQvtCy0LDRgtGMINC/0L7Q -tNC+0LHQvdGL0LUg0LzQtdGA0L7Qv9GA0LjRj9GC0LjRjyDQvdCwINGA0LXQs9C40L7QvdCw0LvR -jNC90L7QvCDQuCDQvNGD0L3QuNGG0LjQv9Cw0LvRjNC90L7QvCDRg9GA0L7QstC90Y/RhSDQn9GA -0LXQt9C40LTQtdC90YIg0KDQpCDQktC70LDQtNC40LzQuNGAINCf0YPRgtC40L0g0L/QvtGA0YPR -h9C40Lsg0LrQsNCx0LzQuNC90YMg0LTQviAzMSDQtNC10LrQsNCx0YDRjyDRgtC10LrRg9GJ0LXQ -s9C+INCz0L7QtNCwLiDQmNC30LzQtdC90LXQvdC40Y8g0LzQvtCz0YPRgiDQutC+0YHQvdGD0YLR -jNGB0Y8g0Lgg0LLQvdC10L/Qu9Cw0L3QvtCy0YvRhSDQv9GA0L7QstC10YDQvtC6LiDQkiDRgdC+ -0L7RgtCy0LXRgtGB0YLQstC40Lgg0YEg0YPQutCw0LfQsNC90LjRj9C80Lgg0L/RgNC10LfQuNC0 -0LXQvdGC0LAsINCT0LXQvdC10YDQsNC70YzQvdCw0Y8g0L/RgNC+0LrRg9GA0LDRgtGD0YDQsCDQ -oNCkINC00L7Qu9C20L3QsCDQstC90LXRgdGC0Lgg0LIg0LfQsNC60L7QvdC+0LTQsNGC0LXQu9GM -0YHRgtCy0L4g0L/QvtC/0YDQsNCy0LrQuCwg0L/RgNC10LTRg9GB0LzQsNGC0YDQuNCy0LDRjtGJ -0LjQtSDRgdC+0LPQu9Cw0YHQvtCy0LDQvdC40LUg0YLQsNC60LjRhSDQv9GA0L7QstC10YDQvtC6 -INGBINC+0YDQs9Cw0L3QsNC80Lgg0L/RgNC+0LrRg9GA0LDRgtGD0YDRiy4g0K3RgtC+INC30LDR -gtGA0L7QvdC10YIg0L/RgNC+0LLQtdGA0LrQuCwg0LjQvdC40YbQuNC40YDRg9C10LzRi9C1INCy -INGB0LLRj9C30Lgg0YEg0L3QsNGA0YPRiNC10L3QuNC10Lwg0L/RgNCw0LIg0L/QvtGC0YDQtdCx -0LjRgtC10LvQtdC5LCDQuCDQvdC10LrQvtGC0L7RgNGL0LUg0LTRgNGD0LPQuNC1LjxwPg0KPGJy -Pg0K0J3QsNGA0Y/QtNGDINGBINGN0YLQuNC8LCDRgNGP0LQg0LfQsNC60L7QvdC+0LTQsNGC0LXQ -u9GM0L3Ri9GFINC40LfQvNC10L3QtdC90LjQuSDQvNC+0LbQtdGCINC60L7RgdC90YPRgtGM0YHR -jyDQtdC00LjQvdC+0LPQviDRgNC10LXRgdGC0YDQsCDQv9GA0L7QstC10YDQvtC6IChwcm92ZXJr -aS5nb3YucnUpLjxwPg0KPGJyPg0K0KLQsNC6LCDQvtGA0LPQsNC90YssINGA0LXQsNC70LjQt9GD -0Y7RidC40LUg0LrQvtC90YLRgNC+0LvRjNC90L4t0L3QsNC00LfQvtGA0L3Ri9C1INC/0L7Qu9C9 -0L7QvNC+0YfQuNGPLCDQtNC+0LvQttC90Ysg0LHRg9C00YPRgiDQv9C+INGB0L7Qs9C70LDRgdC+ -0LLQsNC90LjRjiDRgSDQk9C10L3QtdGA0LDQu9GM0L3QvtC5INC/0YDQvtC60YPRgNCw0YLRg9GA -0L7QuSDQoNCkINGA0LDQt9GA0LDQsdC+0YLQsNGC0Ywg0Lgg0LjQt9C00LDRgtGMINCw0LrRgtGL -LCDRgNC10LPQu9Cw0LzQtdC90YLQuNGA0YPRjtGJ0LjQtSDQv9C+0YDRj9C00L7QuiDQstC90LXR -gdC10L3QuNGPINC40L3RhNC+0YDQvNCw0YbQuNC4INC+INC/0YDQvtCy0LXRgNC60LDRhSDQsiDQ -tdC00LjQvdGL0Lkg0YDQtdC10YHRgtGAINC/0YDQvtCy0LXRgNC+0LouINCQINC30LAg0L3QtdCy -0L3QtdGB0LXQvdC40LUg0YLQsNC60LjRhSDRgdCy0LXQtNC10L3QuNC5INC4INC30LAg0L3QsNGA -0YPRiNC10L3QuNC1INC/0L7RgNGP0LTQutCwINC4INGB0YDQvtC60L7QsiDQuNGFINCy0L3QtdGB -0LXQvdC40Y8g0L/RgNC10LTQu9Cw0LPQsNC10YLRgdGPINGD0YHRgtCw0L3QvtCy0LjRgtGMINCw -0LTQvNC40L3QuNGB0YLRgNCw0YLQuNCy0L3Rg9GOINC+0YLQstC10YLRgdGC0LLQtdC90L3QvtGB -0YLRjC4g0JrQsNC60LjQtSDQuNC80LXQvdC90L4g0L3QsNC60LDQt9Cw0L3QuNGPINC80L7Qs9GD -0YIg0LHRi9GC0Ywg0LLQstC10LTQtdC90Ysg0LIg0JrQvtCQ0J8g0KDQpCDigJMg0L3QtSDRg9GC -0L7Rh9C90Y/QtdGC0YHRjy4g0J7QttC40LTQsNC10YLRgdGPLCDRh9GC0L4g0YPQutCw0LfQsNC9 -0L3Ri9C1INC/0L7RgNGD0YfQtdC90LjRjyDQsdGD0LTRg9GCINC40YHQv9C+0LvQvdC10L3RiyDQ -uiAxINC00LXQutCw0LHRgNGPLjxicj4NCjxwPg0K0JrRgNC+0LzQtSDRgtC+0LPQviwg0LTQviAx -NSDQtNC10LrQsNCx0YDRjyDQk9C10L3QtdGA0LDQu9GM0L3QvtC5INC/0YDQvtC60YPRgNCw0YLR -g9GA0LUg0KDQpCDQvdC10L7QsdGF0L7QtNC40LzQviDQsdGD0LTQtdGCINC00L7RgNCw0LHQvtGC -0LDRgtGMINC10LTQuNC90YvQuSDRgNC10LXRgdGC0YAg0L/RgNC+0LLQtdGA0L7QuiDRgtCw0Los -INGH0YLQvtCx0Ysg0L/RgNC4INCy0L3QtdGB0LXQvdC40Lgg0LIg0L3QtdCz0L4g0LjQvdGE0L7R -gNC80LDRhtC40Lgg0YHRgtCw0LvQviDQstC+0LfQvNC+0LbQvdGL0Lwg0LjRgdC/0L7Qu9GM0LfQ -vtCy0LDQvdC40LUg0YHQstC10LTQtdC90LjQuSDQuNC3INC00YDRg9Cz0LjRhSDQs9C+0YHRg9C0 -0LDRgNGB0YLQstC10L3QvdGL0YUg0LjQvdGE0L7RgNC80LDRhtC40L7QvdC90YvRhSDRgdC40YHR -gtC10LwsINGB0L/RgNCw0LLQvtGH0L3QuNC60L7QsiDQuCDQutC70LDRgdGB0LjRhNC40LrQsNGC -0L7RgNC+0LIuINCi0LDQutC20LUg0LPQu9Cw0LLQsCDQs9C+0YHRg9C00LDRgNGB0YLQstCwINGD -0LrQsNC30LDQuywg0YfRgtC+INC90LXQvtCx0YXQvtC00LjQvNCwINGE0L7RgNC80LDQu9C40LfQ -sNGG0LjRjyDQuCDQutC+0L3QutGA0LXRgtC40LfQsNGG0LjRjyDQstC90L7RgdC40LzQvtC5INCy -INGA0LXQtdGB0YLRgCDQuNC90YTQvtGA0LzQsNGG0LjQuCwg0LXRgdC70Lgg0L7QvdCwINC60LDR -gdCw0LXRgtGB0Y8g0L/RgNCw0LLQvtCy0YvRhSDQvtGB0L3QvtCy0LDQvdC40Lkg0LTQu9GPINC+ -0YDQs9Cw0L3QuNC30LDRhtC40Lgg0L/RgNC+0LLQtdGA0L7Qui4g0KDQtdGH0Ywg0LjQtNC10YIg -0Lgg0L4g0YTQvtGA0LzQsNC70LjQt9Cw0YbQuNC4INGC0YDQtdCx0L7QstCw0L3QuNC5LCDRgdC+ -0LHQu9GO0LTQtdC90LjQtSDQutC+0YLQvtGA0YvRhSDQvtGG0LXQvdC40LLQsNC10YLRgdGPINC/ -0YDQuCDQv9GA0L7QstC10LTQtdC90LjQuCDRgdC+0L7RgtCy0LXRgtGB0YLQstGD0Y7RidC40YUg -0LzQtdGA0L7Qv9GA0LjRj9GC0LjQuSwg0LAg0YLQsNC60LbQtSDRgNC10LfRg9C70YzRgtCw0YLQ -vtCyINC/0YDQvtCy0LXRgNC+0Log0Lgg0L/RgNC40L3Rj9GC0YvRhSDQvNC10YAuPHA+DQo8YnI+ -DQrQn9C+0LzQuNC80L4g0Y3RgtC+0LPQviDQv9GA0LXQt9C40LTQtdC90YIg0L/QvtGA0YPRh9C4 -0Lsg0L/RgNCw0LLQuNGC0LXQu9GM0YHRgtCy0YMg0YDQsNGB0YHQvNC+0YLRgNC10YLRjCDQstC+ -0L/RgNC+0YEg0L4g0YHRgtC40LzRg9C70LjRgNGD0Y7RidC40YUg0LLRi9C/0LvQsNGC0LDRhSDQ -tNC70Y8g0YHQvtGC0YDRg9C00L3QuNC60L7QsiDRhNC10LTQtdGA0LDQu9GM0L3Ri9GFINC40YHQ -v9C+0LvQvdC40YLQtdC70YzQvdGL0YUg0L7RgNCz0LDQvdC+0LIsINC60L7RgtC+0YDRi9C1INC+ -0YHRg9GJ0LXRgdGC0LLQu9GP0Y7RgiDQv9GA0L7QstC10YDQutC4LjxwPg0KPGJyPg0K0J/QviDR -gNC10LfRg9C70YzRgtCw0YLQsNC8INGN0LvQtdC60YLRgNC+0L3QvdC+0LPQviDQsNGD0LrRhtC4 -0L7QvdCwINC30LDQutC70Y7Rh9Cw0YLRjCDQutC+0L3RgtGA0LDQutGCINC90LAg0LHRg9C80LDQ -s9C1INC90LUg0L3Rg9C20L3Qvjxicj4NCjxwPg0KMTIg0LDQstCz0YPRgdGC0LAgMjAxNjxicj4N -CtCS0LDQu9C10YDQuNGPINCX0LXQvdC+0LLQuNC90LA8cD4NCjxicj4NCtCf0L4g0YDQtdC30YPQ -u9GM0YLQsNGC0LDQvCDRjdC70LXQutGC0YDQvtC90L3QvtCz0L4g0LDRg9C60YbQuNC+0L3QsCDQ -t9Cw0LrQu9GO0YfQsNGC0Ywg0LrQvtC90YLRgNCw0LrRgiDQvdCwINCx0YPQvNCw0LPQtSDQvdC1 -INC90YPQttC90L7QnNC40L3RjdC60L7QvdC+0LzRgNCw0LfQstC40YLQuNGPINGA0LDQt9GK0Y/R -gdC90LjQu9C+LCDRh9GC0L4g0L/QviDRgNC10LfRg9C70YzRgtCw0YLQsNC8INGN0LvQtdC60YLR -gNC+0L3QvdC+0LPQviDQsNGD0LrRhtC40L7QvdCwINC/0YDQuCDQvdCw0LvQuNGH0LjQuCDQt9Cw -0LrQu9GO0YfQtdC90L3QvtCz0L4g0LrQvtC90YLRgNCw0LrRgtCwINCyINGN0LvQtdC60YLRgNC+ -0L3QvdC+0Lkg0YTQvtGA0LzQtSDQt9Cw0LrQu9GO0YfQsNGC0Ywg0LrQvtC90YLRgNCw0LrRgiDQ -tdGJ0LUg0Lgg0LIg0L/QuNGB0YzQvNC10L3QvdC+0Lkg0YTQvtGA0LzQtSDQvdCwINCx0YPQvNCw -0LbQvdC+0Lwg0L3QvtGB0LjRgtC10LvQtSDQvdC1INC90YPQttC90L4uINCS0LXQtNC+0LzRgdGC -0LLQviDQv9C+0LTRh9C10YDQutC90YPQu9C+LCDRh9GC0L4g0YLQsNC60LDRjyDQvdC10L7QsdGF -0L7QtNC40LzQvtGB0YLRjCDQvdC1INC/0YDQtdC00YPRgdC80L7RgtGA0LXQvdCwINC00LXQudGB -0YLQstGD0Y7RidC40Lwg0LfQsNC60L7QvdC+0LTQsNGC0LXQu9GM0YHRgtCy0L7QvCAo0L/QuNGB -0YzQvNC+INCc0LjQvdGN0LrQvtC90L7QvNGA0LDQt9Cy0LjRgtC40Y8g0KDQvtGB0YHQuNC4INC+ -0YIgNSDQuNGO0LvRjyAyMDE2INCzLiDihJYg0JQyONC4LTE2ODcpLjxwPg0KPHA+DQrQodC10LPQ -vtC00L3RjyDQtNC70Y8g0YLQvtCz0L4sINGH0YLQvtCx0Ysg0LfQsNC60LvRjtGH0LjRgtGMINGN -0LvQtdC60YLRgNC+0L3QvdGL0Lkg0LrQvtC90YLRgNCw0LrRgiDQvdCwINGN0LvQtdC60YLRgNC+ -0L3QvdC+0Lwg0LDRg9C60YbQuNC+0L3QtSwg0LXQs9C+INC90LXQvtCx0YXQvtC00LjQvNC+INGA -0LDQt9C80LXRgdGC0LjRgtGMINCyINC10LTQuNC90L7QuSDQuNC90YTQvtGA0LzQsNGG0LjQvtC9 -0L3QvtC5INGB0LjRgdGC0LXQvNC1ICjQldCY0KEpLCDQv9GA0LjRh9C10Lwg0L7QvSDQtNC+0LvQ -ttC10L0g0LHRi9GC0Ywg0L/QvtC00L/QuNGB0LDQvSDRg9GB0LjQu9C10L3QvdC+0Lkg0Y3Qu9C1 -0LrRgtGA0L7QvdC90L7QuSDQv9C+0LTQv9C40YHRjNGOINC70LjRhtCwLCDQuNC80LXRjtGJ0LXQ -s9C+INC/0YDQsNCy0L4g0LTQtdC50YHRgtCy0L7QstCw0YLRjCDQvtGCINC40LzQtdC90Lgg0LfQ -sNC60LDQt9GH0LjQutCwLiDQodC00LXQu9Cw0YLRjCDRjdGC0L4g0L3QtdC+0LHRhdC+0LTQuNC8 -0L4g0LIg0YLQtdGH0LXQvdC40LUg0YLRgNC10YUg0YDQsNCx0L7Rh9C40YUg0LTQvdC10Lkg0YEg -0LTQsNGC0Ysg0YDQsNC30LzQtdGJ0LXQvdC40Y8g0L/RgNC+0LXQutGC0LAg0YLQvtCz0L4g0LbQ -tSDQutC+0L3RgtGA0LDQutGC0LAuPGJyPg0KPGJyPg0K0JIg0LrQsNC60LjRhSDRgdC70YPRh9Cw -0Y/RhSDQt9Cw0LrQsNC30YfQuNC6INC+0LHRj9C30LDQvSDQv9GA0L7QstC+0LTQuNGC0Ywg0Y3Q -u9C10LrRgtGA0L7QvdC90YvQuSDQsNGD0LrRhtC40L7QvT8g0KPQt9C90LDQudGC0LUg0LjQtyDQ -vNCw0YLQtdGA0LjQsNC70LAgItCj0YHQu9C+0LLQuNGPINC/0YDQuNC80LXQvdC10L3QuNGPINGN -0LvQtdC60YLRgNC+0L3QvdC+0LPQviDQsNGD0LrRhtC40L7QvdCwIiDQsiAi0K3QvdGG0LjQutC7 -0L7Qv9C10LTQuNC4INGA0LXRiNC10L3QuNC5LiDQk9C+0YHRg9C00LDRgNGB0YLQstC10L3QvdGL -0LUg0Lgg0LrQvtGA0L/QvtGA0LDRgtC40LLQvdGL0LUg0LfQsNC60YPQv9C60LgiINC40L3RgtC1 -0YDQvdC10YIt0LLQtdGA0YHQuNC4INGB0LjRgdGC0LXQvNGLINCT0JDQoNCQ0J3Qoi4g0J/QvtC7 -0YPRh9C40YLQtSDQv9C+0LvQvdGL0Lkg0LTQvtGB0YLRg9C/INC90LAgMyDQtNC90Y8g0LHQtdGB -0L/Qu9Cw0YLQvdC+ITxwPg0K0J/QvtC70YPRh9C40YLRjCDQtNC+0YHRgtGD0L88cD4NCtCj0LrQ -sNC30LDQvdC90YvQuSDQv9GA0L7QtdC60YIg0L/RgNC4INGN0YLQvtC8INGC0L7QttC1INC00L7Q -u9C20LXQvSDQsdGL0YLRjCDQv9C+0LTQv9C40YHQsNC9INGD0YHQuNC70LXQvdC90L7QuSDRjdC7 -0LXQutGC0YDQvtC90L3QvtC5INC/0L7QtNC/0LjRgdGM0Y4sINC90L4g0YPQttC1INC70LjRhtCw -LCDQutC+0YLQvtGA0L7QtSDQuNC80LXQtdGCINC/0YDQsNCy0L4g0LTQtdC50YHRgtCy0L7QstCw -0YLRjCDQvtGCINC40LzQtdC90Lgg0L/QvtCx0LXQtNC40YLQtdC70Y8g0Y3Qu9C10LrRgtGA0L7Q -vdC90L7Qs9C+INCw0YPQutGG0LjQvtC90LAuINCf0L7QvNC40LzQviDQv9GA0L7QtdC60YLQsCDQ -utC+0L3RgtGA0LDQutGC0LAg0YLQsNC60L7QuSDQv9C+0LHQtdC00LjRgtC10LvRjCDQtNC+0LvQ -ttC10L0g0L/RgNC10LTQvtGB0YLQsNCy0LjRgtGMINC+0LHQtdGB0L/QtdGH0LXQvdC40LUg0LjR -gdC/0L7Qu9C90LXQvdC40Y8g0LrQvtC90YLRgNCw0LrRgtCwICjRhy4gNyDRgdGCLiA3MCDQpNC1 -0LTQtdGA0LDQu9GM0L3QvtCz0L4g0LfQsNC60L7QvdCwINC+0YIgNSDQsNC/0YDQtdC70Y8gMjAx -MyDQsy4g4oSWIDQ0LdCk0JcgItCeINC60L7QvdGC0YDQsNC60YLQvdC+0Lkg0YHQuNGB0YLQtdC8 -0LUg0LIg0YHRhNC10YDQtSDQt9Cw0LrRg9C/0L7QuiDRgtC+0LLQsNGA0L7Qsiwg0YDQsNCx0L7R -giwg0YPRgdC70YPQsyDQtNC70Y8g0L7QsdC10YHQv9C10YfQtdC90LjRjyDQs9C+0YHRg9C00LDR -gNGB0YLQstC10L3QvdGL0YUg0Lgg0LzRg9C90LjRhtC40L/QsNC70YzQvdGL0YUg0L3Rg9C20LQi -OyDQtNCw0LvQtdC1IOKAkyDQl9Cw0LrQvtC9IOKEliA0NC3QpNCXKS48cD4NCjxicj4NCtChINC8 -0L7QvNC10L3RgtCwINGA0LDQt9C80LXRidC10L3QuNGPINCyINCV0JjQoSDQv9C+0LTQv9C40YHQ -sNC90L3QvtCz0L4g0LfQsNC60LDQt9GH0LjQutC+0Lwg0LrQvtC90YLRgNCw0LrRgtCwINC+0L0g -0YHRh9C40YLQsNC10YLRgdGPINC30LDQutC70Y7Rh9C10L3QvdGL0LwgKNGHLiA4INGB0YIuIDcw -INCX0LDQutC+0L3QsCDihJYgNDQt0KTQlykuPGJyPg0KPGJyPg0K0J3QsNC/0L7QvNC90LjQvCwg -0L/QviDQtNC10LnRgdGC0LLRg9GO0YnQtdC80YMg0LfQsNC60L7QvdC+0LTQsNGC0LXQu9GM0YHR -gtCy0YMsINGN0LvQtdC60YLRgNC+0L3QvdGL0Lkg0LDRg9C60YbQuNC+0L0g0L/RgNC+0LLQvtC0 -0LjRgtGB0Y8g0L/Rg9GC0LXQvCDRgdC90LjQttC10L3QuNGPINC90LDRh9Cw0LvRjNC90L7QuSAo -0LzQsNC60YHQuNC80LDQu9GM0L3QvtC5KSDRhtC10L3RiyDQutC+0L3RgtGA0LDQutGC0LAsINGD -0LrQsNC30LDQvdC90L7QuSDQsiDQuNC30LLQtdGJ0LXQvdC40Lgg0L4g0L/RgNC+0LLQtdC00LXQ -vdC40Lgg0YLQsNC60L7Qs9C+INCw0YPQutGG0LjQvtC90LAuINCSINC90LXQvCDQvNC+0LPRg9GC -INGD0YfQsNGB0YLQstC+0LLQsNGC0Ywg0YLQvtC70YzQutC+INCw0LrQutGA0LXQtNC40YLQvtCy -0LDQvdC90YvQtSDRg9GH0LDRgdGC0L3QuNC60LgsINCwINC80LXRgdGC0L7QvCDQtdCz0L4g0L/R -gNC+0LLQtdC00LXQvdC40Y8g0Y/QstC70Y/QtdGC0YHRjyDRjdC70LXQutGC0YDQvtC90L3QsNGP -INC/0LvQvtGJ0LDQtNC60LAgKNGB0YIuIDY4INCX0LDQutC+0L3QsCDihJYgNDQt0KTQlykuPGJy -Pg0KPHA+DQo1INC00LXQutCw0LHRgNGPIDIwMTQg0LPQvtC00LAg0YHQvtGB0YLQvtGP0LvQvtGB -0Ywg0LjQvdGC0LXRgNC90LXRgi3QuNC90YLQtdGA0LLRjNGOINGBINC90LDRh9Cw0LvRjNC90LjQ -utC+0Lwg0KPQv9GA0LDQstC70LXQvdC40Y8g0LrQsNC80LXRgNCw0LvRjNC90L7Qs9C+INC60L7Q -vdGC0YDQvtC70Y8g0KTQtdC00LXRgNCw0LvRjNC90L7QuSDQvdCw0LvQvtCz0L7QstC+0Lkg0YHQ -u9GD0LbQsdGLINCh0LDRgtC40L3Ri9C8INCU0LzQuNGC0YDQuNC10Lwg0KHRgtCw0L3QuNGB0LvQ -sNCy0L7QstC40YfQtdC8Ljxicj4NCjxicj4NCtCi0LXQvNCwINC40L3RgtC10YDQvdC10YIt0LjQ -vdGC0LXRgNCy0YzRjjogItCd0LDQu9C+0LMg0L3QsCDQtNC+0LHQsNCy0LvQtdC90L3Rg9GOINGB -0YLQvtC40LzQvtGB0YLRjDog0L3QvtCy0LDRhtC40LggMjAxNSDQs9C+0LTQsCIuPGJyPg0KPHA+ -DQrQktC10LTRg9GJ0LDRjzog0JTQvtCx0YDRi9C5INC00LXQvdGMLCDQlNC80LjRgtGA0LjQuSDQ -odGC0LDQvdC40YHQu9Cw0LLQvtCy0LjRhyEg0KDQsNGB0YHQutCw0LbQuNGC0LUsINC/0L7QttCw -0LvRg9C50YHRgtCwLCDQutCw0LrQuNC1INC90L7QstCw0YbQuNC4LCDRgdCy0Y/Qt9Cw0L3QvdGL -0LUg0YEg0L/RgNC10LTRgdGC0LDQstC70LXQvdC40LXQvCDQvtGC0YfQtdGC0L3QvtGB0YLQuCDQ -v9C+INCd0JTQoSwg0L7QttC40LTQsNGO0YIg0L3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC4 -0LrQvtCyINCyIDIwMTUg0LPQvtC00YM/PGJyPg0KPGJyPg0K0KHQsNGC0LjQvSDQlC7QoS46INCU -0L7QsdGA0YvQuSDQtNC10L3RjCEg0J3QsNGH0LjQvdCw0Y8g0YEg0L3QsNC70L7Qs9C+0LLQvtCz -0L4g0L/QtdGA0LjQvtC00LAg0LfQsCAxINC60LLQsNGA0YLQsNC7IDIwMTUg0LPQvtC00LAg0L3Q -sCDQvtGB0L3QvtCy0LDQvdC40Lgg0L/Rg9C90LrRgtCwIDUuMSDRgdGC0LDRgtGM0LggMTc0INCa -0L7QtNC10LrRgdCwICjQsiDRgNC10LTQsNC60YbQuNC4INCk0LXQtNC10YDQsNC70YzQvdC+0LPQ -viDQt9Cw0LrQvtC90LAg0L7RgiAyOC4wNi4yMDEzIOKEliAxMzQt0KTQlykg0LIg0L3QsNC70L7Q -s9C+0LLRg9GOINC00LXQutC70LDRgNCw0YbQuNGOINC/0L4g0J3QlNChINCy0LrQu9GO0YfQsNGO -0YLRgdGPINGB0LLQtdC00LXQvdC40Y8sINGD0LrQsNC30LDQvdC90YvQtSDQsiDQutC90LjQs9C1 -INC/0L7QutGD0L/QvtC6INC4INC60L3QuNCz0LUg0L/RgNC+0LTQsNC2LiDQn9GA0Lgg0L7RgdGD -0YnQtdGB0YLQstC70LXQvdC40Lgg0L/QvtGB0YDQtdC00L3QuNGH0LXRgdC60L7QuSDQtNC10Y/R -gtC10LvRjNC90L7RgdGC0Lgg0LIg0L3QsNC70L7Qs9C+0LLRg9GOINC00LXQutC70LDRgNCw0YbQ -uNGOINC/0L4g0J3QlNChINCy0LrQu9GO0YfQsNGO0YLRgdGPINGB0LLQtdC00LXQvdC40Y8sINGD -0LrQsNC30LDQvdC90YvQtSDQsiDQttGD0YDQvdCw0LvQtSDRg9GH0LXRgtCwINC/0L7Qu9GD0YfQ -tdC90L3Ri9GFINC4INCy0YvRgdGC0LDQstC70LXQvdC90YvRhSDRgdGH0LXRgtC+0LIt0YTQsNC6 -0YLRg9GALCDQsiDQvtGC0L3QvtGI0LXQvdC40Lgg0YPQutCw0LfQsNC90L3QvtC5INC00LXRj9GC -0LXQu9GM0L3QvtGB0YLQuC48YnI+DQo8YnI+DQrQn9GA0LjQutCw0LfQvtC8INCk0J3QoSDQoNC+ -0YHRgdC40Lgg0L7RgiAyOS4xMC4yMDE0IOKEliDQnNCc0JItNy0zLzU1OEAg0YPRgtCy0LXRgNC2 -0LTQtdC90LAg0YTQvtGA0LzQsCDQvdCw0LvQvtCz0L7QstC+0Lkg0LTQtdC60LvQsNGA0LDRhtC4 -0Lgg0L/QviDQndCU0KEsINC/0L7RgNGP0LTQvtC6INC10LUg0LfQsNC/0L7Qu9C90LXQvdC40Y8g -0Lgg0YTQvtGA0LzQsNGCINC/0YDQtdC00YHRgtCw0LLQu9C10L3QuNGPINCyINGN0LvQtdC60YLR -gNC+0L3QvdC+0Lkg0YTQvtGA0LzQtS4g0JIg0L3QsNGB0YLQvtGP0YnQtdC1INCy0YDQtdC80Y8g -0L/RgNC40LrQsNC3INC/0YDQvtGF0L7QtNC40YIg0LPQvtGB0YPQtNCw0YDRgdGC0LLQtdC90L3R -g9GOINGA0LXQs9C40YHRgtGA0LDRhtC40Y4g0LIg0JzQuNC90Y7RgdGC0LUg0KDQvtGB0YHQuNC4 -LjxwPg0KPHA+DQrQkiDQvdC+0LLQvtC5INGE0L7RgNC80LUg0L3QsNC70L7Qs9C+0LLQvtC5INC0 -0LXQutC70LDRgNCw0YbQuNC4INC/0L4g0J3QlNChINC/0YDQtdC00YPRgdC80L7RgtGA0LXQvdGL -INGA0LDQt9C00LXQu9GLLCDRgdC+0LTQtdGA0LbQsNGJ0LjQtSDRgdCy0LXQtNC10L3QuNGPINC4 -0Lcg0LrQvdC40LMg0L/QvtC60YPQv9C+0LosINC60L3QuNCzINC/0YDQvtC00LDQtiwg0LbRg9GA -0L3QsNC70L7QsiDRg9GH0LXRgtCwINC/0L7Qu9GD0YfQtdC90L3Ri9GFINC4INCy0YvRgdGC0LDQ -stC70LXQvdC90YvRhSDRgdGH0LXRgtC+0LIt0YTQsNC60YLRg9GALjxicj4NCjxicj4NCtCSINGB -0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgSDQv9GD0L3QutGC0L7QvCAzINGB0YLQsNGC0YzQuCA4 -MCDQuCDQv9GD0L3QutGC0L7QvCA1INGB0YLQsNGC0YzQuCAxNzQg0JrQvtC00LXQutGB0LAg0L3Q -sNC70L7Qs9C+0LLQsNGPINC00LXQutC70LDRgNCw0YbQuNGPINC/0L4g0J3QlNChINC00L7Qu9C2 -0L3QsCDQv9GA0LXQtNGB0YLQsNCy0LvRj9GC0YzRgdGPINCyINGN0LvQtdC60YLRgNC+0L3QvdC+ -0Lkg0YTQvtGA0LzQtSDQv9C+INGC0LXQu9C10LrQvtC80LzRg9C90LjQutCw0YbQuNC+0L3QvdGL -0Lwg0LrQsNC90LDQu9Cw0Lwg0YHQstGP0LfQuCDRh9C10YDQtdC3INC+0L/QtdGA0LDRgtC+0YDQ -sCDRjdC70LXQutGC0YDQvtC90L3QvtCz0L4g0LTQvtC60YPQvNC10L3RgtC+0L7QsdC+0YDQvtGC -0LAuINCi0LDQutC40Lwg0L7QsdGA0LDQt9C+0LwsINGE0L7RgNC80LjRgNC+0LLQsNC90LjQtSDQ -vdCw0LvQvtCz0L7QstC+0Lkg0LTQtdC60LvQsNGA0LDRhtC40Lgg0L/QviDQndCU0KEg0L3QsCDQ -sdGD0LzQsNC20L3Ri9GFINC90L7RgdC40YLQtdC70Y/RhSDQt9Cw0LrQvtC90L7QtNCw0YLQtdC7 -0YzRgdGC0LLQvtC8INC+INC90LDQu9C+0LPQsNGFINC4INGB0LHQvtGA0LDRhSDQvdC1INC/0YDQ -tdC00YPRgdC80L7RgtGA0LXQvdC+Ljxicj4NCjxicj4NCtCb0LjRhtCwLCDQvdC1INGP0LLQu9GP -0Y7RidC40LXRgdGPINC90LDQu9C+0LPQvtC/0LvQsNGC0LXQu9GM0YnQuNC60LDQvNC4INCd0JTQ -oSDQuNC70Lgg0L3QsNC70L7Qs9C+0LLRi9C80Lgg0LDQs9C10L3RgtCw0LzQuCDQv9C+INCd0JTQ -oSwg0L3QviDQvtGB0YPRidC10YHRgtCy0LvRj9GO0YnQuNC1INC/0L7RgdGA0LXQtNC90LjRh9C1 -0YHQutGD0Y4g0LTQtdGP0YLQtdC70YzQvdC+0YHRgtGMLCDQtNC+0LvQttC90Ysg0L3QsCDQvtGB -0L3QvtCy0LDQvdC40Lgg0L/Rg9C90LrRgtCwIDUuMiDRgdGC0LDRgtGM0LggMTc0INCa0L7QtNC1 -0LrRgdCwINC/0YDQtdC00YHRgtCw0LLQu9GP0YLRjCDQsiDQvdCw0LvQvtCz0L7QstGL0Lkg0L7R -gNCz0LDQvSDQsiDQvtGC0L3QvtGI0LXQvdC40Lgg0YPQutCw0LfQsNC90L3QvtC5INC00LXRj9GC -0LXQu9GM0L3QvtGB0YLQuCDQttGD0YDQvdCw0Lsg0YPRh9C10YLQsCDQv9C+0LvRg9GH0LXQvdC9 -0YvRhSDQuCDQstGL0YHRgtCw0LLQu9C10L3QvdGL0YUg0YHRh9C10YLQvtCyLdGE0LDQutGC0YPR -gCDQv9C+INCi0JrQoSDRh9C10YDQtdC3INC+0L/QtdGA0LDRgtC+0YDQsCDQrdCU0J4uPHA+DQo8 -cD4NCtCk0LXQtNC10YDQsNC70YzQvdC+0Lkg0L3QsNC70L7Qs9C+0LLQvtC5INGB0LvRg9C20LHQ -vtC5INGA0LDQt9GA0LDQsdC+0YLQsNC90LAg0LHQtdGB0L/Qu9Cw0YLQvdCw0Y8g0L/RgNC+0LPR -gNCw0LzQvNCwICLQndCw0LvQvtCz0L7Qv9C70LDRgtC10LvRjNGJ0LjQuiDQrtCbIiDQtNC70Y8g -0LLQtdC00LXQvdC40Y8g0LrQvdC40LMg0L/QvtC60YPQv9C+0Log0Lgg0L/RgNC+0LTQsNC2LCDQ -sCDRgtCw0LrQttC1INGE0L7RgNC80LjRgNC+0LLQsNC90LjRjyDQvdCw0LvQvtCz0L7QstC+0Lkg -0LTQtdC60LvQsNGA0LDRhtC40Lgg0L/QviDQndCU0KEuINCU0LDQvdC90YvQvCDQv9GA0L7Qs9GA -0LDQvNC80L3Ri9C8INC/0YDQvtC00YPQutGC0L7QvCDQvNC+0LbQtdGCINCy0L7RgdC/0L7Qu9GM -0LfQvtCy0LDRgtGM0YHRjyDQu9GO0LHQvtC5INC90LDQu9C+0LPQvtC/0LvQsNGC0LXQu9GM0YnQ -uNC6INCx0LXRgdC/0LvQsNGC0L3QviDQt9Cw0LPRgNGD0LfQuNCyINC10LPQviDRgSDQvtGE0LjR -htC40LDQu9GM0L3QvtCz0L4g0YHQsNC50YLQsCDQpNCd0KEg0KDQvtGB0YHQuNC4IChodHRwOi8v -d3d3Lm5hbG9nLnJ1L3JuNzcvL3Byb2dyYW0vYWxsL25hbF91bC8pLjxicj4NCjxicj4NCtCS0LXQ -tNGD0YnQsNGPOiDQlNC80LjRgtGA0LjQuSDQodGC0LDQvdC40YHQu9Cw0LLQvtCy0LjRhywg0LXR -gdGC0Ywg0YLQsNC60LDRjyDRgdC40YLRg9Cw0YbQuNGPOiDQtNC+0LHRgNC+0YHQvtCy0LXRgdGC -0L3QsNGPINC60L7QvNC/0LDQvdC40Y8g0L7RgtGA0LDQt9C40LvQsCDQsiDQtNC10LrQu9Cw0YDQ -sNGG0LjQuCDRgNGP0LQg0YHRh9C10YLQvtCyLdGE0LDQutGC0YPRgCwg0L/QviDQutC+0YLQvtGA -0YvQvCDQv9C+0YHRgtCw0LLRidC40Log0L7RgtGH0LXRgtC90L7RgdGC0Ywg0L3QtSDRgdC00LDQ -uyDQstC+0L7QsdGJ0LUuINCl0L7RgtGPINC90LAg0LzQvtC80LXQvdGCINC/0YDQvtCy0LXQtNC1 -0L3QuNGPINGB0LTQtdC70LrQuCDQutC+0L3RgtGA0LDQs9C10L3RgiDQv9C+INC00LDQvdC90YvQ -vCDQstGL0L/QuNGB0LrQuCDQuNC3INCV0JPQoNCu0Jsg0LHRi9C7INCy0L/QvtC70L3QtSDQtNC1 -0LnRgdGC0LLRg9GO0YnQuNC8LiDQmtCw0LrQuNC1INC/0L7RgdC70LXQtNGB0YLQstC40Y8g0L7Q -ttC40LTQsNGO0YIg0L3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LrQsCDQsiDRgtCw0LrQ -vtC8INGB0LvRg9GH0LDQtT8gPHA+DQo8YnI+DQrQodCw0YLQuNC9INCULtChLjog0JXRgdC70Lgg -0LrQvtC90YLRgNCw0LPQtdC90YIg0L3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LrQsCDQ -vdC1INC/0YDQtdC00YHRgtCw0LLQuNC7INC90LDQu9C+0LPQvtCy0YPRjiDQtNC10LrQu9Cw0YDQ -sNGG0LjRjiDQv9C+INCd0JTQoSDQuNC70Lgg0LIg0LXQs9C+INC/0YDQtdC00YHRgtCw0LLQu9C1 -0L3QvdC+0Lkg0LTQtdC60LvQsNGA0LDRhtC40Lgg0L3QtSDQvtGC0YDQsNC20LXQvdGLINC60LDQ -utC40LUt0LvQuNCx0L4g0YHRh9C10YLQsC3RhNCw0LrRgtGD0YDRiywg0YLQviDRgtCw0LrQvtC5 -INC60L7QvdGC0YDQsNCz0LXQvdGCINGB0YLQsNC90L7QstC40YLRgdGPINC+0LHRitC10LrRgtC+ -0Lwg0LLQvdC40LzQsNC90LjRjyDRgdC+INGB0YLQvtGA0L7QvdGLINC90LDQu9C+0LPQvtCy0L7Q -s9C+INC+0YDQs9Cw0L3QsC4g0J3QsCDQv9C10YDQstC+0Lwg0Y3RgtCw0L/QtSDQsiDRgdC70YPR -h9Cw0LUg0L7RgtGB0YPRgtGB0YLQstC40Y8g0L3QsNC70L7Qs9C+0LLQvtC5INC00LXQutC70LDR -gNCw0YbQuNC4INC90LDQu9C+0LPQvtCy0YvQuSDQvtGA0LPQsNC9INCx0YPQtNC10YIg0LjQvNC1 -0YLRjCDQstC+0LfQvNC+0LbQvdC+0YHRgtGMINC/0YDQuNC+0YHRgtCw0L3QvtCy0LjRgtGMINC0 -0LLQuNC20LXQvdC40LUg0LTQtdC90LXQttC90YvRhSDRgdGA0LXQtNGB0YLQsiDQv9C+INGA0LDR -gdGH0LXRgtC90YvQvCDRgdGH0LXRgtCw0LwuINCU0LDQu9C10LUg0L/RgNC+0LLQvtC00LjRgtGB -0Y8g0YPRgdGC0LDQvdC+0LLQu9C10L3QvdGL0Lkg0LPQu9Cw0LLQvtC5IDE0INCd0LDQu9C+0LPQ -vtCy0L7Qs9C+INC60L7QtNC10LrRgdCwINCg0L7RgdGB0LjQudGB0LrQvtC5INCk0LXQtNC10YDQ -sNGG0LjQuCDQutC+0LzQv9C70LXQutGBINC60L7QvdGC0YDQvtC70YzQvdGL0YUg0LzQtdGA0L7Q -v9GA0LjRj9GC0LjQuSDQv9C+INGE0L7RgNC80LjRgNC+0LLQsNC90LjRjiDQtNC+0LrQsNC30LDR -gtC10LvRjNC90L7QuSDQsdCw0LfRiyDQvtCxINGD0LrQu9C+0L3QtdC90LjQuCDQvtGCINC90LDQ -u9C+0LPQvtC+0LHQu9C+0LbQtdC90LjRjy4g0K3RgtC+INC40YHRgtGA0LXQsdC+0LLQsNC90LjQ -tSDQuNC90YTQvtGA0LzQsNGG0LjQuCDQuCDQtNC+0LrRg9C80LXQvdGC0L7Qsiwg0LAg0L/RgNC4 -INC90LXQvtCx0YXQvtC00LjQvNC+0YHRgtC4INC00YDRg9Cz0LjQtSDQvNC10YDQvtC/0YDQuNGP -0YLQuNGPINC90LDQu9C+0LPQvtCy0L7Qs9C+INC60L7QvdGC0YDQvtC70Y8uINCf0YDQuCDRjdGC -0L7QvCDQsiDRgdC70YPRh9Cw0LUg0YPRgdGC0LDQvdC+0LLQu9C10L3QuNGPINGE0LDQutGC0LAg -0YDQtdCw0LvRjNC90L7RgdGC0Lgg0L7Qv9C10YDQsNGG0LjQuCwg0LTQu9GPINGB0LDQvNC+0LPQ -viDQvdCw0LvQvtCz0L7Qv9C70LDRgtC10LvRjNGJ0LjQutCwINC90Lgg0LrQsNC60LjRhSDQv9C+ -0YHQu9C10LTRgdGC0LLQuNC5INC90LUg0LHRg9C00LXRgi4g0Jog0L7RgtCy0LXRgtGB0YLQstC1 -0L3QvdC+0YHRgtC4INCx0YPQtNC10YIg0L/RgNC40LLQu9C10YfQtdC90L4g0YLQviDQu9C40YbQ -viwg0LrQvtGC0L7RgNC+0LUg0L3QsNGA0YPRiNC40LvQviDQvdCw0LvQvtCz0L7QstC+0LUg0LfQ -sNC60L7QvdC+0LTQsNGC0LXQu9GM0YHRgtCy0L4uPGJyPg0KPHA+DQrQn9GA0Lgg0Y3RgtC+0Lwg -0LzRiyDRgNC10LrQvtC80LXQvdC00YPQtdC8INCw0LrQutGD0YDQsNGC0L3QviDQv9C+0LTRhdC+ -0LTQuNGC0Ywg0Log0LLRi9Cx0L7RgNGDINC/0L7RgtC10L3RhtC40LDQu9GM0L3Ri9GFINC60L7Q -vdGC0YDQsNCz0LXQvdGC0L7QsiDQuCDQv9GA0L7Rj9Cy0LvRj9GC0Ywg0LTQvtC70LbQvdGD0Y4g -0L7RgdC80L7RgtGA0LjRgtC10LvRjNC90L7RgdGC0YwsINGC0LDQuiDQutCw0Log0Y3RgtC+INC9 -0LXQvtCx0YXQvtC00LjQvNC+INC00LvRjyDRgdC+0YXRgNCw0L3QtdC90LjRjyDQtNC10LvQvtCy -0L7QuSDRgNC10L/Rg9GC0LDRhtC40Lgg0Lgg0YTQvtGA0LzQuNGA0L7QstCw0L3QuNGPINC/0L7Q -u9C+0LbQuNGC0LXQu9GM0L3QvtC5INC90LDQu9C+0LPQvtCy0L7QuSDQuNGB0YLQvtGA0LjQuC48 -cD4NCjxicj4NCtCS0LXQtNGD0YnQsNGPOiDQldGB0LvQuCDQsiDRgdCy0LXQtNC10L3QuNGP0YUg -0YMg0LrQvtC90YLRgNCw0LPQtdC90YLQvtCyINCx0YPQtNGD0YIg0YDQsNGB0YXQvtC20LTQtdC9 -0LjRjywg0L3QsNC/0YDQuNC80LXRgCwg0YDQsNC30L3Ri9C1INC90L7QvNC10YDQsCDRgdGH0LXR -gtC+0LIt0YTQsNC60YLRg9GAINC4INGA0LDQt9C90YvQtSDQtNCw0YLRiywg0LrQsNC6INGN0YLQ -viDQsdGD0LTRg9GCINC+0YbQtdC90LjQstCw0YLRjCDQuNC90YHQv9C10LrRgtC+0YDRiz8g0JAg -0LXRgdC70Lgg0L7RgtC70LjRh9Cw0Y7RgtGB0Y8g0YHRg9C80LzRiyDQv9C+INC30LDRgNC10LPQ -uNGB0YLRgNC40YDQvtCy0LDQvdC90YvQvCDRgdGH0LXRgtCw0Lwt0YTQsNC60YLRg9GA0LDQvD8g -PGJyPg0KPGJyPg0K0KHQsNGC0LjQvSDQlC7QoS46INCf0YDQtdC20LTQtSDQstGB0LXQs9C+INCy -0L4g0LjQt9Cx0LXQttCw0L3QuNC1INGC0LXRhdC90LjRh9C10YHQutC40YUg0L7RiNC40LHQvtC6 -INCyINC/0YDQtdC00YHRgtCw0LLQu9C10L3QvdC+0Lkg0LTQtdC60LvQsNGA0LDRhtC40Lgg0L/Q -viDQndCU0KEg0LzRiyDRgNC10LrQvtC80LXQvdC00YPQtdC8INC/0YDQvtCy0LXRgNC40YLRjCDQ -uNC90YTQvtGA0LzQsNGG0LjRjiDQviDQutC+0L3RgtGA0LDQs9C10L3RgtCw0YUsINC60L7RgtC+ -0YDQsNGPINGB0L7QtNC10YDQttC40YLRgdGPINCyINCy0LDRiNC10Lkg0YPRh9C10YLQvdC+0Lkg -KNCx0YPRhdCz0LDQu9GC0LXRgNGB0LrQvtC5KSDRgdC40YHRgtC10LzQtSwg0L3QsCDQv9GA0LXQ -tNC80LXRgiDQv9GA0LDQstC40LvRjNC90L7RgdGC0Lgg0LfQsNC90LXRgdC10L3QuNGPINCyINGB -0LjRgdGC0LXQvNGDINCY0J3QnSDQuCDQmtCf0J8g0LrQvtC90YLRgNCw0LPQtdC90YLQvtCyLiDQ -nNC+0LbQvdC+INCy0L7RgdC/0L7Qu9GM0LfQvtCy0LDRgtGM0YHRjywg0L3QsNC/0YDQuNC80LXR -gCwg0L7QvdC70LDQudC9LdGB0LXRgNCy0LjRgdC+0LwsINGA0LDQt9C80LXRidC10L3QvdGL0Lwg -0L3QsCDQvtGE0LjRhtC40LDQu9GM0L3QvtC8INGB0LDQudGC0LUg0KTQndChINCg0L7RgdGB0LjQ -uCAod3d3Lm5hbG9nLnJ1LCBodHRwOi8vbnBjaGsubmFsb2cucnUpLjxicj4NCjxicj4NCtCV0YHQ -u9C4INGA0LDRgdGF0L7QttC00LXQvdC40Y8g0LIg0YHQstC10LTQtdC90LjRj9GFINC+0LEg0L7Q -v9C10YDQsNGG0LjRj9GFINC60L7QvdGC0YDQsNCz0LXQvdGC0L7QsiDQsdGD0LTRg9GCINCy0YHQ -tS3RgtCw0LrQuCDQstGL0Y/QstC70LXQvdGLLCDRgtC+INC80Ysg0LjRhSDQtNC10LvQuNC8INC9 -0LAg0LTQstC1INC60LDRgtC10LPQvtGA0LjQuC4g0J/QtdGA0LLQsNGPINGN0YLQviDRgtC10YXQ -vdC40YfQtdGB0LrQuNC1INC+0YjQuNCx0LrQuCwg0YLQsNC6INC90LDQt9GL0LLQsNC10LzRi9C1 -ICLQvtGI0LjQsdC60Lgg0LLQstC+0LTQsCIsINC60L7RgtC+0YDRi9C1INC90LUg0LjQvNC10Y7R -giDQstGL0YHQvtC60L7Qs9C+INC/0YDQuNC+0YDQuNGC0LXRgtCwINCyINC+0YLRgNCw0LHQvtGC -0LrQtS4g0J7RiNC40LHQutC4INCyINC90L7QvNC10YDQtSwg0LTQsNGC0LUg0YHRh9C10YLQsC3R -hNCw0LrRgtGD0YDRiyDQuNC70Lgg0LIg0LTRgNGD0LPQvtC8INGA0LXQutCy0LjQt9C40YLQtSwg -0L3QtSDQstC70LjRj9GO0YnQtdC8INC90LAg0YHRg9C80LzRgyDQvdCw0LvQvtCz0LAsINC80Ysg -0YDQsNGB0YHRh9C40YLRi9Cy0LDQtdC8INGD0YHRgtGA0LDQvdGP0YLRjCDQvdCwINGN0YLQsNC/ -0LUg0LfQsNC/0YDQvtGB0LAg0L/QvtGP0YHQvdC10L3QuNC5LiDQn9GA0Lgg0Y3RgtC+0Lwg0L3Q -sNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LrRgyDQv9GA0LXQtNGB0YLQsNCy0LjRgtGB0Y8g -0LLQvtC30LzQvtC20L3QvtGB0YLRjCDQvdCw0L/RgNCw0LLQuNGC0Ywg0LIg0L3QsNC70L7Qs9C+ -0LLRi9C5INC+0YDQs9Cw0L0g0YTQvtGA0LzQsNC70LjQt9C+0LLQsNC90L3Ri9C1INC/0L7Rj9GB -0L3QtdC90LjRjyDQsdC10Lcg0LrQsNC60LjRhS3Qu9C40LHQviDQvdCw0LvQvtCz0L7QstGL0YUg -0L/QvtGB0LvQtdC00YHRgtCy0LjQuS4g0JTQu9GPINGN0YLQvtCz0L4g0L3QsNC00L4g0LHRg9C0 -0LXRgiDRg9C60LDQt9Cw0YLRjCDQsiDRgdC/0LXRhtC40LDQu9GM0L3QvtC5INGA0LXQutC+0LzQ -tdC90LTQvtCy0LDQvdC90L7QuSDRhNC+0YDQvNC1INC60L7RgNGA0LXQutGC0L3Ri9C1INGB0LLQ -tdC00LXQvdC40Y8g0Lgg0L3QsNC/0YDQsNCy0LjRgtGMINC40YUg0L/QviDQotCa0KEg0YfQtdGA -0LXQtyDQvtC/0LXRgNCw0YLQvtGA0LAg0Y3Qu9C10LrRgtGA0L7QvdC90L7Qs9C+INC00L7QutGD -0LzQtdC90YLQvtC+0LHQvtGA0L7RgtCwINCyINC90LDQu9C+0LPQvtCy0YvQtSDQvtGA0LPQsNC9 -0YsuINCf0YDQtdC00YHRgtCw0LLQu9GP0YLRjCDRg9GC0L7Rh9C90LXQvdC90YPRjiDQvdCw0LvQ -vtCz0L7QstGD0Y4g0LTQtdC60LvQsNGA0LDRhtC40Y4g0L/QviDQndCU0KEg0LIg0YLQsNC60L7Q -vCDRgdC70YPRh9Cw0LUg0L3QtSDRgtGA0LXQsdGD0LXRgtGB0Y8uPGJyPg0KPHA+DQrQldGB0LvQ -uCDQttC1INGA0LDRgdGF0L7QttC00LXQvdC40Y8g0LLRi9C30LLQsNC90Ysg0L3QtdCy0LXRgNC9 -0YvQvCDRgNCw0YHRh9C10YLQvtC8INGB0YPQvNC80Ysg0L3QsNC70L7Qs9CwINC40LvQuCDRgdGH -0LXRgi3RhNCw0LrRgtGD0YDQsCDQvtGC0YDQsNC20LXQvSDQvtGI0LjQsdC+0YfQvdC+LCDRgtC+ -INCyINGB0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgdC+INGB0YLQsNGC0YzQtdC5IDgxINCd0LDQ -u9C+0LPQvtCy0L7Qs9C+INC60L7QtNC10LrRgdCwINCg0L7RgdGB0LjQudGB0LrQvtC5INCk0LXQ -tNC10YDQsNGG0LjQuCDQvdC10L7QsdGF0L7QtNC40LzQviDQsdGD0LTQtdGCINC/0YDQtdC00YHR -gtCw0LLQuNGC0Ywg0YPRgtC+0YfQvdC10L3QvdGD0Y4g0L3QsNC70L7Qs9C+0LLRg9GOINC00LXQ -utC70LDRgNCw0YbQuNGOINC/0L4g0J3QlNChLiDQmCDQt9C00LXRgdGMINC/0YDQtdC00YPRgdC8 -0LDRgtGA0LjQstCw0LXRgtGB0Y8g0L3QvtCy0YvQuSDQv9C+0LTRhdC+0LQuINCi0LXQv9C10YDR -jCDQtNC+0L/Rg9GB0LrQsNC10YLRgdGPINC/0YDQtdC00YHRgtCw0LLQu9C10L3QuNC1INGD0YLQ -vtGH0L3QtdC90L3QvtC5INC90LDQu9C+0LPQvtCy0L7QuSDQtNC10LrQu9Cw0YDQsNGG0LjQuCAi -0L3QsCDQtNC10LvRjNGC0YMiLiDQotC+INC10YHRgtGMINCyINGD0YLQvtGH0L3QtdC90L3QvtC5 -INC90LDQu9C+0LPQvtCy0L7QuSDQtNC10LrQu9Cw0YDQsNGG0LjQuCDQt9Cw0L/QvtC70L3Rj9GO -0YLRgdGPINGC0L7Qu9GM0LrQviDRgtC1INGA0LDQt9C00LXQu9GLLCDQsiDQutC+0YLQvtGA0YvQ -tSDQstC90LXRgdC10L3RiyDQuNGB0L/RgNCw0LLQu9C10L3QuNGPLiDQn9C+0LTRgNC+0LHQvdC1 -0LUg0L/QvtGA0Y/QtNC+0Log0LfQsNC/0L7Qu9C90LXQvdC40Y8g0L3QsNC70L7Qs9C+0LLQvtC5 -INC00LXQutC70LDRgNCw0YbQuNC4INC40LfQu9C+0LbQtdC9INCyINC/0YDQuNC60LDQt9C1INCk -0J3QoSDQoNC+0YHRgdC40Lgg0L7RgiAyOS4xMC4yMDE0IOKEliDQnNCc0JItNy0zLzU1OEAuPGJy -Pg0KPHA+DQo8cD4NCtCd0LDQu9C+0LPQvtC/0LvQsNGC0LXQu9GM0YnQuNC60LDQvNC4INC4INC/ -0LvQsNGC0LXQu9GM0YnQuNC60LDQvNC4INGB0LHQvtGA0L7QsiDQv9GA0LjQt9C90LDRjtGC0YHR -jyDQvtGA0LPQsNC90LjQt9Cw0YbQuNC4INC4INGE0LjQt9C40YfQtdGB0LrQuNC1INC70LjRhtCw -LCDQvdCwINC60L7RgtC+0YDRi9GFINCyINGB0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgSDQvdCw -0YHRgtC+0Y/RidC40Lwg0JrQvtC00LXQutGB0L7QvCDQstC+0LfQu9C+0LbQtdC90LAg0L7QsdGP -0LfQsNC90L3QvtGB0YLRjCDRg9C/0LvQsNGH0LjQstCw0YLRjCDRgdC+0L7RgtCy0LXRgtGB0YLQ -stC10L3QvdC+INC90LDQu9C+0LPQuCDQuCAo0LjQu9C4KSDRgdCx0L7RgNGLLjxicj4NCtCSINC/ -0L7RgNGP0LTQutC1LCDQv9GA0LXQtNGD0YHQvNC+0YLRgNC10L3QvdC+0Lwg0L3QsNGB0YLQvtGP -0YnQuNC8INCa0L7QtNC10LrRgdC+0LwsINGE0LjQu9C40LDQu9GLINC4INC40L3Ri9C1INC+0LHQ -vtGB0L7QsdC70LXQvdC90YvQtSDQv9C+0LTRgNCw0LfQtNC10LvQtdC90LjRjyDRgNC+0YHRgdC4 -0LnRgdC60LjRhSDQvtGA0LPQsNC90LjQt9Cw0YbQuNC5INC40YHQv9C+0LvQvdGP0Y7RgiDQvtCx -0Y/Qt9Cw0L3QvdC+0YHRgtC4INGN0YLQuNGFINC+0YDQs9Cw0L3QuNC30LDRhtC40Lkg0L/QviDR -g9C/0LvQsNGC0LUg0L3QsNC70L7Qs9C+0LIg0Lgg0YHQsdC+0YDQvtCyINC/0L4g0LzQtdGB0YLR -gyDQvdCw0YXQvtC20LTQtdC90LjRjyDRjdGC0LjRhSDRhNC40LvQuNCw0LvQvtCyINC4INC40L3R -i9GFINC+0LHQvtGB0L7QsdC70LXQvdC90YvRhSDQv9C+0LTRgNCw0LfQtNC10LvQtdC90LjQuS48 -YnI+DQrQkiDRgdC70YPRh9Cw0Y/RhSwg0L/RgNC10LTRg9GB0LzQvtGC0YDQtdC90L3Ri9GFINC9 -0LDRgdGC0L7Rj9GJ0LjQvCDQmtC+0LTQtdC60YHQvtC8LCDQvdCw0LvQvtCz0L7Qv9C70LDRgtC1 -0LvRjNGJ0LjQutCw0LzQuCDQv9GA0LjQt9C90LDRjtGC0YHRjyDQuNC90L7RgdGC0YDQsNC90L3R -i9C1INGB0YLRgNGD0LrRgtGD0YDRiyDQsdC10Lcg0L7QsdGA0LDQt9C+0LLQsNC90LjRjyDRjtGA -0LjQtNC40YfQtdGB0LrQvtCz0L4g0LvQuNGG0LAuPHA+DQo8cD4NCtCh0YLQsNGC0YzRjyAyMC4g -0JLQt9Cw0LjQvNC+0LfQsNCy0LjRgdC40LzRi9C1INC70LjRhtCwPHA+DQoxLiDQktC30LDQuNC8 -0L7Qt9Cw0LLQuNGB0LjQvNGL0LzQuCDQu9C40YbQsNC80Lgg0LTQu9GPINGG0LXQu9C10Lkg0L3Q -sNC70L7Qs9C+0L7QsdC70L7QttC10L3QuNGPINC/0YDQuNC30L3QsNGO0YLRgdGPINGE0LjQt9C4 -0YfQtdGB0LrQuNC1INC70LjRhtCwINC4ICjQuNC70LgpINC+0YDQs9Cw0L3QuNC30LDRhtC40Lgs -INC+0YLQvdC+0YjQtdC90LjRjyDQvNC10LbQtNGDINC60L7RgtC+0YDRi9C80Lgg0LzQvtCz0YPR -giDQvtC60LDQt9GL0LLQsNGC0Ywg0LLQu9C40Y/QvdC40LUg0L3QsCDRg9GB0LvQvtCy0LjRjyDQ -uNC70Lgg0Y3QutC+0L3QvtC80LjRh9C10YHQutC40LUg0YDQtdC30YPQu9GM0YLQsNGC0Ysg0LjR -hSDQtNC10Y/RgtC10LvRjNC90L7RgdGC0Lgg0LjQu9C4INC00LXRj9GC0LXQu9GM0L3QvtGB0YLQ -uCDQv9GA0LXQtNGB0YLQsNCy0LvRj9C10LzRi9GFINC40LzQuCDQu9C40YYsINCwINC40LzQtdC9 -0L3Qvjo8YnI+DQoxKSDQvtC00L3QsCDQvtGA0LPQsNC90LjQt9Cw0YbQuNGPINC90LXQv9C+0YHR -gNC10LTRgdGC0LLQtdC90L3QviDQuCAo0LjQu9C4KSDQutC+0YHQstC10L3QvdC+INGD0YfQsNGB -0YLQstGD0LXRgiDQsiDQtNGA0YPQs9C+0Lkg0L7RgNCz0LDQvdC40LfQsNGG0LjQuCwg0Lgg0YHR -g9C80LzQsNGA0L3QsNGPINC00L7Qu9GPINGC0LDQutC+0LPQviDRg9GH0LDRgdGC0LjRjyDRgdC+ -0YHRgtCw0LLQu9GP0LXRgiDQsdC+0LvQtdC1IDIwINC/0YDQvtGG0LXQvdGC0L7Qsi4g0JTQvtC7 -0Y8g0LrQvtGB0LLQtdC90L3QvtCz0L4g0YPRh9Cw0YHRgtC40Y8g0L7QtNC90L7QuSDQvtGA0LPQ -sNC90LjQt9Cw0YbQuNC4INCyINC00YDRg9Cz0L7QuSDRh9C10YDQtdC3INC/0L7RgdC70LXQtNC+ -0LLQsNGC0LXQu9GM0L3QvtGB0YLRjCDQuNC90YvRhSDQvtGA0LPQsNC90LjQt9Cw0YbQuNC5INC+ -0L/RgNC10LTQtdC70Y/QtdGC0YHRjyDQsiDQstC40LTQtSDQv9GA0L7QuNC30LLQtdC00LXQvdC4 -0Y8g0LTQvtC70LXQuSDQvdC10L/QvtGB0YDQtdC00YHRgtCy0LXQvdC90L7Qs9C+INGD0YfQsNGB -0YLQuNGPINC+0YDQs9Cw0L3QuNC30LDRhtC40Lkg0Y3RgtC+0Lkg0L/QvtGB0LvQtdC00L7QstCw -0YLQtdC70YzQvdC+0YHRgtC4INC+0LTQvdCwINCyINC00YDRg9Cz0L7QuTs8YnI+DQoyKSDQvtC0 -0L3QviDRhNC40LfQuNGH0LXRgdC60L7QtSDQu9C40YbQviDQv9C+0LTRh9C40L3Rj9C10YLRgdGP -INC00YDRg9Cz0L7QvNGDINGE0LjQt9C40YfQtdGB0LrQvtC80YMg0LvQuNGG0YMg0L/QviDQtNC+ -0LvQttC90L7RgdGC0L3QvtC80YMg0L/QvtC70L7QttC10L3QuNGOOzxicj4NCjMpINC70LjRhtCw -INGB0L7RgdGC0L7Rj9GCINCyINGB0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgSDRgdC10LzQtdC5 -0L3Ri9C8INC30LDQutC+0L3QvtC00LDRgtC10LvRjNGB0YLQstC+0Lwg0KDQvtGB0YHQuNC50YHQ -utC+0Lkg0KTQtdC00LXRgNCw0YbQuNC4INCyINCx0YDQsNGH0L3Ri9GFINC+0YLQvdC+0YjQtdC9 -0LjRj9GFLCDQvtGC0L3QvtGI0LXQvdC40Y/RhSDRgNC+0LTRgdGC0LLQsCDQuNC70Lgg0YHQstC+ -0LnRgdGC0LLQsCwg0YPRgdGL0L3QvtCy0LjRgtC10LvRjyDQuCDRg9GB0YvQvdC+0LLQu9C10L3Q -vdC+0LPQviwg0LAg0YLQsNC60LbQtSDQv9C+0L/QtdGH0LjRgtC10LvRjyDQuCDQvtC/0LXQutCw -0LXQvNC+0LPQvi48cD4NCjIuINCh0YPQtCDQvNC+0LbQtdGCINC/0YDQuNC30L3QsNGC0Ywg0LvQ -uNGG0LAg0LLQt9Cw0LjQvNC+0LfQsNCy0LjRgdC40LzRi9C80Lgg0L/QviDQuNC90YvQvCDQvtGB -0L3QvtCy0LDQvdC40Y/QvCwg0L3QtSDQv9GA0LXQtNGD0YHQvNC+0YLRgNC10L3QvdGL0Lwg0L/R -g9C90LrRgtC+0LwgMSDQvdCw0YHRgtC+0Y/RidC10Lkg0YHRgtCw0YLRjNC4LCDQtdGB0LvQuCDQ -vtGC0L3QvtGI0LXQvdC40Y8g0LzQtdC20LTRgyDRjdGC0LjQvNC4INC70LjRhtCw0LzQuCDQvNC+ -0LPRg9GCINC/0L7QstC70LjRj9GC0Ywg0L3QsCDRgNC10LfRg9C70YzRgtCw0YLRiyDRgdC00LXQ -u9C+0Log0L/QviDRgNC10LDQu9C40LfQsNGG0LjQuCDRgtC+0LLQsNGA0L7QsiAo0YDQsNCx0L7R -giwg0YPRgdC70YPQsykuPGJyPg0KPGJyPg0KPGJyPg0K0J3QtdC00LDQstC90L4gwqvRhtC40YTR -gNC+0LLQsNGPINGN0LrQvtC90L7QvNC40LrQsMK7INC+0LTQtdGA0LbQsNC70LAg0LLQsNC20L3R -g9GOINC/0L7QsdC10LTRgywg0LLRi9C60LjQvdGD0LIg0LjQtyDQv9GP0YLQtdGA0LrQuCDRgdCw -0LzRi9GFINC00L7RgNC+0LPQuNGFINC60L7QvNC/0LDQvdC40Lkg0LzQuNGA0LAg0L/QvtGB0LvQ -tdC00L3QtdCz0L4g0L/RgNC10LTRgdGC0LDQstC40YLQtdC70Y8gwqvRgNC10LDQu9GM0L3QvtCz -0L4g0YHQtdC60YLQvtGA0LDCuyBFeHhvbk1vYmlsLiDQndC+INC/0L7QutCwINCx0LjRgtCy0LAg -wqvRgdC10YDQstC10YDQsCDRgdC+INGB0YLQsNC90LrQvtC8wrsg0L/RgNC40LLQvtC00LjRgiDQ -uiDRgtC+0YDQvNC+0LbQtdC90LjRjiDRjdC60L7QvdC+0LzQuNGH0LXRgdC60L7Qs9C+INGA0L7R -gdGC0LAuINCe0YfQtdCy0LjQtNC90L4sINGH0YLQviDQtNC70Y8g0L7QutC+0L3Rh9Cw0YLQtdC7 -0YzQvdC+0LPQviDRhNC+0YDQvNC40YDQvtCy0LDQvdC40Y8g0L3QvtCy0L7Qs9C+INGN0LrQvtC9 -0L7QvNC40YfQtdGB0LrQvtCz0L4g0YPQutC70LDQtNCwINC80LjRgNGDINC/0YDQuNC00LXRgtGB -0Y8g0L/QtdGA0LXQttC40YLRjCDQtdGJ0LUg0L3QtSDQvtC00L3QviDQv9C+0YLRgNGP0YHQtdC9 -0LjQtS48cD4NCiA8YnI+DQo8cD4NCjxwPg0KPHA+DQog0J/Rj9GC0LXRgNC60YMg0LrRgNGD0L/Q -vdC10LnRiNC40YUg0L/QviDRgNGL0L3QvtGH0L3QvtC5INGB0YLQvtC40LzQvtGB0YLQuCDQutC+ -0LzQv9Cw0L3QuNC5INC+0LrQutGD0L/QuNGA0L7QstCw0LvQuCBJVC3Qs9C40LPQsNC90YLRizxi -cj4NCtCd0LXRhNGC0Y/QvdC40LrQvtCyINGB0LzQtdC90LjQu9C4INGC0LXRhdC90L7Qu9C+0LPQ -uNC4PGJyPg0K0J3QsCDRgdC80LXQvdGDINGA0LXRgdGD0YDRgdC90L7QuSDRjdC60L7QvdC+0LzQ -uNC60LUg0L/RgNC40YXQvtC00LjRgiDRgtC10YXQvdC+0LvQvtCz0LjRh9C10YHQutCw0Y8uINCV -0YHQu9C4INGA0LDQvdGM0YjQtSDQsiDRgtC+0L8tNSDQu9C40LTQtdGA0L7QsiDQv9C+INC60LDQ -v9C40YLQsNC70LjQt9Cw0YbQuNC4IElULdCz0LjQs9Cw0L3RgtGLINC/0L7Qv9Cw0LTQsNC70Lgg -0LvQuNGI0Ywg0LjQt9GA0LXQtNC60LAsINGC0L4g0YLQtdC/0LXRgNGMINCy0YHRji4uLiDihpI8 -YnI+DQrQndC+INC90Lgg0LIg0L7QtNC90L7QvCDQsdCw0L3QutC+0LzQsNGC0LUg0YHQvdGP0YLR -jCDQtNC10L3QtdCzINC90LUg0L/QvtC70YPRh9C40LvQvtGB0YwuINCi0LXQu9C10YTQvtC9INCx -0LDQvdC60LAg0L3QtSDQvtGC0LLQtdGH0LDQuywg0YHQsNC50YIg0LLQuNGB0LXQuy4g0KfQtdGA -0LXQtyDQv9C+0LvRh9Cw0YHQsCDQstGL0Y/RgdC90LjQu9C+0YHRjCwg0YfRgtC+INCx0LDQvdC6 -0L7QstGB0LrQuNC1INGB0LjRgdGC0LXQvNGLINGA0YPRhdC90YPQu9C4LCDQsCDRgdGH0LXRgtCw -INC00LXRgdGP0YLQutC+0LIg0LzQuNC70LvQuNC+0L3QvtCyINCy0LrQu9Cw0LTRh9C40LrQvtCy -INC+0LHQvdGD0LvQtdC90YsuINCU0L7Qu9C70LDRgCwg0YTRg9C90YIg0Lgg0LXQstGA0L4g0L/R -gNC10LLRgNCw0YLQuNC70LjRgdGMINCyINC/0YvQu9GMLCDQt9Cw0YLQviDRgtC1LCDQutGC0L4g -0LfQsNC/0LDRgdCw0LvRgdGPINC30L7Qu9C+0YLQvtC8INC4INC/0L7QutGD0L/QsNC7INC60YDQ -uNC/0YLQvtCy0LDQu9GO0YLRiywg0L/QvtGC0LjRgNCw0LvQuCDRgNGD0LrQuC4g0KHRgtGA0LDR -hSDQuCDRgNCw0YHRgtC10YDRj9C90L3QvtGB0YLRjCDigJQg0LLQvtGCINC00LLQsCDQs9C70LDQ -stC90YvRhSDRgdC70L7QstCwLCDQutC+0YLQvtGA0YvQtSDQsdGL0LvQuCDQsiDQt9Cw0LPQvtC7 -0L7QstC60LDRhSDQvdC+0LLQvtGB0YLQvdGL0YUg0YHQvtC+0LHRidC10L3QuNC5INCy0YHQtdGF -INC40L3RhNC+0YDQvNCw0LPQtdC90YLRgdGC0LIuINCd0L7QstGL0Lkg0LTQuNCy0L3Ri9C5INC8 -0LjRgCDQstGB0YLRgNC10YfQsNC7INGB0LLQvtC40YUg0LjRgdC/0YPQs9Cw0L3QvdGL0YUg0L7Q -sdC40YLQsNGC0LXQu9C10LnigKY8cD4NCjxicj4NCtCa0L7QvdC10YfQvdC+LCDQstC10YHRjNC8 -0LAg0LLQtdGA0L7Rj9GC0L3Qviwg0YfRgtC+INGB0YbQtdC90LDRgNC40LkgwqvRhNC40LvRjNC8 -0LAt0LrQsNGC0LDRgdGC0YDQvtGE0YvCuyDQsiDQttC40LfQvdC4INC90LjQutC+0LPQtNCwINC9 -0LUg0LHRg9C00LXRgiDRgNC10LDQu9C40LfQvtCy0LDQvS4g0J3QviDQstC+0YIg0YfRgtC+INC9 -0LDQtNC+INGH0LXRgtC60L4g0L/QvtC90LjQvNCw0YLRjDog0L3QvtCy0YvQuSDQvNC40YAsINC9 -0L7QstCw0Y8g0YbQuNGE0YDQvtCy0LDRjyDRgNC10LDQu9GM0L3QvtGB0YLRjCDRg9C20LUg0L/R -gNC40LHQuNGA0LDQtdGCINC6INGA0YPQutCw0Lwg0L7QutGA0YPQttCw0Y7RidC40Lkg0LzQuNGA -Ljxicj4NCjxwPg0K0J3QtdC00LDQstC90L4g0LLQv9C10YDQstGL0LUg0LIg0LjRgdGC0L7RgNC4 -0Lgg0L/Rj9GC0LXRgNC60LAg0YHQsNC80YvRhSDQtNC+0YDQvtCz0LjRhSDQutC+0LzQv9Cw0L3Q -uNC5INC80LjRgNCwINGB0YLQsNC70LAg0LjRgdC60LvRjtGH0LjRgtC10LvRjNC90L4gwqvRhtC4 -0YTRgNC+0LLQvtC5wrsuINCf0L7RgdC70LXQtNC90LjQuSDQv9GA0LXQtNGB0YLQsNCy0LjRgtC1 -0LvRjCDCq9GB0YLQsNGA0L7Qs9C+INC80LjRgNCwwrsg4oCUINC90LXRhNGC0Y/QvdC+0Lkg0LPQ -uNCz0LDQvdGCIEV4eG9uTW9iaWwg0LHRi9C7INCy0YvRgtC10YHQvdC10L0g0YEg0L/Rj9GC0L7Q -s9C+INC80LXRgdGC0LAg0LjQvdGC0LXRgNC90LXRgi3QvNCw0LPQsNC30LjQvdC+0LwgQW1hem9u -INC4INGB0L7RhtGB0LXRgtGM0Y4gRmFjZWJvb2suINCf0LXRgNCy0YvQtSDRgtGA0Lgg0L/QvtC3 -0LjRhtC40Lgg0L/RgNC40L3QsNC00LvQtdC20LDRgiBBcHBsZSwgQWxwaGFiZXQgKNC80LDRgtC1 -0YDQuNC90YHQutCw0Y8g0LrQvtC80L/QsNC90LjRjyBHb29nbGUpINC4IE1pY3Jvc29mdC4gwqvQ -r9Cx0LvQvtGH0L3Ri9C5wrsg0LvQuNC00LXRgCDRgdGC0L7QuNGCINC+0LrQvtC70L4gJDU3MCDQ -vNC70YDQtC48YnI+DQo8cD4NCtCf0YDQtdC40LzRg9GJ0LXRgdGC0LLQviBJVC3RgdC10LrRgtC+ -0YDQsCDQvdCw0YDQsNGB0YLQsNC10YIg0YEg0LrQvtC90YbQsCDQtNC10LLRj9C90L7RgdGC0YvR -hSDQs9C+0LTQvtCyINC/0YDQvtGI0LvQvtCz0L4g0LLQtdC60LAuINCi0L7Qs9C00LAg0L/RgNC+ -0LjQt9C+0YjQtdC7INGE0LDQu9GM0YHRgtCw0YDRgiwg0LLRi9C70LjQstGI0LjQudGB0Y8g0LIg -0LrRgNC40LfQuNGBINC00L7RgtC60L7QvNC+0LIsINGH0YPRgtGMINCx0YvQu9C+INC90LUg0L/Q -vtCz0YDRg9C30LjQstGI0LjQuSDQsNC80LXRgNC40LrQsNC90YHQutGD0Y4g0Y3QutC+0L3QvtC8 -0LjQutGDINCyINGA0LXRhtC10YHRgdC40Y4g0LIg0L3QsNGH0LDQu9C1IDIwMDAt0YUg0LPQvtC0 -0L7Qsi4g0J3QviDQuCDRgdC10LnRh9Cw0YEg0L3QvtCy0LDRjyDRhtC40YTRgNC+0LLQsNGPINGN -0YDQsCDQvdC40LrQsNC6INC90LUg0LLRi9Cy0LXQtNC10YIg0Y3QutC+0L3QvtC80LjQutGDINC9 -0LAg0YLRgNCw0LXQutGC0L7RgNC40Y4g0YPRgdGC0L7QudGH0LjQstC+0LPQviDRgNC+0YHRgtCw -LjxwPg0KPHA+DQrQkdC+0LvQtdC1INGC0L7Qs9C+LCDRgdGA0LXQtNC90LjQtSDRgtC10LzQv9GL -INGA0L7RgdGC0LAg0JLQktCfINCh0L7QtdC00LjQvdC10L3QvdGL0YUg0KjRgtCw0YLQvtCyICjQ -uNC80LXQvdC90L4g0LIg0Y3RgtC+0Lkg0YHRgtGA0LDQvdC1INCx0LDQt9C40YDRg9C10YLRgdGP -INCx0L7Qu9GM0YjQuNC90YHRgtCy0L4g0LrRgNGD0L/QvdC10LnRiNC40YUg0LLRi9GB0L7QutC+ -0YLQtdGF0L3QvtC70L7Qs9C40YfQvdGL0YUg0LrQvtGA0L/QvtGA0LDRhtC40LkpINCyIFhYSSDQ -stC10LrQtSDQvdCw0YXQvtC00Y/RgtGB0Y8g0L3QsCDQvNC40L3QuNC80LDQu9GM0L3QvtC8INGD -0YDQvtCy0L3QtSDQv9C+INGB0YDQsNCy0L3QtdC90LjRjiDRgdC+INCy0YLQvtGA0L7QuSDQv9C+ -0LvQvtCy0LjQvdC+0LkgWFgg0LLQtdC60LAuPHA+DQo8cD4NCtCSIDIwMDHigJMyMDE1INCz0L7Q -tNCw0YUg0LDQvNC10YDQuNC60LDQvdGB0LrQsNGPINGN0LrQvtC90L7QvNC40LrQsCDRgNC+0YHQ -u9CwINC90LAgMSw4JSDQsiDRgdGA0LXQtNC90LXQvCDQt9CwINCz0L7QtC4g0KDQvtGB0YIg0LIg -0L/RgNC10LTRi9C00YPRidC40LUg0LTQstC1INC/0Y/RgtC90LDQtNGG0LDRgtC40LvQtdGC0LrQ -uCDRgdC+0YHRgtCw0LLQu9GP0LsgMyw0INC4IDMsMyUsINCwINC00L4g0Y3RgtC+0LPQviDQsdGL -0Lsg0LXRidC1INCy0YvRiNC1ICjQt9C00LXRgdGMINC4INC00LDQu9C10LUg4oCUINC00LDQvdC9 -0YvQtSDQktGB0LXQvNC40YDQvdC+0LPQviDQsdCw0L3QutCwKS48YnI+DQo8cD4NCtCYINGN0YLQ -viDQvdC1INCy0YHQtSDQsNC90YLQuNGA0LXQutC+0YDQtNGLLiDQotC10LzQv9GLINGA0L7RgdGC -0LAg0LfQsCDQv9C+0YHQu9C10LTQvdC40LUg0L/Rj9GC0L3QsNC00YbQsNGC0Ywg0LvQtdGCINC9 -0Lgg0YDQsNC30YMg0L3QtSDQv9GA0LXQstGL0YjQsNC70LggNCUgKNC80LDQutGB0LjQvNGD0Lwg -4oCUIDMsOCUg4oCUINCx0YvQuyDQv9C+0LrQsNC30LDQvSDQsiAyMDA0INCz0L7QtNGDKSwg0YLQ -vtCz0LTQsCDQutCw0Log0YDQsNC90LXQtSDRjdGC0LggNCUg0LHRi9C70Lgg0L7QsdGL0YfQvdGL -0Lwg0LTQtdC70L7QvCAo0L/QuNC6INCyIDcsMjYlINCx0YvQuyDQv9C+0LrQsNC30LDQvSDQsiAx -OTg0INCz0L7QtNGDKS48cD4NCjxwPg0KPHA+DQrQk9C70YPQsdC40L3QsCDQv9Cw0LTQtdC90LjR -jyDRjdC60L7QvdC+0LzQuNC60Lgg0LIgMjAwOSDQs9C+0LTRgyAo4oCTMiw3OCUpINGB0YLQsNC7 -0LAg0LzQsNC60YHQuNC80LDQu9GM0L3QvtC5INGBIDE5NjEg0LPQvtC00LAuINCf0YDQuCDRjdGC -0L7QvCDQvNCw0YHRiNGC0LDQsdGLIMKr0L7RgtGB0LrQvtC60LDCuyDQv9C+0YHQu9C1INGB0L/Q -sNC00LAgKCsyLDUzJSDQsiAyMDEwINCz0L7QtNGDKSDQsdGL0LvQuCDQvNC40L3QuNC80LDQu9GM -0L3Ri9C80Lgg0LfQsCDQstC10YHRjCDQvdCw0LHQu9GO0LTQsNC10LzRi9C5INC/0LXRgNC40L7Q -tC4g0JIg0L7QsdGJ0LXQvCwg0L3QtSDQt9GA0Y8g0L/QvtGB0LvQtdC00L3QuNC5INC60YDQuNC3 -0LjRgSDQv9C+0LvRg9GH0LjQuyDQsiDQqNGC0LDRgtCw0YUg0L3QsNC30LLQsNC90LjQtSDCq9CS -0LXQu9C40LrQsNGPINGA0LXRhtC10YHRgdC40Y/CuyAoR3JlYXQgUmVjZXNzaW9uKS48cD4NCjxi -cj4NCiDQm9Cw0LfQtdGA0Ysg0Lgg0LTRgNC+0L3RiyDQvtGCIEZhY2Vib29rINC4INC00YDRg9Cz -0LjQtSDRgdC/0L7RgdC+0LHRiyDQvtCx0LXRgdC/0LXRh9C40YLRjCDQsdC10LTQvdGL0LUg0YHR -gtGA0LDQvdGLINC40L3RgtC10YDQvdC10YLQvtC8PGJyPg0K0JvQsNC30LXRgNGLINC90LAg0LHQ -tdC00L3QvtGB0YLRjDxicj4NCtCW0LXQu9Cw0L3QuNC1INCc0LDRgNC60LAg0KbRg9C60LXRgNCx -0LXRgNCz0LAg0L/QvtC60YDRi9GC0Ywg0LjQvdGC0LXRgNC90LXRgtC+0Lwg0LLQtdGB0Ywg0LzQ -uNGAINC90LDQsdC40YDQsNC10YIg0L3QvtCy0YvQtSDQvtCx0L7RgNC+0YLRiyDigJQg0Log0LTR -gNC+0L3QsNC8LCDQutC+0YLQvtGA0YvQtSDQsdGD0LTRg9GCINC+0LHQtdGB0L/QtdGH0LjQstCw -0YLRjCDRgdC10YLRjCDQsiDRgtGA0YPQtNC90L7QtNC+0YHRgtGD0L/QvdGL0YUuLi4g4oaSPHA+ -DQrQn9GA0LXQt9C40LTQtdC90YLRgdGC0LLQviDQkdCw0YDQsNC60LAg0J7QsdCw0LzRiyDQvNC+ -0LbQvdC+INCy0L7QvtCx0YnQtSDRgdGH0LjRgtCw0YLRjCDRgdCw0LzRi9C8INC90LXRg9C00LDR -h9C90YvQvCDRgSDRgtC+0YfQutC4INC30YDQtdC90LjRjyDRjdC60L7QvdC+0LzQuNGH0LXRgdC6 -0LjRhSDRg9GB0L/QtdGF0L7Qsi4g0JfQsCDQv9GA0LXQtNGL0LTRg9GJ0LjQtSDRgdC10LzRjCDQ -u9C10YIg0JLQktCfINCh0KjQkCDRg9Cy0LXQu9C40YfQuNCy0LDQu9GB0Y8g0YHRgNC10LTQvdC1 -0LPQvtC00L7QstGL0LzQuCDRgtC10LzQv9Cw0LzQuCAxLDQlLiDQndC4INC/0YDQuCDQvtC00L3Q -vtC8INC/0YDQtdC30LjQtNC10L3RgtC1LCDQvdCw0YfQuNC90LDRjyDRgSDQlNC20L7QvdCwINCa -0LXQvdC90LXQtNC4LCDRgtCw0Log0LzQtdC00LvQtdC90L3QviDRjdC60L7QvdC+0LzQuNC60LAg -0L3QtSDRgNC+0YHQu9CwLjxwPg0KPGJyPg0K0KLQtdC60YPRidC40Lkg0LPQvtC0INC90LUg0L/R -gNC40L3QtdGB0LXRgiDQvdC40YfQtdCz0L4g0YXQvtGA0L7RiNC10LPQvi4g0JIg0L/QtdGA0LLQ -vtC8INC60LLQsNGA0YLQsNC70LUg0JLQktCfINCy0YvRgNC+0YEg0L3QsCAxLDElINCyINCz0L7Q -tNC+0LLQvtC8INC40YHRh9C40YHQu9C10L3QuNC4LCDQstC+INCy0YLQvtGA0L7QvCwg0L/QviDQ -v9C10YDQstC+0Lkg0L7RhtC10L3QutC1LCDQvdCwIDEsMiUuINCf0YDQvtCz0L3QvtC3INC90LAg -0LLQtdGB0Ywg0LPQvtC0IOKAlCAy4oCTMiwyJSwg0YfRgtC+INCx0YPQtNC10YIg0LzQtdC90YzR -iNC1INC/0YDQvtGI0LvQvtCz0L7QtNC90LjRhSAyLDQlLjxwPg0KPHA+DQrQp9C70LXQvSDRgdC+ -0LLQtdGC0LAg0YPQv9GA0LDQstC70Y/RjtGJ0LjRhSDQpNC10LTQtdGA0LDQu9GM0L3QvtC5INGA -0LXQt9C10YDQstC90L7QuSDRgdC40YHRgtC10LzRiyDQodCo0JAg0JTQttC10YDQvtC8INCf0LDR -g9GN0LvQuyDQsiDQuNC90YLQtdGA0LLRjNGOIEZpbmFuY2lhbCBUaW1lcyDQt9Cw0Y/QstC40Lss -INGH0YLQviDQtdGB0YLRjCDRgNC40YHQuiDQstGC0Y/Qs9C40LLQsNC90LjRjyDQsiDQtNC70LjR -gtC10LvRjNC90YvQuSDQv9C10YDQuNC+0LQg0YHQu9Cw0LHQvtCz0L4g0YDQvtGB0YLQsC48YnI+ -DQo8cD4NCtCd0LDQtNC+INGC0LDQutC20LUg0YPRh9C40YLRi9Cy0LDRgtGMLCDRh9GC0L4g0Y3R -gtC+0YIg0LzQuNC90LjQvNCw0LvRjNC90YvQuSDRgNC+0YHRgiDQv9C+0YHQu9C10LTQvdC40YUg -0LvQtdGCINGB0L7Qv9GA0L7QstC+0LbQtNCw0LXRgtGB0Y8g0YPQstC10LvQuNGH0LXQvdC40LXQ -vCDQtNC+0LvQs9CwLiDQk9C+0YHRg9C00LDRgNGB0YLQstC10L3QvdGL0Lkg0LTQvtC70LMg0LLR -i9GA0L7RgSDQt9CwINCy0L7RgdC10LzRjCDQu9C10YIg0L/QvtGH0YLQuCDQsiDQtNCy0LAg0YDQ -sNC30LAg0Lgg0YHQtdC50YfQsNGBINC/0YDQtdCy0YvRiNCw0LXRgiAxMDAlINCS0JLQnyDQodCo -0JAg0Lgg0YHQvtGB0YLQsNCy0LvRj9C10YIgJDE5LDQg0YLRgNC70L0uINCSINGN0YLQvtC8INCz -0L7QtNGDLCDQv9C+INC+0YbQtdC90LrQtSDQsNC80LXRgNC40LrQsNC90YHQutC+0LPQviDQvNC4 -0L3RhNC40L3QsCwg0L7QvSDQstGL0YDQsNGB0YLQtdGCINC10YnQtSDQvdCwICQzODMg0LzQu9GA -0LQuPGJyPg0KPGJyPg0K0J7QtNC90L7QstGA0LXQvNC10L3QvdC+INCk0KDQoSDRg9C00LXRgNC2 -0LjQstCw0LXRgiDQutC70Y7Rh9C10LLRg9GOINGB0YLQsNCy0LrRgyDQvdCwINC80LjQvdC40LzQ -sNC70YzQvdC+0Lwg0YPRgNC+0LLQvdC1INC90LjQttC1IDElINGBINC00LXQutCw0LHRgNGPIDIw -MDgg0LPQvtC00LAuINCX0LAg0Y3RgtC+INCy0YDQtdC80Y8g0LHRi9C70L4g0YDQtdCw0LvQuNC3 -0L7QstCw0L3QviDRgtGA0Lgg0YDQsNGD0L3QtNCwINC/0YDQvtCz0YDQsNC80LzRiyDQutC+0LvQ -uNGH0LXRgdGC0LLQtdC90L3QvtCz0L4g0YHQvNGP0LPRh9C10L3QuNGPIChRRSksINC60L7RgtC+ -0YDQsNGPINC30LDQstC10YDRiNC40LvQsNGB0Ywg0LIg0L7QutGC0Y/QsdGA0LUgMjAxNCDQs9C+ -0LTQsCAo0LIg0YLRgNC10YLRjNC10Lwg0YDQsNGD0L3QtNC1INCyINC80LXRgdGP0YYg0KTQoNCh -INC/0L7QutGD0L/QsNC7INGDINCx0LDQvdC60L7QsiDQs9C+0YHRg9C00LDRgNGB0YLQstC10L3Q -vdGL0YUg0Lgg0LjQv9C+0YLQtdGH0L3Ri9GFINC+0LHQu9C40LPQsNGG0LjQuSDQvdCwICQ4NSDQ -vNC70YDQtCkuPGJyPg0KPHA+DQrQndC+LCDQv9C+0LTRh9C10YDQutC90LXQvCDQtdGJ0LUg0YDQ -sNC3LCDQvdC10YHQvNC+0YLRgNGPINC90LAg0LLRgdC1INGN0YLQuCDRjdC60YHRgtGA0LDQvtGA -0LTQuNC90LDRgNC90YvQtSDQvNC10YDRiywg0YLQtdC80L/RiyDRgNC+0YHRgtCwINC/0YDQtdCx -0YvQstCw0Y7RgiDQvdCwINC80L3QvtCz0L7Qu9C10YLQvdC40YUg0LzQuNC90LjQvNGD0LzQsNGF -LCDQsCDQstGB0Y8g0Y3RgtCwINC40YHRgtC+0YDQuNGPINCy0LXQu9C40LrQvtCz0L4g0YLQvtGA -0LzQvtC20LXQvdC40Y8g0L/RgNC+0LjRgdGF0L7QtNC40YIg0L3QsCDRhNC+0L3QtSDRgNC10LfQ -utC+0LPQviDRg9GB0LjQu9C10L3QuNGPINGA0L7Qu9C4IElULdGB0LXQutGC0L7RgNCwLCDQsCDR -gtCw0LrQttC1INGA0L7RgdGC0LAg0YTQuNC90LDQvdGB0L7QstGL0YUg0YDRi9C90LrQvtCyLjxw -Pg0KPHA+DQrQmtC70Y7Rh9C10LLQvtC5INCx0LjRgNC20LXQstC+0Lkg0LjQvdC00LXQutGBIFMm -UDUwMCDQvdCw0YXQvtC00LjRgtGB0Y8g0L3QsCDQuNGB0YLQvtGA0LjRh9C10YHQutC40YUg0LzQ -sNC60YHQuNC80YPQvNCw0YUuINCQ0L3QsNC70LjRgtC40LrQuCBHb2xkbWFuIFNhY2hzINC/0YDQ -tdC00YPQv9GA0LXQttC00LDRjtGCOiDQutC+0L3QtdGGINGA0LDQu9C70Lgg0LHQu9C40LfQvtC6 -LiDQn9GA0LXQtNGL0LTRg9GJ0LjQtSDQv9C10YDQuNC+0LTRiyDRgNC+0YHRgtCwICjQsiAxOTg0 -4oCTMTk4NyDQuCAxOTk04oCTMTk5OSDQs9C+0LTQsNGFKSDQt9Cw0LrQsNC90YfQuNCy0LDQu9C4 -0YHRjCDQvtCx0LLQsNC70L7QvCDQutC+0YLQuNGA0L7QstC+0LouPHA+DQo8cD4NCtCi0L4sINGH -0YLQviDQv9GA0L7QuNGB0YXQvtC00LjRgiDQsiDQodCo0JAsINCyINGC0L7QuSDQuNC70Lgg0LjQ -vdC+0Lkg0YHRgtC10L/QtdC90Lgg0YXQsNGA0LDQutGC0LXRgNC90L4g0Lgg0LTQu9GPINC00YDR -g9Cz0LjRhSDRgNCw0LfQstC40YLRi9GFINGB0YLRgNCw0L06INCy0LDQuyDQtNC+0LvQs9C+0LIs -INC90LjQt9C60LjQtSDRgtC10LzQv9GLINGA0L7RgdGC0LAsINC+0YLRgdGD0YLRgdGC0LLQuNC1 -INC40L3RhNC70Y/RhtC40LgsINC/0L7RgdGC0LXQv9C10L3QvdC+0LUg0YHQvtC60YDQsNGJ0LXQ -vdC40LUg0YHRgNC10LTQvdC10LPQviDQutC70LDRgdGB0LAg0Lgg0LrQvtC90YbQtdC90YLRgNCw -0YbQuNGPINCx0L7Qs9Cw0YLRgdGC0LLQsCDQsiDRgNGD0LrQsNGFINC+0LPRgNCw0L3QuNGH0LXQ -vdC90L7Qs9C+INC60YDRg9Cz0LAg0LvRjtC00LXQuS4g0J/RgNC4INGN0YLQvtC8INC40LfQvNC1 -0L3QtdC90LjQtSDRgdC40YLRg9Cw0YbQuNC4INGBINC/0L7QvNC+0YnRjNGOINC80LXRgCDRjdC6 -0L7QvdC+0LzQuNGH0LXRgdC60L7QuSDQuCDQtNC10L3QtdC20L3Qvi3QutGA0LXQtNC40YLQvdC+ -0Lkg0L/QvtC70LjRgtC40LrQuCDQvdC10LLQvtC30LzQvtC20L3Qvi4g0K3RgtC+INC90LUg0YHR -h9C40YLQsNGPIMKr0YfQtdGA0L3Ri9GFINC70LXQsdC10LTQtdC5wrsg0LLRgNC+0LTQtSBCcmV4 -aXQsINC60L7RgtC+0YDRi9C1INGB0LXRjtGCINGB0LzRj9GC0LXQvdC40LUg0Lgg0YXQsNC+0YEg -0L3QsCDRgNGL0L3QutCw0YUuPHA+DQo8YnI+DQrQlNC+0LvQs9C+0LUg0LLRgNC10LzRjyDQvNC4 -0YAg0LLRi9C/0LvRi9Cy0LDQuyDQt9CwINGB0YfQtdGCINCx0YPRgNC90L7Qs9C+INGA0L7RgdGC -0LAg0LIg0YHRgtGA0LDQvdCw0YUg0YLRgNC10YLRjNC10LPQviDQvNC40YDQsCwg0LrQvtGC0L7R -gNGL0LUg0Y3QutGB0L/QvtGA0YLQuNGA0L7QstCw0LvQuCDRgdGL0YDRjNC1INC/0L4g0LLRi9GB -0L7QutC40Lwg0YbQtdC90LDQvCwg0L/QvtC60YPQv9Cw0LvQuCDRgtC+0LLQsNGA0Ysg0Lgg0YLQ -tdGF0L3QvtC70L7Qs9C40Lgg0Lgg0YDQsNC30YDQtdGI0LDQu9C4INC+0YLQutGA0YvQstCw0YLR -jCDRgyDRgdC10LHRjyDQv9GA0L7QuNC30LLQvtC00YHRgtCy0LAg0YLRgNCw0L3RgdC90LDRhtC4 -0L7QvdCw0LvRjNC90YvQvCDQutC+0YDQv9C+0YDQsNGG0LjRj9C8LCDRgdC90LDQsdC20LDRjyDQ -uNGFINC00LXRiNC10LLQvtC5INGA0LDQsdC+0YfQtdC5INGB0LjQu9C+0LkuINCd0L4g0YHQtdCz -0L7QtNC90Y8g0L7QvdC4INGC0LDQutC20LUg0LIg0LrRgNC40LfQuNGB0LUuPGJyPg0KPGJyPg0K -INCV0LvQuNC30LDQstC10YLQsCDQkNC70LXQutGB0LDQvdC00YDQvtCy0LAt0JfQvtGA0LjQvdCw -INC+INGC0L7QvCwg0LPQvtGC0L7QstC+INC70Lgg0YfQtdC70L7QstC10YfQtdGB0YLQstC+INC6 -INCz0LXQvdC10YLQuNGH0LXRgdC60L7QuSDQuCDRgtC10YXQvdC+0LvQvtCz0LjRh9C10YHQutC+ -0LkgwqvRgNC10LTQsNC60YLRg9GA0LXCuzxicj4NCtCn0LXQu9C+0LLQtdC6INC90L7QstGL0Lks -INGD0LvRg9GH0YjQtdC90L3Ri9C5PHA+DQrQniDQvdC+0LLQvtC8INGH0LXQu9C+0LLQtdC60LUs -IGwnaG9tbWUgbm91dmVhdSwg0LzQtdGH0YLQsNC70Lgg0YHQtdCy0LXRgNC+0LDQvNC10YDQuNC6 -0LDQvdGB0LrQuNC1INC/0L7RgdC10LvQtdC90YbRiywg0L3QsNGG0LjRgdGC0Ysg0Lgg0LrQvtC8 -0LzRg9C90LjRgdGC0YssINCi0YDQvtGG0LrQuNC5LCDQp9C1INCT0LXQstCw0YDQsCDQuCDQvNC9 -0L7Qs9C40LUg0LTRgNGD0LPQuNC1LiDQoNC10YfRjCDRiNC70LAg0L7QsS4uLiDihpI8YnI+DQrQ -otCw0Log0L/QvtGH0LXQvNGDINC20LUgwqvRhtC40YTRgNCwwrsg0YLQsNC6INC4INC90LUg0YHR -gtCw0LvQsCDRgtC10Lwg0YHQsNC80YvQvCDQtNGA0LDQudCy0LXRgNC+0LwsINC60L7RgtC+0YDR -i9C5INCy0LXRgNC90YPQuyDQsdGLINGN0LrQvtC90L7QvNC40LrRgyDQvdCwINGC0YDQsNC10LrR -gtC+0YDQuNGOINCy0YvRgdC+0LrQvtCz0L4g0Lgg0YPRgdGC0L7QudGH0LjQstC+0LPQviDRgNC+ -0YHRgtCwPyDQrdGC0L7QvNGDINC80LXRiNCw0Y7RgiDRgdGC0LDRgNGL0LUg0LjQvdGB0YLQuNGC -0YPRgtGLINC4INC40L3RhNGA0LDRgdGC0YDRg9C60YLRg9GA0LAsINGF0L7RgNC+0YjQviDQv9GA -0LjRgdC/0L7RgdC+0LHQu9C10L3QvdGL0LUg0LTQu9GPINGC0YDQsNC00LjRhtC40L7QvdC90L7Q -uSDRjdC60L7QvdC+0LzQuNC60Lgg0YEg0LXQtSDQs9C70LDQstC10L3RgdGC0LLRg9GO0YnQtdC5 -INGA0L7Qu9GM0Y4gwqvRgNC10LDQu9GM0L3QvtCz0L4g0YHQtdC60YLQvtGA0LDCuyDQuCDQsdCw -0L3QutC+0LLRgdC60L7Qs9C+INC60YDQtdC00LjRgtCwINC60LDQuiDQtNGA0LDQudCy0LXRgNCw -INC40L3QstC10YHRgtC40YbQuNC5INC4INC/0L7RgtGA0LXQsdC70LXQvdC40Y8uPHA+DQo8cD4N -CtCd0LAg0YHQvNC10L3RgyDRgdGC0LDRgNGL0Lwg0LLQsNC70Y7RgtCw0LwsINCx0LDQvdC60LDQ -vCDQuCDQsdC40YDQttCw0LwsINGD0LPQu9C10LLQvtC00L7RgNC+0LTQvdC+0Lkg0Y3QvdC10YDQ -s9C10YLQuNC60LUg0Lgg0YLRgNCw0LTQuNGG0LjQvtC90L3Ri9C8INGE0LDQsdGA0LjQutCw0Lwg -0YPQttC1INC40LTRg9GCINC90L7QstGL0LUg0YLQtdGF0L3QvtC70L7Qs9C40LguINCd0LDQv9GA -0LjQvNC10YAsINCx0LvQvtC60YfQtdC50L0g0Lgg0LHQuNGC0LrQvtC40L3Riywg0LrQvtGC0L7R -gNGL0LUg0L/QvtC30LLQvtC70Y/RjtGCINCy0L7QvtCx0YnQtSDQstGL0LLQtdGB0YLQuCDQs9C+ -0YHRg9C00LDRgNGB0YLQstC+ICjQutCw0Log0Y3QvNC40YLQtdC90YLQsCDQstCw0LvRjtGC0Ysp -INC4INCx0LDQvdC60LggKNC60LDQuiDQuNGB0YLQvtGH0L3QuNC6INC60YDQtdC00LjRgtCwINC4 -INGE0LjQvdCw0L3RgdC+0LLRi9C1INC/0L7RgdGA0LXQtNC90LjQutC4KSDQuNC3INC40LPRgNGL -LjxwPg0KPGJyPg0K0J/RgNC10LfQuNC00LXQvdGCINCh0LHQtdGA0LHQsNC90LrQsCDQk9C10YDQ -vNCw0L0g0JPRgNC10YQg0L3QtdC+0LTQvdC+0LrRgNCw0YLQvdC+INC/0L7QtNGH0LXRgNC60LjQ -stCw0LssINGH0YLQviDRg9C20LUg0LIg0LHQu9C40LbQsNC50YjQtdC1INCy0YDQtdC80Y8g0YLR -gNCw0LTQuNGG0LjQvtC90L3Ri9C8INGE0LjQvdCw0L3RgdC+0LLRi9C8INC+0YDQs9Cw0L3QuNC3 -0LDRhtC40Y/QvCDQv9GA0LjQtNC10YLRgdGPINC60L7QvdC60YPRgNC40YDQvtCy0LDRgtGMINGB -INC40L3RgtC10YDQvdC10YIt0LjQvdC00YPRgdGC0YDQuNC10LkuPHA+DQo8cD4NCtCSINC40L3R -gtC10YDQstGM0Y4g0LbRg9GA0L3QsNC70YMgSGFydmFyZCBCdXNpbmVzcyBSZXZpZXcg0L7QvSDQ -vtGC0LzQtdGH0LDQuywg0YfRgtC+INCx0LDQvdC6IMKr0LDQsdGB0L7Qu9GO0YLQvdC+INC90LXQ -utC+0L3QutGD0YDQtdC90YLQvtGB0L/QvtGB0L7QsdC10L3CuyDQsiDQv9C10YDRgdC/0LXQutGC -0LjQstC1INC00LXRgdGP0YLQuCDQu9C10YIsINC/0L7RgdC60L7Qu9GM0LrRgyDQv9GA0L7QuNCz -0YDRi9Cy0LDQtdGCINGC0LXRhdC90L7Qu9C+0LPQuNGH0LXRgdC60LjQvCDQutC+0LzQv9Cw0L3Q -uNGP0LwgKEdvb2dsZSwgQWxpYmFiYSDQuCBBbWF6b24g0Lgg0L/RgC4pLCDQstGL0YXQvtC00Y/R -idC40Lwg0L3QsCDRgNGL0L3QvtC6INC/0YDQtdC00L7RgdGC0LDQstC70LXQvdC40Y8g0YTQuNC9 -0LDQvdGB0L7QstGL0YUg0YPRgdC70YPQsy4gwqvQntC90Lgg0L3QsNC80L3QvtCz0L4g0YHQuNC7 -0YzQvdC10LUg0L/QvtGH0YLQuCDQstC+INCy0YHQtdC8wrssIOKAlCDQv9C+0LTRh9C10YDQutC4 -0LLQsNC7INCT0YDQtdGELjxicj4NCjxicj4NCsKr0JXRgdC70Lgg0YMg0L3QsNGBIHRpbWUgdG8g -bWFya2V0ICjQstGA0LXQvNGPINCy0YvRhdC+0LTQsCDQv9GA0L7QtNGD0LrRgtCwINC90LAg0YDR -i9C90L7QuiDRgSDQvNC+0LzQtdC90YLQsCDRgdC+0LfQtNCw0L3QuNGPLiDigJQgwqvQk9Cw0LfQ -tdGC0LAuUnXCuykg0LzQtdGA0Y/QtdGC0YHRjyDQvNC90L7Qs9C40LzQuCDQvNC10YHRj9GG0LDQ -vNC4LCDQsCDRgyDQvdC40YUg0YfQsNGB0LDQvNC4LCDRgtC+INC60LDQuiDQvdCw0Lwg0LrQvtC9 -0LrRg9GA0LjRgNC+0LLQsNGC0Yw/INCd0LjQutCw0LouINCe0L3QuCDQstGB0LXQs9C00LAg0L3Q -sNGBINCx0YPQtNGD0YIg0L7Qv9C10YDQtdC20LDRgtGMLiDQp9GC0L4g0L3QsNC8INC90YPQttC9 -0L4g0YHQtNC10LvQsNGC0Yw/INCi0LDQutC+0Lkg0LbQtSB0aW1lIHRvIG1hcmtldMK7LCDigJQg -0LfQsNGP0LLQuNC7INC/0YDQtdC30LjQtNC10L3RgiDQodCx0LXRgNCx0LDQvdC60LAuPHA+DQo8 -cD4NCtCR0LXQt9GD0YHQu9C+0LLQvdC+LCDQuCDQsdC70L7QutGH0LXQudC9LCDQuCDQsdC40YLQ -utC+0LjQvdGLINC10YnQtSDQvdGD0LbQtNCw0Y7RgtGB0Y8g0LIg0LTQvtGA0LDQsdC+0YLQutC1 -LiDQndC10LTQsNCy0L3Rj9GPINGF0LDQutC10YDRgdC60LDRjyDQsNGC0LDQutCwINC90LAg0L7Q -tNC90YMg0LjQtyDQutGA0YPQv9C90LXQudGI0LjRhSDQsdC40YLQutC+0LjQvS3QsdC40YDQtiDQ -siDQk9C+0L3QutC+0L3Qs9C1INC/0YDQuNCy0LXQu9CwINC6INC/0LDQtNC10L3QuNGOINC60YPR -gNGB0LAg0LrRgNC40L/RgtC+0LLQsNC70Y7RgtGLINC90LAgMjAlLiDQpdCw0LrQtdGA0LDQvCDR -g9C00LDQu9C+0YHRjCDQv9C+0YXQuNGC0LjRgtGMIDEyMCDRgtGL0YEuINCx0LjRgtC60L7QuNC9 -0L7Qsiwg0LrQvtGC0L7RgNGL0LUg0L3QsCDRgtC+0YIg0LzQvtC80LXQvdGCINCx0YvQu9C4INGN -0LrQstC40LLQsNC70LXQvdGC0L3RiyDQv9GA0LjQvNC10YDQvdC+ICQ3MCDQvNC70L0uPGJyPg0K -PGJyPg0K0JLQv9GA0L7Rh9C10LwsINC/0L7QsdC10LTQvdGD0Y4g0L/QvtGB0YLRg9C/0Ywg0LHQ -u9C+0LrRh9C10LnQvdCwINCy0YDRj9C0INC70Lgg0YPQtNCw0YHRgtGB0Y8g0L7RgdGC0LDQvdC+ -0LLQuNGC0YwuINCQINCy0LXQtNGMINC10YHRgtGMINC10YnQtSDRgtC10YXQvdC+0LvQvtCz0LjQ -uCDQuNC3INGA0LXQsNC70YzQvdC+0LPQviDRgdC10LrRgtC+0YDQsCDigJQg0LDQu9GM0YLQtdGA -0L3QsNGC0LjQstC90LDRjyDRjdC90LXRgNCz0LXRgtC40LrQsCDQuCDRjdC70LXQutGC0YDQvtC8 -0L7QsdC40LvQuCwg0LAg0YLQsNC60LbQtSAzRC3Qv9C10YfQsNGC0YwsINGA0L7QsdC+0YLQuNC3 -0LDRhtC40Y8g0L/RgNC+0LjQt9Cy0L7QtNGB0YLQstC10L3QvdGL0YUg0L/RgNC+0YbQtdGB0YHQ -vtCyLCDRgdC40L3RgtC10Lcg0L/QuNGJ0LXQstGL0YUg0L/RgNC+0LTRg9C60YLQvtCyINC4INC8 -0L3QvtCz0L7QtSDQtNGA0YPQs9C+0LUuPHA+DQo8YnI+DQrQktC/0L7Qu9C90LUg0LLQtdGA0L7R -j9GC0L3Qviwg0YfRgtC+INC90LAg0LPQvtGA0LjQt9C+0L3RgtC1INCx0LvQuNC20LDQudGI0LjR -hSDQtNCy0YPRhS3RgtGA0LXRhSDQtNC10YHRj9GC0LjQu9C10YLQuNC5INCx0LDQt9C+0LLRi9C1 -INC/0L7RgtGA0LXQsdC90L7RgdGC0Lgg0YfQtdC70L7QstC10LrQsCDQsiDQv9C40YnQtSwg0L7Q -tNC10LbQtNC1INC4INC/0LXRgNC10LTQstC40LbQtdC90LjQuCDQsdGD0LTRg9GCINGD0LTQvtCy -0LvQtdGC0LLQvtGA0Y/RgtGM0YHRjyDQv9C+0YfRgtC4INCx0LXRgdC/0LvQsNGC0L3Qvi4g0J/Q -u9Cw0YLQuNGC0Ywg0L/RgNC40LTQtdGC0YHRjyDQsiDQvtGB0L3QvtCy0L3QvtC8INC30LAgwqvQ -utC+0L3RgtC10L3RgsK7Ljxicj4NCjxicj4NCtCR0LXQtNC90L7RgdGC0Ywg0LIg0YHRgtGA0LDQ -vdCw0YUgwqvQt9C+0LvQvtGC0L7Qs9C+INC80LjQu9C70LjQsNGA0LTQsMK7INC+0LrQvtC90YfQ -sNGC0LXQu9GM0L3QviDRg9C50LTQtdGCINCyINC/0YDQvtGI0LvQvtC1LCDQs9GA0LDQttC00LDQ -vdC1INCx0YPQtNGD0YIg0L/QvtC70YPRh9Cw0YLRjCDQs9Cw0YDQsNC90YLQuNGA0L7QstCw0L3Q -vdGL0Lkg0LTQvtGF0L7QtCDQv9GA0Y/QvNC+INGBINGA0L7QttC00LXQvdC40Y8gKNC/0YDQuNCy -0YvRh9C90YvRhSDQsdGD0LzQsNC20L3Ri9GFINC00LXQvdC10LMg0L3QtSDQsdGD0LTQtdGCLCDQ -uNGFINGBINCx0L7Qu9GM0YjQvtC5INC00L7Qu9C10Lkg0LLQtdGA0L7Rj9GC0L3QvtGB0YLQuCDQ -vtGC0LzQtdC90Y/RgiDRg9C20LUg0LTQvtCy0L7Qu9GM0L3QviDRgdC60L7RgNC+KS4g0JIg0YLQ -viDQttC1INCy0YDQtdC80Y8g0LLRi9Cx0LjRgtGM0YHRjyDQsiDRgdGA0LXQtNC90LjQuSDQutC7 -0LDRgdGBINC40LvQuCDRgNCw0LfQsdC+0LPQsNGC0LXRgtGMINGB0YLQsNC90LXRgiDQs9C+0YDQ -sNC30LTQviDRgdC70L7QttC90LXQtSwg0YfQtdC8INGB0LXQudGH0LDRgS48YnI+DQo8YnI+DQrQ -ndC+INCy0L7RgiDRh9GC0L4g0YHRgtC+0LjRgiDQv9C+0LTRh9C10YDQutC90YPRgtGMLiDQnNC4 -0YAg0LbQtNC10YIg0L3QvtCy0LDRjyDRjdC60L7QvdC+0LzQuNGH0LXRgdC60LDRjyDRgNC10LLQ -vtC70Y7RhtC40Y8sINC60L7RgtC+0YDQsNGPINCx0YPQtNC10YIg0YHQvtC/0YDQvtCy0L7QttC0 -0LDRgtGM0YHRjyDQutGA0LjQt9C40YHQvdGL0LzQuCDQv9C10YDQuNC+0LTQsNC80LguINCi0LXQ -vCDQsdC+0LvQtdC1INGH0YLQviwg0LrQsNC6INCx0YvQu9C+INC/0L7QutCw0LfQsNC90L4g0LLR -i9GI0LUsINC/0YDQtdC00L/QvtGB0YvQu9C+0Log0LTQu9GPINC90L7QstC+0LPQviDQvtCx0LLQ -sNC70LAg0LHQvtC70LXQtSDRh9C10Lwg0LTQvtGB0YLQsNGC0L7Rh9C90L4uINCS0L7Qv9GA0L7R -gSDRgtC+0LvRjNC60L4g0LIg0YLQvtC8LCDQs9C00LUg0Lgg0LrQvtCz0LTQsCDQvdCw0YfQvdC1 -0YLRgdGPINC+0LHRgNGD0YjQtdC90LjQtS48YnI+DQo8cD4NCjxicj4NCg== ---2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7 -Content-Type: application/octet-stream; name="ФНС_РФ558.zip" -Content-Disposition:attachment; filename="ФНС_РФ558.zip" -Content-Transfer-Encoding: base64 - -UEsDBBQAAAAIALtmFklP5Nc/ZCIAAGMiAAAPAAAA4avjpqGglI2ROTQuemlwdXpTcCUME+yJk41t -2zrhxhvbtm3bG+PEtq2NbVsb28nGTu73P9ynW3fmYWr6aaqmqru6qhVlICAxAAAALKDTHEvKj7O8 -6ogUABhSBQCQ/kNNHUwM3NzNTB3sbJmsXWr4rOIPeJ3zdt4xZwBgfGcZMRRRoJgtRJJx0E2VX+c8 -mO/489XTfP/Xs2pioejMtfnxryejUbJoBszoYd6KOIGnxYvz+tmXSacnm+OAr48DTgGL8xLjY4eM -1ufej1cYvh6rm7m3xk80LrcVXV2xDcOrV/1mOBvmhsgxPH3cv6y5abJ8IYKn+0G7nTP3F6Ne8ehf -dyc73zGQVk3J9T76c26zo6Y5vf+wyioxWrnl3m02Dyn0jp0WTacPNBPW+H5lPfeacps/yewb8H9x -UNBlWYRJBhCVmNpDpmSlS37C2zjiVUzrZItv7TPKk8lJ/g2b23ZUt+MWw5cfE9/DQlCFrVCj4BUA -G/3g3DYP+m1gMsUx9J5hBhndQ9ZTDd1Wkbtotcnjwy/CHOhvGlEznKOP+YZyx//L7iflYBWdqqed -1SKa+yaUYK0s73Uw+XvbME9jhE42+Ua7oRSUxuC7AdtzOu5zuhiCm7T8kQopNYOrsre8nPBsBe4i -ub5Tj0j0Y2n5jAbUMqdYM4onViMD074YoVdIXUXzSY0mildI9F42F40FgoNmdeKYxTP0NJQBC8R+ -/EgA7Dfra3x/QLlowHPpHPGS9dmPqNcWTQi+kSrD/Ui5HqQNVLsejJ+J85hRMHlPoEiYfzB63t/S -Xp8DaThtXaEiQq0VLNeKrwCaSUnMbhoPgNf4oe3vIT+Z06pgz0wK7Odkv2z4gYoTZ1o4M4OCgmzl -ORwOSIhdVnOABv7aCqVY0JXmcopolsPJKxk3W6g53w0lOvIucrhngphN+Xm7v7RGMk2HJA2vuGhy -Wgm9NIbeJhmaX+Kcy2qbWAAZJ0I+4fBfxqgWIuJRf6kPm6HeUlEvHJrim3vas89oku6ASCmunn9Y -1FtzB6WJlXyWTn9d1J0381ROd5v02XwB7xz7BkddgmXnQQpnGepbovBPKl7nQtSBjZIdpz/FIU6v -AN4gdoaCuxFPcZQHchNN702eAUTwsWdZprIS15i7H7jXbWlojfZwI9U6rCYv8sIqaU/QmeEUHnMU -ZwIik7Kt2YTgpuh596C98rU6rO/M2zpbYCpda+pgGIV3xW082oFDusE+ZxmQOlO8o1/J+J1xHrjY -H/EWBE59GUDBF+xJEjA3HCJ7/avgDwkP3WDPms6IdUPiLHsldKD5+oLosztMN6zPOE5mShOkFg0J -4B3seDbmHYkpDmrsOYZ2g+dUI2K9+oSWx6e/zW/qT853WAv3Itn2LkPWtFd0caoJ8f2F7y6jv6EJ -xiYFw2iXWdOKMMvTYMhxLrvDbEb7ydSWi0vDSZnFLDJXVrxg46/7PVp4HeJrUA6KOBqxIlxr3cmy -/23e/Gs9xOztJbH5vS3T8yM2uev0x3JC9qcMxRtzoubDqmR13dlC9MSEzIXrHkRDUYinRMY2YcT9 -BH74oR7SPiLjbTgtbrJwV0Gk1k0BBOvuO3D3J/uuD/euhGmfh11++fa2SIF7Gho+rHFIuuZvwiGS -0hXSruHxZUjMeYKc3MBe7t0lyCjTPtijFFDjjef+B+nqEM9Gfvz6bWNn8wWL8FWrMPHiHPqythAy -urDjA9CJYIvXFWtucIlxPKLTLSwiMUBm3HF4Qm8vklXunA56U22qFd8BYwffHtlJUHcGNo194Ah/ -j+4cFpquWFeQUtn9PZgEmsjE2VlW2DgLXVR0UZKfyRakamC8KAvTpBJhJL35DYpsuIUhIwLekjgY -KsWqTYCKlGgRhpHY4qI+RTIAmqk1cq4rNyanPYXo5n8H0Iyxwf1NCVRs98C6W1gvScbHO1RiUe1B -1DjTRUOnU+QNkbK9RjppfCSWeFahkXIzLRmWmaDzYU65LVtbZzFv+T4Q3XBpUqU1BQL/Tds7JXrS -CLHsrWEEjB6f5n4/FrIo31hu+3n6TuZiMnHR77m2S7iv117X629+qpWOXiHTb2iyTy4Urd4wyW/C -xpYd3Af89DaeF0ypRmGcCqLRQkep1lLtPDilsFFN1Yx/RipsCGuHoHNmsazihTA5WvAhqrSlQfnm -AxaW/hGxZYWpL7AD4rhHSvTeHEHF/90gw7e2L4yO3OjGQwOf5ps135MVQeEzFPC9XIpjEGChySDf -S1D6vcLC+Pxz0Pj2g7Jxh4zR4luw9p6L9l3P3rKXSzatxH9Hzf0bxYx+h63G+uFRAZVpZ56hq8F0 -rxM2k8VNdfwHPmsQlazXIEICTnBkFldqiFeACRwA7B8S8cBbcntGO7PGhheVz9QHpv7T46zg7vDm -g6flQbyhmcgT3YqW8ZJvxoDz5kXa82WicJztLvwI3NqQMKWl+na6zPt9uW8UEgRtPo3O24is6YLg -ZfWBGCvhZ66Y8Rc/uxT0w/nYsdVxr1P5qe8+PvfizjE3vHgD7lisguf9PWxNL/WmBS49bYFnApnE -A6jwEttYcXIOMRbPW5+9+SPNIYM5d8t+I+fqXqup9ZV5wb42pZuAoRilTpBwFId2SDjVJJOqhXDo -D2dmq86n8oVnmtX7UpxpKIuM8Ou8HCvns1pDomQbCvTUyH9cu51r6lzJf/o0jx5askZmmmXSF1ZP -nJHNdMczDUEVj/pE6IdEe+WlmDQkRnvFgz9SFeKoR47HSpLairjK4hjWnXTy9uircKFQF+ely3RC -75XIzkG9mOSL4cAdf/tXgIMjJHNJNKaFB3f2oa+WwVSURbt/SRI1pTVYQ7LFcJKoIa11PTf5B74x -ExRg11ELTkHfgJUObA7ZMSEMO9yfG7zPipMg8kdYeCl1CfldlUUXwfSC3XSza6HNn+szpobYbGHL -3MPm7GO04GjXWYnBHD6mbkN7Vw8Yr5JASa/JykTE1ciJPsz84r4XtWQwyeNeNuwzQxNNFGOikn5k -4xggvKZ2DLKjyPHGJF+O0lAXuU4UsKfN+NcROjDx8N7VJmr4PFzMd3DZd2O5vPZgXz7szVGM06Wj -fPYopZ/BzKwOBFz66OL+lXqlZHzQS2SclTby+HauXDnv65aAPw5rBIpypFJD/CtCl/CPAvfcOLgx -xy+leD8wFwFcn4VUTHSCzJKEGeyF3ARVgoDk8td/XFshM6/aVGLekga6X+/UCBuA49gwtS5tmYpl -3rv610HZ/mgKXE/LQ/YyY5GYjr6klNrAGApyRV290md6e+ujLvam18dU+ouxJGl2KdX+d0skW3CD -5TFQs0NBUdKv8qxnxF3ZZTXJounwrCyDnSvO3OX07A4e8BuPP/7zAerER6k6Cw3ZzICcN+lL3+f1 -H1b82QKuK1NRPTu7PJt6erlhjuRU1xWUfJ5yLJoGvJCi76J4l9q9/l8c6AxaZecN+rUOD1qTb9jQ -JdTFlvs/Dke0Oq8vutUI2CM2fvyUZa9eZWE9VOR+5bg6tO0u8of2dFb2xJg3PJP6TLyDfKnOKA8g -VkYJdNCqSFF8HzFuUtTatmARGCSKJNHk2mioUG+lcYnrgHcr5ihwzqXDwbKteeKkRYmLt/N1vIYh -OPu1Mr19S1141YpBxfdLNX8RqAe/lxdoqT3zh8YWYnoRUG0pBJFjN2Lb0ssH02l4ceky35+JeQ71 -KtaTyb4FqSxVocr7Yx7yY4iq9NiGygm4M3bByyprTtN8b/PM2nMDv5oag0NFn/5+lZK87rmN9jrv -FgXNKEFgvM6zETZIjCatd2oSZXEwL9V0+ihnII6Ms3Q5LWc854RmsuoWghd0jUOqbH/YR/USwb5T -Vc21qHbrGGd+ZDd5O/rjajSycpZ3wenHgGhsegfMKSPjqJ3j3pwDA/i75QYELq6sYr2cDijkFqVN -d5oN2HzcB6tjAa6s34UEP7pX7d4texxUxtBbEe4cayU1cf2IfltP+MLcTIhHJtKt64BRlLvxt7U6 -LsZuTmPZXJ83c+tIN5uiWJcgUzqiGIJlcpuxYxV1pQcFVpuAobbcapl8aQ32M5F0CGU5iuiJDEE+ -qaAMEkyooJxSTeBH8LfhCQHhl4IsikQa/OSoS4PVdG3suYSPK/eEDcNH8TgXiFufbnLovP+swQ7e -SS5N+7OHQG/CW7VOw4GNLT2fSkPBucd3E5PgVweUzV+YmHqwdLMb+UYFywHUJ1AQOyXC5r+JWmxg -Ay/5gxKmGh54jwXCqGRkRaVXfWZ2ZwAin1ExUJE/FVocqpCGe6xt4NjEPQtxNT6JkhnpuJyo4TBE -nyv4NdeOlGhCNG/p6v2B5Qq9Ixc66sBrq7zg+ZX2rCE+VCnGsh/kjD8NJtREbHViHZgLDA5U4tlC -ffQNCKqFFAlS+7OrdhIlzzsQqMWjAqqu0uaocafIUlrTC91nAf7Or2iWmgEY4GPzaKnlJpnw6z4d -TxrBNCvbs6lQ2HooKcrQX8G9NvKhX5aMAO8vzEoIn6m7WzcEfOZrpCVgoAwUUhruhOlQp3WCXWzB -gqX0MEq96N9lO+9wUoW+ZcLzC7CQltC08m4aMFokdZ5ZkcT/IwLyewSAdQVd3ufmErqBp/6wYxqP -30BhIZrhDLar7qzC9VGj/rFwt6nhF75LyHZ776Thv1l4Yx+Tr0kfTv014s+nhy5e/Z250h6LDNoY -vdOzF14988bOzENvZO86TaPYE5aQp1yzFepI2yKq84SLe25BUe6vWK14DzD7fD8zaWKfuV4L9C9o -gCeyD6qGOwPtTDMl0mF0X0pQGjKLJ+MifNs2yxCqkNPl43dMaE50QFCoXVcwBWLdoGJs3Y0yjWsM -GjUjcmKOhK/jl8g0S8a9RtHsCOfm3yI3PVa/xPNSvzjdcd2WsDrrN6x5W/q59NKuHlCPHqPDCdRw -ZNCetbD6AsU0CGW3U3WPwDCxWGuisZUqpwVVPfncMxGmOnRshLjMMh0FFrWIFrIz3AgjmKUxlSwk -09toMMleRvV6fy4krUh4qpxntwjVfqnsIP69Vucio0WE2rX07o7uvELTT71kqnc3yUonVdgvs0j2 -gGjrybp9B0jEj6tSfD4WnLxEQ1FBUZfdWOwmfK/4MtaYRrWMaAob3BffYlUI7yCJ0LdbrhMspyGb -AgC0UWxsVWXCrzOWIDgVpXDTI3GnYwRjsGjyvthCMgQdn3vFDgv/VlnCkaX+fFWGU9KuHHXe9BVZ -+mA8cEtJxxM7TGPlMEkgUCNvxBp8VPh1sds0hYbkgZNoZgOygHggDr6songosZIa+13z2mFzGNMl -bDnW/TJsz0NX+zjzZisg3cGjgoQuqyZj7TIuRdhGv6d3comy6W6snUw4bWwi0Mkg37rbxlczU5W0 -790H5MjJSLO8Ogxr1i2BAErYrY5brF+InUBN0wrpC4FHcKuQduBgA0vMnS12m0U4wFFcf5Yni2tW -ZO5TRE4UMXYhOV7ITddzRJyR8BqxbjlauqYufXA/usuVGWUcXGgmJkvomGWa5uPnNVGQseoJld+a -pjxzHxbqVLRW68WfdAqoztDKKi8rKnVcpqp6K9DazAXoe65XmWpuyAGL/B74JdFI/MngUe/IIRo6 -x0wK3kaGcpmOHvWXJ63NaL9uInCiNTHA4fbRHJeE1NjdUHmnlVtf39bwViu7iuPwdJb9XtnO5Oth -yazQ2+yD0QfBZyZMAqZ3gnXcfFxketYTWAeAHP41MsM70YZoRbr/KMoNedw8xvcvkhiQOn1+kzBE -hoQwNbshwpBj3Oj9MG9hVfyFIr64EFb0By8wh9J/S16kRQSyUgXNGuvUVqGIYcCKuXLx7QNIQCN1 -sCElhyP9mNcx5pGBVS47bUp3WaTu6V5WZGGGzetdZUQIUxUZg2xeURlAUsIGAp5q7S+bxjrfrpln -gU6Q+de+g8flVExjQlCWRQjXD2FnbdstXpkCfWfrm+iY+K6WWSpGQcnlsL1mb2F7rbKuas30i5i9 -qnWeAqZXUZo6dAsBleb0EtWbBbu7ttsi0wgKslyj3Q7zmB+sohi16PExXvxj4mfDU1uvUsx1lIwq -5fDdBCK7Mf0RJaxZflZLZSePwu+nuJNdBzQCHd5bhRspySczMldZNRcdcAV+hqF89Qzii2EGjvjR -zhbrcLq4J/KIaVTKNEQUFvN/7twtUekNRztUiBBzuRbOju9bx6trMSjAH0neJGj3soZkYhxJJevG -zuzdyYb3FolyGQD59AlYRB2nNmQIulRyooqUnN2Anw7jA2rK0aVGKh1Iytp2aFmSQt6RIe3u5/eX -E4U8ldk2I2yNFxDPTco+vjTiTiXhoZ1VxfE6ZX7KgwWpZ2vJ59kWi6/smk8f5nVzbBLdZnuv8ykN -A6KPMTLY3yGL2v9ia8gqfuW3QBimJiZAMpefgUFms9tKWFbyTM7+Lr9ABOniS9iEJtzgZknbJ5fh -Zf/VnKifLSqOMTl0qwx5hIolPrVN7D43JhSIv+j0uZl9W533Fz4Sm1GTaGB6jUyxL8RifBgFh31K -d/C/Rrpa7uIub8HEVFeKLjSTCokF5fsylJzMlhOLr3f43joGEpEChJY3wsmzI95g4L+h0xvLwlG6 -xTHjsf9TmtJkHqjA80xkSjNBlJ40IzwqKmYE4i7mvPpGfeq2PBn2Orxs/bymadq64i3DykwRXpmW -SvsESz9Gi1eMLwu2ZafVznsompr1TBqLrqrjjDx02vnLZTRsPxq9lWwj0h+BWk/m837aRd0SJMc+ -U6LDgvMSv0gm0uqhKGXtAJD7ThkmJeEiMdmXBn66w/2h95Ysyxzz4ILasRZNqwPtinAuX2JO8YYT -ChoUJfigEzuQFGW4eJukm3bW5m8/Hz/NSfsagUnadtH0wnwbGcqp24qcQ3mOPMX9kMWis6VCdcA1 -M4NuiEcgQ0O35QxvbKrLOeKxtBZ4Ag1zxIqy3gAxdQPBizISCbzt70NX85Uyf57q089PNyvOpzzp -yBENiLzSoYWQtKGupSaOSXCj8YZyDx1LBfeVos1UVkJVq2t+ChdHTY7QcpbKMEVguhM0+0YyXwU/ -Ly8AjL+4LKXuAy8Fq+8/PyzjGl5NF5+fV4m2p+KGIdGvPoNM8Uhfn59OMGZD9KHd9cCmuyqYl+9S -s1H811d3wzpVSA7H4/B9t5AX7ARzwGCr8A+h5ZAhUqb42Wa9R41+fpLVhVuRasR/v1i8kRmt3o8u -7rkKrHkWVt3n0x72DiRrwX+4TBAJ6NGgCbOyBpLyoJSsWt0ViFOb+VbEqOMHaokLGTOyPAexcLep -LoIc2N88Z8Bg8UGu/l1kDqiQ+GQGz5CBeN1Sh7q8VstkCraLC/coMG28PxEOrIWIiZss2TfoIk/j -vEtsaF7ZfRKnmusxcouCru54hXzmNLkK9a5OfdFw1/WnDeRnF7gYR4Xqz0nEpLH57AGTjdjUlcg5 -EICCQzzg6x+AT15XJpVuPFgqFdg/QgdwSdZabc9q+cbRg9u8zhZ65Hi4wJ10B5raXQSF0yWWHSlT -vr8v/q7wog+cohblfJISQEekyqchK4m7QptL1iaNT0SlUX323/Feuf9uJkj6MJHRIiVVq27W3cWk -fx1T42CwIJK2d8+9x5/+xR9X2XnUmsAX+g5CsWLqOAeoaQ03sB64xHkNP/T5YCEPcoWf/tywk8IK -PClrCoVZeVOT48CCGHfreQytzkDpRtsiB5NkrmpIcnZZ3km5ngxRYJKWgjavUEvO0QXKNcpxpsNi -G0upIDcEFpgso5MJ6tv0JPo53Wkx5616iKWUr5fmJGO5KeQ8LITK0zoh5HhCN+hOB3IvBD+zMP95 -tk6JTvHv4cgRiPtdqOzLVzFa+0w3ryfdyAsd1DbELrvIPl8flomke/vAjWhMNDt/146XbdMq3q0G -V9hzF9dy3sNQks+B95T0EHWMj+Xmxr0LtFjbZfMRzxdWlbVbg3C03zXIgWaRL6vdUwkxQHzlc5UE -Lku/+bbb4YtRogPWln9KulXkqvphdg1qW4991p8+rWe1Oq8UiU/rv7Diy2KGqnG8N6QSBI9hTv0I -5LH95fJ69seDMFZGlfZBojaTPKXSI2yWAgmSeZir327Vlb8Hx417xa7O86rl5JHyir4g/43VrJPZ -zriThgDzXHEVkmvrByMV5sW018KFa/SXcdtkPP6r3YX9hKZR6XmOZH9srzxo26UCfGzTLNAMFy7S -27hlD5khEmVNskCs03MOBdrfmOnVGCWEKvDjkJppOcHSJUWk0I+2nUrsbokt3egFhzsFzk5eQGAl -Vt1TfVM0vaBDcoEab3mdtsJB2DV5cRd4hlC0aLD9wcj/wHzw2a2yuR5lOLXUNaXL9gwvnCclVAAJ -TZnu4s6ROk4Wb13XyXaDTOmoXw4Z7rztuIPyDlfW/0RaXewrmDbZIYiLwF+Fuy040gGyDtOyDl9M -YZZwXZhO7gqlLc0ES0UsuTS6Qpe7TTZgkeqBjRTXrPmF04ak+uB+G0oKa0uWqGys94jBL7Ng/wbl -9BvZWCoPEhIjPmwVkhEEQbyx6+PElHs9WelUi4CJDEdV1amY2Gj3aO29DLn0wyLgwPLq7lFqEA+X -i1KgporjqQcjZOdkENi5HwQxzGI3ez+mnXhR/lO/reYnIR3t+KtCKxZlWde8JBmcNKFIBNz5cthc -wlkCyQmWeM+OgDzVnv76fLg3L72zlNt35hqLShPi5MEqwBvb4RBz4qjGgEjseV25HRkuSRWPUs8L -l8ojwRtkYABPf0qEOKvl66PcLqYBLryrp52a4zu0KiqBFkhCJsVkHBsHL9LXnjzRbPOYqtVEgol/ -XlCPYxiJMLUWOAyYPGmSsajXd/84qQvZa1IuTDG5/ofT+kT8S6x1ednhLisR428z3Ti8LDwE0/Uf -ounjLwGv45+Y3E6tRWSMNVHWe8Uy1CfISpahlvSARwu0NV3r+8DU8/7aXI/gFvWPjy5ZjZomZa7f -4+dH6e3J7XSpAkmikcLCps4dNUF1hexT3l+svNhMMGytWJiUlYwVK/t0W46bpe1pfKIRXvDchg2B -9w22rjdF2meS7yV2Lpg0TpdPZRiyTtR6zrQcIFVicsFIDCUzAItPytcMGL0DR/2QA25cBJwGKlU3 -4IHcREckW7qcGuWO38TGWVxYDWy6PRkifWkBXoGGN/MXeELBHOA72OHOmHejBpnewTVP4QNCTZ5g -Sp25UYpjd9XJmXGGRUiDZ5OnYlkj5cKWMPwR5Bz5qhY7r7eu+4LuVICgS4q8L6/igjD6r+/R2Q2W -N8Nzp/mxVmtnijnqLevjoR90uBL+aXo0tKgLKuIdmmCq+ziSiG/k6XYnKxO3AtrQSVCq+pV3mzL4 -7P8tP+vnsRA/XSdUjYtf9zShIZJW4J9zOWtLuYn0JemXOvUeMYUCNPmc0Q8zynSTItiwjRN3l/Ox -+kUKMeRHz9C+44ALafaDjNkA5ay9ruUC9NOHTL0FuD3mNrsxIgAUvFKjoMc/9Rv54TJ8XaETwXPu -Th0Xvgjvy7VpBivJtvi7KhB90aaTG5FSZFgvK5yLL+LvqQZ8NllH9QopCwo6mL2SE5jLovbRPNh/ -4syk0Smy7KRal3O8Wz4N2X/YNs0TyWqIru9mZh79lQQTp2VujNYmErFXIOf8dba0Il6kjiOA2ftD -vMpL+izKqcZP9k5MuSNiHTLQjGthOZJ1EQcwMPtB2JDj9SRhiBKOG5CowbfX/2vuGSHl4DrJe6BU -Z9zralmsAI2AFSKmDS4cItvdsZUV6JJPTVKNs4GX5pGjpVHH721NiqVYLqM3JmDqbDMPPoo11Y4k -XywsHMGnj5mnClNUzt/9VFOorFBeKXG1a049S8H/r0P+S1ZEiZda+CvDd6/zIICO1B2crvWlEoAw -fk3xpU9XAHkmlett5A8imTSPQaLqalg7448mRQ6dm3VgIIhgFtsbWCd5mHzlKO+12IeXe33PZaWK -9B/ZmMwevm24iHAYaj8Ni86WWOub4ndRPogRGwLBzM0mNmRsC5GleiVKCRTy9RWpjVkYiVWjQ/ey -erD0lITZuZ7/C+F/cB0akttjOqWNiFblTvWCvdECUC/OTxy4ml+BnFxSCYKeqfUsqbcXufg0G0FV -H9gUqtMFUwcYPxzUqLWW5HccI3ccz7lqhdFs0auPdoXRnzsdtw0KdNq3ly3aXRPyudxR1iq/ACtd -rO1+s+DZButoaRYzxPcB7pMXZfymWTjYCt/DRN8VhclNTNQKd+/YeYWQle3aM0uPgBg2bX7zG4E0 -Uw1BqLdSgdPc5+5jtVdzwHHl9eqviafpBQx7Q5hUM1zOyaNs9RZzrht5b30W/atk8E3lGi+oO726 -w5s9lvOkzgEau2mWaAcsv2h4++lMPRnWpEmIPcQD6tDPsfRIfF/+otmY+hunGr69zExBvubDuuTg -B07ksO8IpOgifd327K34NH496BE1WvxBhkcRERW6bo7OQhzr19KioKAiUmQHXmS/PwvSdp9G2UjS -bj+Oy59MGMTIvjjGW6S5ag0kTrteVxl8vAp91/SGUf1loXjX0CKXh5M/24No/TmSNtPhWW/D/jMD -sBkUFmLT5oTFgnLq77IHgj8Gwjy8CKaPduIJLAYHRPIYn4iV9IlpV7utKzt9E63Xqh5Jx3uFiw+0 -maZv6SObDYivZmvEms9+jjZy1cbKSCPBmlcyRQw1qZCZwJzVBT8qZuUNXIq9PZ1OCGSCw+JJg9aG -MPWbf+HoH0vdvQj8IArsU5zQqC0w0DPwIWdU1GTz57Lart6xOC5VzgNl7R9Vqw4+JhS7vkkuEJfK -94+cQ6dG6uUcbUzZWGIIWOhcAMn1KIvA75d+RFEPY+TZkZYtcVQc65AepTwWre94yrQuK7d9rquL -uZLxjCQaoF76S5LxDiVAGGo9bR/emE85sgIGo3hFLwp71MRlQoUi7JLYGqKlwYRfPNiKN2DtH8s2 -YNFag6UzAVNK0q5bXHDLMdn9nIadLd0lufQvPsOdSEv/mnYShvxX6Shm4gqCiBHmq4yUo08wZq/5 -FtzlHUQ9xJ0Oj+ynRaTGbVpVR/1ov+EY9lyVdVzsjHk3uWKcry0GCbenovirDapqytCKRCubvKxl -YCeLOtd+/AkdOmPOjwfMXSq/vSsiA4tgg7tJ/iT41UVyA57If5Ur1ulh+IX6zo5hW3Wm5YmI0eos -Xbou5NT3tRSbX14ZMM1oOSh0OWuphRCqPQL/rA+50csqYQTHFxOo5O4JsMu9pj3IL3LaNXb9yB16 -iLYRvdPIxk1T4Fb+eA6DA3YmhYIh+2vx11BNc1EZ3miNWFcEowXQzah/EvlozaVtFyv7gR3FLQmu -edVf2ApsN/aplVUa/suQ0/szocWyHMIdhjMUjrI6ZcWL4/dxHKecpcc2g88ZtLYJfqLVKqvg0kzQ -Iodi8G8V4twEAT4oXeI+EwLfxJopXRC92sDyZdfGBAjV0GRVIBYKmClloiFCFuGszAqHCeq1h8kM -Ix4LvCMpT10bOX6/qsQVy6KxmD7vys4KjTlJdjIBdtrqBSRivXntSMLHivajRSC67jhOZcvJMRC+ -CxWXhEH7n4kjgpfphsz1UfK+Z2NXgLFJzTwgEh/zovApnoP6mhElkGMVXSLE+TXTSo4Q4YM9Fx2N -aJMhUco6gCwEspBK6vXzgRAYvW//HudCuX8HB7GxLgwtiGQbACcn+81ccZTrgPYJ+kflDD87iidz -C6bNq2LCjnZIBHBhDObnQ8vWs7GX0/X+1uFCAHOsUeK+mRVlwMAxIP7/kZD/W3+CAP9PQERRBgr6 -fyjYf83330Ql+9/2fwBQSwECFAMUAAAACAC7ZhZJT+TXP2QiAABjIgAADwAAAAAAAAAAAAAAtoEA -AAAA4avjpqGglI2ROTQuemlwUEsFBgAAAAABAAEAPQAAAJEiAAAAAA== ---2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7 diff --git a/tests/email_testfiles/mail_1.eml.zip b/tests/email_testfiles/mail_1.eml.zip new file mode 100644 index 0000000..b01dab8 Binary files /dev/null and b/tests/email_testfiles/mail_1.eml.zip differ diff --git a/tests/mispevent_testfiles/existing_event.json b/tests/mispevent_testfiles/existing_event.json index 6d04a38..a123662 100644 --- a/tests/mispevent_testfiles/existing_event.json +++ b/tests/mispevent_testfiles/existing_event.json @@ -4590,7 +4590,7 @@ "org_id": "2", "orgc_id": "2", "proposal_email_lock": false, - "publish_timestamp": 0, + "publish_timestamp": "0", "published": false, "sharing_group_id": "0", "threat_level_id": "3", diff --git a/tests/mispevent_testfiles/existing_event_edited.json b/tests/mispevent_testfiles/existing_event_edited.json index b5937d3..da10762 100644 --- a/tests/mispevent_testfiles/existing_event_edited.json +++ b/tests/mispevent_testfiles/existing_event_edited.json @@ -4593,7 +4593,7 @@ "org_id": "2", "orgc_id": "2", "proposal_email_lock": false, - "publish_timestamp": 0, + "publish_timestamp": "0", "published": false, "sharing_group_id": "0", "threat_level_id": "3", diff --git a/tests/mispevent_testfiles/malware_exist.json b/tests/mispevent_testfiles/malware_exist.json index d118168..013304c 100644 --- a/tests/mispevent_testfiles/malware_exist.json +++ b/tests/mispevent_testfiles/malware_exist.json @@ -13,7 +13,7 @@ "distribution": "0", "proposal_email_lock": false, "locked": false, - "publish_timestamp": 0, + "publish_timestamp": "0", "sharing_group_id": "0", "disable_correlation": false, "event_creator_email": "raphael.vinot@circl.lu", diff --git a/tests/mispevent_testfiles/shadow.json b/tests/mispevent_testfiles/shadow.json index de0d5ad..61b484c 100644 --- a/tests/mispevent_testfiles/shadow.json +++ b/tests/mispevent_testfiles/shadow.json @@ -138,7 +138,7 @@ "org_id": "1", "orgc_id": "1", "proposal_email_lock": true, - "publish_timestamp": 0, + "publish_timestamp": "0", "published": false, "sharing_group_id": "0", "threat_level_id": "1", diff --git a/tests/test_emailobject.py b/tests/test_emailobject.py index 1940f5f..01cb9d0 100644 --- a/tests/test_emailobject.py +++ b/tests/test_emailobject.py @@ -1,17 +1,33 @@ -from email.message import EmailMessage +from __future__ import annotations + +# import json import unittest + +from email.message import EmailMessage from io import BytesIO -from typing import List -from pymisp.tools import EMailObject -from pathlib import Path from os import urandom -import json +from pathlib import Path +from typing import TypeVar, Type +from zipfile import ZipFile + +from pymisp.tools import EMailObject from pymisp.exceptions import PyMISPNotImplementedYet, InvalidMISPObject +T = TypeVar('T', bound='TestEmailObject') + class TestEmailObject(unittest.TestCase): - def test_mail_1(self): - email_object = EMailObject(Path("tests/email_testfiles/mail_1.eml")) + + eml_1: BytesIO + + @classmethod + def setUpClass(cls: type[T]) -> None: + with ZipFile(Path("tests/email_testfiles/mail_1.eml.zip"), 'r') as myzip: + with myzip.open('mail_1.eml', pwd=b'AVs are dumb') as myfile: + cls.eml_1 = BytesIO(myfile.read()) + + def test_mail_1(self) -> None: + email_object = EMailObject(pseudofile=self.eml_1) self.assertEqual(self._get_values(email_object, "subject")[0], "письмо уведом-е") self.assertEqual(self._get_values(email_object, "to")[0], "kinney@noth.com") self.assertEqual(self._get_values(email_object, "from")[0], "suvorov.s@nalg.ru") @@ -27,7 +43,7 @@ class TestEmailObject(unittest.TestCase): self.assertIsInstance(file_name, str) self.assertIsInstance(file_content, BytesIO) - def test_mail_1_headers_only(self): + def test_mail_1_headers_only(self) -> None: email_object = EMailObject(Path("tests/email_testfiles/mail_1_headers_only.eml")) self.assertEqual(self._get_values(email_object, "subject")[0], "письмо уведом-е") self.assertEqual(self._get_values(email_object, "to")[0], "kinney@noth.com") @@ -38,7 +54,7 @@ class TestEmailObject(unittest.TestCase): self.assertIsInstance(email_object.email, EmailMessage) self.assertEqual(len(email_object.attachments), 0) - def test_mail_multiple_to(self): + def test_mail_multiple_to(self) -> None: email_object = EMailObject(Path("tests/email_testfiles/mail_multiple_to.eml")) to = self._get_values(email_object, "to") @@ -48,9 +64,9 @@ class TestEmailObject(unittest.TestCase): self.assertEqual(to[1], "jan.marek@example.com") self.assertEqual(to_display_name[1], "Marek, Jan") - def test_msg(self): + def test_msg(self) -> None: # Test result of eml converted to msg is the same - eml_email_object = EMailObject(Path("tests/email_testfiles/mail_1.eml")) + eml_email_object = EMailObject(pseudofile=self.eml_1) email_object = EMailObject(Path("tests/email_testfiles/mail_1.msg")) self.assertIsInstance(email_object.email, EmailMessage) @@ -64,20 +80,17 @@ class TestEmailObject(unittest.TestCase): self._get_values(eml_email_object, "to")[0]) self.assertEqual(self._get_values(email_object, "from")[0], self._get_values(eml_email_object, "from")[0]) - dirty_display_name = self._get_values(email_object, "from-display-name")[0] - dirty_display_name = dirty_display_name[:-2] + dirty_display_name[-1] - self.assertEqual(dirty_display_name, + self.assertEqual(self._get_values(email_object, "from-display-name")[0], self._get_values(eml_email_object, "from-display-name")[0]) self.assertEqual(len(self._get_values(email_object, "email-body")), 2) self.assertEqual(self._get_values(email_object, "received-header-ip"), self._get_values(eml_email_object, "received-header-ip")) - - def test_bom_encoded(self): + def test_bom_encoded(self) -> None: """Test utf-8-sig encoded email""" bom_email_object = EMailObject(Path("tests/email_testfiles/mail_1_bom.eml")) - eml_email_object = EMailObject(Path("tests/email_testfiles/mail_1.eml")) + eml_email_object = EMailObject(pseudofile=self.eml_1) self.assertIsInstance(bom_email_object.email, EmailMessage) for file_name, file_content in bom_email_object.attachments: @@ -97,7 +110,7 @@ class TestEmailObject(unittest.TestCase): self.assertEqual(self._get_values(bom_email_object, "received-header-ip"), self._get_values(eml_email_object, "received-header-ip")) - def test_handling_of_various_email_types(self): + def test_handling_of_various_email_types(self) -> None: self._does_not_fail(Path("tests/email_testfiles/mail_2.eml"), "ensuring all headers work") self._does_not_fail(Path('tests/email_testfiles/mail_3.eml'), @@ -109,7 +122,7 @@ class TestEmailObject(unittest.TestCase): self._does_not_fail(Path('tests/email_testfiles/mail_5.msg'), "Check encapsulated HTML works") - def _does_not_fail(self, path, test_type="test"): + def _does_not_fail(self, path: Path, test_type: str="test") -> None: found_error = None try: EMailObject(path) @@ -121,7 +134,7 @@ class TestEmailObject(unittest.TestCase): path, test_type)) - def test_random_binary_blob(self): + def test_random_binary_blob(self) -> None: """Email parser fails correctly on random binary blob.""" random_data = urandom(1024) random_blob = BytesIO(random_data) @@ -136,10 +149,9 @@ class TestEmailObject(unittest.TestCase): broken_obj = EMailObject(pseudofile=random_blob) except Exception as _e: found_error = _e - if not isinstance(found_error, PyMISPNotImplementedYet): - self.fail("Expected PyMISPNotImplementedYet when EmailObject receives completely unknown binary input data in a pseudofile. But, did not get that exception.") - + if not isinstance(found_error, InvalidMISPObject): + self.fail("Expected InvalidMISPObject when EmailObject receives completely unknown binary input data in a pseudofile. But, did not get that exception.") @staticmethod - def _get_values(obj: EMailObject, relation: str) -> List[str]: + def _get_values(obj: EMailObject, relation: str) -> list[str]: return [attr.value for attr in obj.attributes if attr['object_relation'] == relation] diff --git a/tests/test_fileobject.py b/tests/test_fileobject.py index 9b6e80d..1299b3a 100644 --- a/tests/test_fileobject.py +++ b/tests/test_fileobject.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations import unittest import json @@ -8,7 +9,7 @@ import pathlib class TestFileObject(unittest.TestCase): - def test_mimeType(self): + def test_mimeType(self) -> None: file_object = FileObject(filepath=pathlib.Path(__file__)) attributes = json.loads(file_object.to_json())['Attribute'] mime = next(attr for attr in attributes if attr['object_relation'] == 'mimetype') diff --git a/tests/test_mispevent.py b/tests/test_mispevent.py index 7d27a18..8c9564c 100644 --- a/tests/test_mispevent.py +++ b/tests/test_mispevent.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations import unittest import json @@ -16,89 +17,89 @@ from pymisp.tools import GitVulnFinderObject class TestMISPEvent(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.maxDiff = None self.mispevent = MISPEvent() - def init_event(self): + def init_event(self) -> None: self.mispevent.info = 'This is a test' self.mispevent.distribution = 1 self.mispevent.threat_level_id = 1 self.mispevent.analysis = 1 self.mispevent.set_date("2017-12-31") # test the set date method - def test_simple(self): - with open('tests/mispevent_testfiles/simple.json', 'r') as f: + def test_simple(self) -> None: + with open('tests/mispevent_testfiles/simple.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_event(self): + def test_event(self) -> None: self.init_event() self.mispevent.publish() - with open('tests/mispevent_testfiles/event.json', 'r') as f: + with open('tests/mispevent_testfiles/event.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_loadfile(self): + def test_loadfile(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/event.json') - with open('tests/mispevent_testfiles/event.json', 'r') as f: + with open('tests/mispevent_testfiles/event.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_loadfile_validate(self): + def test_loadfile_validate(self) -> None: misp_event = MISPEvent() misp_event.load_file('tests/mispevent_testfiles/event.json', validate=True) - def test_loadfile_validate_strict(self): + def test_loadfile_validate_strict(self) -> None: misp_event = MISPEvent(strict_validation=True) misp_event.load_file('tests/mispevent_testfiles/event.json', validate=True) - def test_event_tag(self): + def test_event_tag(self) -> None: self.init_event() self.mispevent.add_tag('bar') self.mispevent.add_tag(name='baz') new_tag = MISPTag() new_tag.from_dict(name='foo') self.mispevent.add_tag(new_tag) - with open('tests/mispevent_testfiles/event_tags.json', 'r') as f: + with open('tests/mispevent_testfiles/event_tags.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_event_galaxy(self): + def test_event_galaxy(self) -> None: self.init_event() - with open('tests/mispevent_testfiles/galaxy.json', 'r') as f: + with open('tests/mispevent_testfiles/galaxy.json') as f: galaxy = json.load(f) misp_galaxy = MISPGalaxy() misp_galaxy.from_dict(**galaxy) self.mispevent.add_galaxy(misp_galaxy) self.assertEqual(self.mispevent.galaxies[0].to_json(sort_keys=True, indent=2), json.dumps(galaxy, sort_keys=True, indent=2)) - def test_attribute(self): + def test_attribute(self) -> None: self.init_event() - a = self.mispevent.add_attribute('filename', 'bar.exe') + a: MISPAttribute = self.mispevent.add_attribute('filename', 'bar.exe') # type: ignore[assignment] del a.uuid - a = self.mispevent.add_attribute_tag('osint', 'bar.exe') + a = self.mispevent.add_attribute_tag('osint', 'bar.exe') # type: ignore[assignment] attr_tags = self.mispevent.get_attribute_tag('bar.exe') self.assertEqual(self.mispevent.attributes[0].tags[0].name, 'osint') self.assertEqual(attr_tags[0].name, 'osint') - with open('tests/mispevent_testfiles/attribute.json', 'r') as f: + with open('tests/mispevent_testfiles/attribute.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) # Fake setting an attribute ID for testing self.mispevent.attributes[0].id = 42 - self.mispevent.delete_attribute(42) - with open('tests/mispevent_testfiles/attribute_del.json', 'r') as f: + self.mispevent.delete_attribute('42') + with open('tests/mispevent_testfiles/attribute_del.json') as f: ref_json = json.load(f) self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_attribute_galaxy(self): + def test_attribute_galaxy(self) -> None: self.init_event() - with open('tests/mispevent_testfiles/galaxy.json', 'r') as f: + with open('tests/mispevent_testfiles/galaxy.json') as f: galaxy = json.load(f) misp_galaxy = MISPGalaxy() misp_galaxy.from_dict(**galaxy) @@ -111,7 +112,7 @@ class TestMISPEvent(unittest.TestCase): json.dumps(galaxy, sort_keys=True, indent=2) ) - def test_to_dict_json_format(self): + def test_to_dict_json_format(self) -> None: misp_event = MISPEvent() av_signature_object = MISPObject("av-signature") av_signature_object.add_attribute("signature", "EICAR") @@ -120,47 +121,47 @@ class TestMISPEvent(unittest.TestCase): self.assertEqual(json.loads(misp_event.to_json()), misp_event.to_dict(json_format=True)) - def test_object_tag(self): + def test_object_tag(self) -> None: self.mispevent.add_object(name='file', strict=True) - a = self.mispevent.objects[0].add_attribute('filename', value='') + a: MISPAttribute = self.mispevent.objects[0].add_attribute('filename', value='') # type: ignore[assignment] self.assertEqual(a, None) - a = self.mispevent.objects[0].add_attribute('filename', value=None) + a = self.mispevent.objects[0].add_attribute('filename', value=None) # type: ignore[assignment] self.assertEqual(a, None) - a = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}]) + a = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}]) # type: ignore[assignment] del a.uuid self.assertEqual(self.mispevent.objects[0].attributes[0].tags[0].name, 'blah') self.assertTrue(self.mispevent.objects[0].has_attributes_by_relation(['filename'])) self.assertEqual(len(self.mispevent.objects[0].get_attributes_by_relation('filename')), 1) self.mispevent.add_object(name='url', strict=True) - a = self.mispevent.objects[1].add_attribute('url', value='https://www.circl.lu') + a = self.mispevent.objects[1].add_attribute('url', value='https://www.circl.lu') # type: ignore[assignment] del a.uuid self.mispevent.objects[0].uuid = 'a' self.mispevent.objects[1].uuid = 'b' reference = self.mispevent.objects[0].add_reference(self.mispevent.objects[1], 'baz', comment='foo') del reference.uuid self.assertEqual(self.mispevent.objects[0].references[0].relationship_type, 'baz') - with open('tests/mispevent_testfiles/event_obj_attr_tag.json', 'r') as f: + with open('tests/mispevent_testfiles/event_obj_attr_tag.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) @unittest.skip("Not supported on MISP: https://github.com/MISP/MISP/issues/2638 - https://github.com/MISP/PyMISP/issues/168") - def test_object_level_tag(self): + def test_object_level_tag(self) -> None: self.mispevent.add_object(name='file', strict=True) self.mispevent.objects[0].add_attribute('filename', value='bar') - self.mispevent.objects[0].add_tag('osint') + self.mispevent.objects[0].add_tag('osint') # type: ignore[attr-defined] self.mispevent.objects[0].uuid = 'a' - with open('tests/mispevent_testfiles/event_obj_tag.json', 'r') as f: + with open('tests/mispevent_testfiles/event_obj_tag.json') as f: ref_json = json.load(f) self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_object_galaxy(self): + def test_object_galaxy(self) -> None: self.init_event() misp_object = MISPObject('github-user') misp_object.add_attribute('username', 'adulau') misp_object.add_attribute('repository', 'cve-search') self.mispevent.add_object(misp_object) - with open('tests/mispevent_testfiles/galaxy.json', 'r') as f: + with open('tests/mispevent_testfiles/galaxy.json') as f: galaxy = json.load(f) misp_galaxy = MISPGalaxy() misp_galaxy.from_dict(**galaxy) @@ -170,95 +171,95 @@ class TestMISPEvent(unittest.TestCase): json.dumps(galaxy, sort_keys=True, indent=2) ) - def test_malware(self): + def test_malware(self) -> None: with open('tests/mispevent_testfiles/simple.json', 'rb') as f: pseudofile = BytesIO(f.read()) self.init_event() - a = self.mispevent.add_attribute('malware-sample', 'bar.exe', data=pseudofile) + a: MISPAttribute = self.mispevent.add_attribute('malware-sample', 'bar.exe', data=pseudofile) # type: ignore[assignment] del a.uuid attribute = self.mispevent.attributes[0] self.assertEqual(attribute.malware_binary, pseudofile) - with open('tests/mispevent_testfiles/malware.json', 'r') as f: + with open('tests/mispevent_testfiles/malware.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_existing_malware(self): + def test_existing_malware(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/malware_exist.json') with open('tests/mispevent_testfiles/simple.json', 'rb') as f: pseudofile = BytesIO(f.read()) - self.assertEqual( - self.mispevent.objects[0].get_attributes_by_relation('malware-sample')[0].malware_binary.read(), - pseudofile.read()) + self.assertTrue(self.mispevent.objects[0].get_attributes_by_relation('malware-sample')[0].malware_binary) + if _mb := self.mispevent.objects[0].get_attributes_by_relation('malware-sample')[0].malware_binary: + self.assertEqual(_mb.read(), pseudofile.read()) - def test_sighting(self): + def test_sighting(self) -> None: sighting = MISPSighting() sighting.from_dict(value='1', type='bar', timestamp=11111111) - with open('tests/mispevent_testfiles/sighting.json', 'r') as f: + with open('tests/mispevent_testfiles/sighting.json') as f: ref_json = json.load(f) self.assertEqual(sighting.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_existing_event(self): + def test_existing_event(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') - with open('tests/mispevent_testfiles/existing_event.json', 'r') as f: + with open('tests/mispevent_testfiles/existing_event.json') as f: ref_json = json.load(f) self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_shadow_attributes_existing(self): + def test_shadow_attributes_existing(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/shadow.json') - with open('tests/mispevent_testfiles/shadow.json', 'r') as f: + with open('tests/mispevent_testfiles/shadow.json') as f: ref_json = json.load(f) self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) @unittest.skip("Not supported on MISP.") - def test_shadow_attributes(self): + def test_shadow_attributes(self) -> None: self.init_event() p = self.mispevent.add_proposal(type='filename', value='baz.jpg') del p.uuid - a = self.mispevent.add_attribute('filename', 'bar.exe') + a: MISPAttribute = self.mispevent.add_attribute('filename', 'bar.exe') # type: ignore[assignment] del a.uuid p = self.mispevent.attributes[0].add_proposal(type='filename', value='bar.pdf') del p.uuid - with open('tests/mispevent_testfiles/proposals.json', 'r') as f: + with open('tests/mispevent_testfiles/proposals.json') as f: ref_json = json.load(f) self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_default_attributes(self): + def test_default_attributes(self) -> None: self.mispevent.add_object(name='file', strict=True) - a = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}]) + a: MISPAttribute = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}]) # type: ignore[assignment] del a.uuid - a = self.mispevent.objects[0].add_attribute('pattern-in-file', value='baz') + a = self.mispevent.objects[0].add_attribute('pattern-in-file', value='baz') # type: ignore[assignment] self.assertEqual(a.category, 'Artifacts dropped') del a.uuid self.mispevent.add_object(name='file', strict=False, default_attributes_parameters=self.mispevent.objects[0].attributes[0]) - a = self.mispevent.objects[1].add_attribute('filename', value='baz') + a = self.mispevent.objects[1].add_attribute('filename', value='baz') # type: ignore[assignment] del a.uuid self.mispevent.objects[0].uuid = 'a' self.mispevent.objects[1].uuid = 'b' - with open('tests/mispevent_testfiles/event_obj_def_param.json', 'r') as f: + with open('tests/mispevent_testfiles/event_obj_def_param.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_obj_default_values(self): + def test_obj_default_values(self) -> None: self.init_event() self.mispevent.add_object(name='whois', strict=True) - a = self.mispevent.objects[0].add_attribute('registrar', value='registar.example.com') + a: MISPAttribute = self.mispevent.objects[0].add_attribute('registrar', value='registar.example.com') # type: ignore[assignment] del a.uuid - a = self.mispevent.objects[0].add_attribute('domain', value='domain.example.com') + a = self.mispevent.objects[0].add_attribute('domain', value='domain.example.com') # type: ignore[assignment] del a.uuid - a = self.mispevent.objects[0].add_attribute('nameserver', value='ns1.example.com') + a = self.mispevent.objects[0].add_attribute('nameserver', value='ns1.example.com') # type: ignore[assignment] del a.uuid - a = self.mispevent.objects[0].add_attribute('nameserver', value='ns2.example.com', disable_correlation=False, to_ids=True, category='External analysis') + a = self.mispevent.objects[0].add_attribute('nameserver', value='ns2.example.com', disable_correlation=False, to_ids=True, category='External analysis') # type: ignore[assignment] del a.uuid self.mispevent.objects[0].uuid = 'a' - with open('tests/mispevent_testfiles/def_param.json', 'r') as f: + with open('tests/mispevent_testfiles/def_param.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_obj_references_export(self): + def test_obj_references_export(self) -> None: self.init_event() obj1 = MISPObject(name="file") obj2 = MISPObject(name="url", standalone=False) @@ -271,29 +272,29 @@ class TestMISPEvent(unittest.TestCase): self.assertTrue("ObjectReference" in obj1.jsonable()) self.assertFalse("ObjectReference" in obj2.jsonable()) - def test_event_not_edited(self): + def test_event_not_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) - def test_event_edited(self): + def test_event_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.mispevent.info = 'blah' self.assertTrue(self.mispevent.edited) - def test_event_tag_edited(self): + def test_event_tag_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) self.mispevent.add_tag('foo') self.assertTrue(self.mispevent.edited) - def test_event_attribute_edited(self): + def test_event_attribute_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.mispevent.attributes[0].value = 'blah' self.assertTrue(self.mispevent.attributes[0].edited) self.assertFalse(self.mispevent.attributes[1].edited) self.assertTrue(self.mispevent.edited) - def test_event_attribute_tag_edited(self): + def test_event_attribute_tag_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) self.mispevent.attributes[0].tags[0].name = 'blah' @@ -302,14 +303,14 @@ class TestMISPEvent(unittest.TestCase): self.assertTrue(self.mispevent.attributes[0].edited) self.assertTrue(self.mispevent.edited) - def test_event_attribute_tag_edited_second(self): + def test_event_attribute_tag_edited_second(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) self.mispevent.attributes[0].add_tag(name='blah') self.assertTrue(self.mispevent.attributes[0].edited) self.assertTrue(self.mispevent.edited) - def test_event_object_edited(self): + def test_event_object_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) self.mispevent.objects[0].comment = 'blah' @@ -317,7 +318,7 @@ class TestMISPEvent(unittest.TestCase): self.assertFalse(self.mispevent.objects[1].edited) self.assertTrue(self.mispevent.edited) - def test_event_object_attribute_edited(self): + def test_event_object_attribute_edited(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) self.mispevent.objects[0].attributes[0].comment = 'blah' @@ -325,23 +326,23 @@ class TestMISPEvent(unittest.TestCase): self.assertTrue(self.mispevent.objects[0].edited) self.assertTrue(self.mispevent.edited) - def test_event_object_attribute_edited_tag(self): + def test_event_object_attribute_edited_tag(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') self.assertFalse(self.mispevent.edited) self.mispevent.objects[0].attributes[0].add_tag('blah') self.assertTrue(self.mispevent.objects[0].attributes[0].edited) self.assertTrue(self.mispevent.objects[0].edited) self.assertTrue(self.mispevent.edited) - with open('tests/mispevent_testfiles/existing_event_edited.json', 'r') as f: + with open('tests/mispevent_testfiles/existing_event_edited.json') as f: ref_json = json.load(f) self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_obj_by_id(self): + def test_obj_by_id(self) -> None: self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json') misp_obj = self.mispevent.get_object_by_id(1556) self.assertEqual(misp_obj.uuid, '5a3cd604-e11c-4de5-bbbf-c170950d210f') - def test_userdefined_object_custom_template(self): + def test_userdefined_object_custom_template(self) -> None: self.init_event() with open('tests/mispevent_testfiles/test_object_template/definition.json') as f: template = json.load(f) @@ -352,16 +353,16 @@ class TestMISPEvent(unittest.TestCase): self.mispevent.to_json(sort_keys=True, indent=2) self.assertEqual(e.exception.message, '{\'member3\'} are required.') - a = self.mispevent.objects[0].add_attribute('member3', value='foo') + a: MISPAttribute = self.mispevent.objects[0].add_attribute('member3', value='foo') # type: ignore[assignment] del a.uuid with self.assertRaises(InvalidMISPObject) as e: # Fail on requiredOneOf self.mispevent.to_json(sort_keys=True, indent=2) self.assertEqual(e.exception.message, 'At least one of the following attributes is required: member1, member2') - a = self.mispevent.objects[0].add_attribute('member1', value='bar') + a = self.mispevent.objects[0].add_attribute('member1', value='bar') # type: ignore[assignment] del a.uuid - a = self.mispevent.objects[0].add_attribute('member1', value='baz') + a = self.mispevent.objects[0].add_attribute('member1', value='baz') # type: ignore[assignment] del a.uuid with self.assertRaises(InvalidMISPObject) as e: # member1 is not a multiple @@ -370,12 +371,12 @@ class TestMISPEvent(unittest.TestCase): self.mispevent.objects[0].attributes = self.mispevent.objects[0].attributes[:2] self.mispevent.objects[0].uuid = 'a' - with open('tests/mispevent_testfiles/misp_custom_obj.json', 'r') as f: + with open('tests/mispevent_testfiles/misp_custom_obj.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_userdefined_object_custom_dir(self): + def test_userdefined_object_custom_dir(self) -> None: self.init_event() self.mispevent.add_object(name='test_object_template', strict=True, misp_objects_path_custom='tests/mispevent_testfiles') with self.assertRaises(InvalidMISPObject) as e: @@ -383,16 +384,16 @@ class TestMISPEvent(unittest.TestCase): self.mispevent.to_json(sort_keys=True, indent=2) self.assertEqual(e.exception.message, '{\'member3\'} are required.') - a = self.mispevent.objects[0].add_attribute('member3', value='foo') + a: MISPAttribute = self.mispevent.objects[0].add_attribute('member3', value='foo') # type: ignore[assignment] del a.uuid with self.assertRaises(InvalidMISPObject) as e: # Fail on requiredOneOf self.mispevent.to_json(sort_keys=True, indent=2) self.assertEqual(e.exception.message, 'At least one of the following attributes is required: member1, member2') - a = self.mispevent.objects[0].add_attribute('member1', value='bar') + a = self.mispevent.objects[0].add_attribute('member1', value='bar') # type: ignore[assignment] del a.uuid - a = self.mispevent.objects[0].add_attribute('member1', value='baz') + a = self.mispevent.objects[0].add_attribute('member1', value='baz') # type: ignore[assignment] del a.uuid with self.assertRaises(InvalidMISPObject) as e: # member1 is not a multiple @@ -401,15 +402,15 @@ class TestMISPEvent(unittest.TestCase): self.mispevent.objects[0].attributes = self.mispevent.objects[0].attributes[:2] self.mispevent.objects[0].uuid = 'a' - with open('tests/mispevent_testfiles/misp_custom_obj.json', 'r') as f: + with open('tests/mispevent_testfiles/misp_custom_obj.json') as f: ref_json = json.load(f) del self.mispevent.uuid self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2)) - def test_first_last_seen(self): + def test_first_last_seen(self) -> None: me = MISPEvent() me.info = 'Test First and Last Seen' - me.date = '2020.01.12' + me.date = '2020.01.12' # type: ignore[assignment] self.assertEqual(me.date.day, 12) me.add_attribute('ip-dst', '8.8.8.8', first_seen='06-21-1998', last_seen=1580213607.469571) self.assertEqual(me.attributes[0].first_seen.year, 1998) @@ -417,11 +418,11 @@ class TestMISPEvent(unittest.TestCase): now = datetime.now().astimezone() me.attributes[0].last_seen = now today = date.today() - me.attributes[0].first_seen = today + me.attributes[0].first_seen = today # type: ignore[assignment] self.assertEqual(me.attributes[0].first_seen.year, today.year) self.assertEqual(me.attributes[0].last_seen, now) - def test_feed(self): + def test_feed(self) -> None: me = MISPEvent() me.info = 'Test feed' org = MISPOrganisation() @@ -439,7 +440,7 @@ class TestMISPEvent(unittest.TestCase): self.assertEqual(feed['Event']['_manifest'][me.uuid]['info'], 'Test feed') self.assertEqual(len(feed['Event']['Object'][0]['Attribute']), 2) - def test_object_templates(self): + def test_object_templates(self) -> None: me = MISPEvent() for template in glob.glob(str(me.misp_objects_path / '*' / 'definition.json')): with open(template) as f: @@ -458,7 +459,7 @@ class TestMISPEvent(unittest.TestCase): subset = set(entry['categories']).issubset(me.describe_types['categories']) self.assertTrue(subset, f'{t_json["name"]} - {obj_relation}') - def test_git_vuln_finder(self): + def test_git_vuln_finder(self) -> None: with open('tests/git-vuln-finder-quagga.json') as f: dump = json.load(f) diff --git a/tests/test_reportlab.py b/tests/test_reportlab.py index 8fd40ee..d3b589b 100644 --- a/tests/test_reportlab.py +++ b/tests/test_reportlab.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +from __future__ import annotations import os import sys diff --git a/tests/testlive_comprehensive.py b/tests/testlive_comprehensive.py index aed4950..e741acc 100644 --- a/tests/testlive_comprehensive.py +++ b/tests/testlive_comprehensive.py @@ -1,41 +1,34 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- +from __future__ import annotations + +import hashlib +import json +import logging import os -import sys - +import time import unittest -from pymisp.tools import make_binary_objects from datetime import datetime, timedelta, date, timezone from io import BytesIO -import json from pathlib import Path -import hashlib - -import urllib3 # type: ignore -import time +from typing import TypeVar, Any from uuid import uuid4 -import email - -from collections import defaultdict - -import logging -logging.disable(logging.CRITICAL) -logger = logging.getLogger('pymisp') +import urllib3 +from pymisp.tools import make_binary_objects try: - from pymisp import register_user, PyMISP, MISPEvent, MISPOrganisation, MISPUser, Distribution, ThreatLevel, Analysis, MISPObject, MISPAttribute, MISPSighting, MISPShadowAttribute, MISPTag, MISPSharingGroup, MISPFeed, MISPServer, MISPUserSetting, MISPEventBlocklist, MISPEventReport, MISPCorrelationExclusion, MISPGalaxyCluster + from pymisp import (register_user, PyMISP, MISPEvent, MISPOrganisation, + MISPUser, Distribution, ThreatLevel, Analysis, MISPObject, + MISPAttribute, MISPSighting, MISPShadowAttribute, MISPTag, + MISPSharingGroup, MISPFeed, MISPServer, MISPUserSetting, + MISPEventReport, MISPCorrelationExclusion, MISPGalaxyCluster, + MISPGalaxy, MISPOrganisationBlocklist, MISPEventBlocklist) from pymisp.tools import CSVLoader, DomainIPObject, ASNObject, GenericObjectGenerator - from pymisp.exceptions import MISPServerError except ImportError: - if sys.version_info < (3, 6): - print('This test suite requires Python 3.6+, breaking.') - sys.exit(0) - else: - raise + raise try: from keys import url, key # type: ignore @@ -46,6 +39,8 @@ except ImportError as e: key = 'sL9hrjIyY405RyGQHLx5DoCAM92BNmmGa8P4ck1E' verifycert = False +logging.disable(logging.CRITICAL) +logger = logging.getLogger('pymisp') urllib3.disable_warnings() @@ -59,11 +54,23 @@ if not test_file_path.exists(): print('The test files are missing, pulling it.') os.system('git clone https://github.com/viper-framework/viper-test-files.git tests/viper-test-files') +T = TypeVar('T', bound='TestComprehensive') + class TestComprehensive(unittest.TestCase): + admin_misp_connector: PyMISP + user_misp_connector: PyMISP + test_usr: MISPUser + test_pub: MISPUser + test_org: MISPOrganisation + test_org_delegate: MISPOrganisation + delegate_user_misp_connector: PyMISP + pub_misp_connector: PyMISP + test_usr_delegate: MISPUser + @classmethod - def setUpClass(cls): + def setUpClass(cls: type[T]) -> None: cls.maxDiff = None # Connect as admin cls.admin_misp_connector = PyMISP(url, key, verifycert, debug=False) @@ -75,18 +82,18 @@ class TestComprehensive(unittest.TestCase): # Creates an org organisation = MISPOrganisation() organisation.name = 'Test Org' - cls.test_org = cls.admin_misp_connector.add_organisation(organisation, pythonify=True) + cls.test_org = cls.admin_misp_connector.add_organisation(organisation, pythonify=True) # type: ignore[assignment] # Create an org to delegate to organisation = MISPOrganisation() organisation.name = 'Test Org - delegate' - cls.test_org_delegate = cls.admin_misp_connector.add_organisation(organisation, pythonify=True) + cls.test_org_delegate = cls.admin_misp_connector.add_organisation(organisation, pythonify=True) # type: ignore[assignment] # Set the refault role (id 3 on the VM) cls.admin_misp_connector.set_default_role(3) # Creates a user user = MISPUser() user.email = 'testusr@user.local' user.org_id = cls.test_org.id - cls.test_usr = cls.admin_misp_connector.add_user(user, pythonify=True) + cls.test_usr = cls.admin_misp_connector.add_user(user, pythonify=True) # type: ignore[assignment] cls.user_misp_connector = PyMISP(url, cls.test_usr.authkey, verifycert, debug=True) cls.user_misp_connector.toggle_global_pythonify() # Creates a publisher @@ -94,14 +101,14 @@ class TestComprehensive(unittest.TestCase): user.email = 'testpub@user.local' user.org_id = cls.test_org.id user.role_id = 4 - cls.test_pub = cls.admin_misp_connector.add_user(user, pythonify=True) + cls.test_pub = cls.admin_misp_connector.add_user(user, pythonify=True) # type: ignore[assignment] cls.pub_misp_connector = PyMISP(url, cls.test_pub.authkey, verifycert) # Creates a user that can accept a delegation request user = MISPUser() user.email = 'testusr@delegate.recipient.local' user.org_id = cls.test_org_delegate.id user.role_id = 2 - cls.test_usr_delegate = cls.admin_misp_connector.add_user(user, pythonify=True) + cls.test_usr_delegate = cls.admin_misp_connector.add_user(user, pythonify=True) # type: ignore[assignment] cls.delegate_user_misp_connector = PyMISP(url, cls.test_usr_delegate.authkey, verifycert, debug=False) cls.delegate_user_misp_connector.toggle_global_pythonify() if not fast_mode: @@ -114,7 +121,7 @@ class TestComprehensive(unittest.TestCase): cls.admin_misp_connector.load_default_feeds() @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: # Delete publisher cls.admin_misp_connector.delete_user(cls.test_pub) # Delete user @@ -124,7 +131,7 @@ class TestComprehensive(unittest.TestCase): cls.admin_misp_connector.delete_organisation(cls.test_org) cls.admin_misp_connector.delete_organisation(cls.test_org_delegate) - def create_simple_event(self, force_timestamps=False): + def create_simple_event(self, force_timestamps: bool=False) -> MISPEvent: mispevent = MISPEvent(force_timestamps=force_timestamps) mispevent.info = 'This is a super simple test' mispevent.distribution = Distribution.your_organisation_only @@ -133,7 +140,7 @@ class TestComprehensive(unittest.TestCase): mispevent.add_attribute('text', str(uuid4())) return mispevent - def environment(self): + def environment(self) -> tuple[MISPEvent, MISPEvent, MISPEvent]: first_event = MISPEvent() first_event.info = 'First event - org only - low - completed' first_event.distribution = Distribution.your_organisation_only @@ -175,13 +182,13 @@ class TestComprehensive(unittest.TestCase): # Create first and third event as admin # usr won't be able to see the first one - first = self.admin_misp_connector.add_event(first_event, pythonify=True) - third = self.admin_misp_connector.add_event(third_event, pythonify=True) + first: MISPEvent = self.admin_misp_connector.add_event(first_event, pythonify=True) # type: ignore[assignment] + third: MISPEvent = self.admin_misp_connector.add_event(third_event, pythonify=True) # type: ignore[assignment] # Create second event as user - second = self.user_misp_connector.add_event(second_event) + second: MISPEvent = self.user_misp_connector.add_event(second_event) # type: ignore[assignment] return first, second, third - def test_server_settings(self): + def test_server_settings(self) -> None: settings = self.admin_misp_connector.server_settings() for final_setting in settings['finalSettings']: if final_setting['setting'] == 'MISP.max_correlations_per_event': @@ -206,7 +213,7 @@ class TestComprehensive(unittest.TestCase): setting = self.admin_misp_connector.get_server_setting('MISP.live') self.assertTrue(setting['value']) - def test_search_value_event(self): + def test_search_value_event(self) -> None: '''Search a value on the event controller * Test ACL admin user vs normal user in an other org * Make sure we have one match @@ -214,17 +221,17 @@ class TestComprehensive(unittest.TestCase): try: first, second, third = self.environment() # Search as admin - events = self.admin_misp_connector.search(value=first.attributes[0].value, pythonify=True) + events: list[MISPEvent] = self.admin_misp_connector.search(value=first.attributes[0].value, pythonify=True) # type: ignore[assignment] self.assertEqual(len(events), 2) for e in events: self.assertIn(e.id, [first.id, second.id]) # Search as user - events = self.user_misp_connector.search(value=first.attributes[0].value) + events = self.user_misp_connector.search(value=first.attributes[0].value) # type: ignore[assignment] self.assertEqual(len(events), 1) for e in events: self.assertIn(e.id, [second.id]) # Non-existing value - events = self.user_misp_connector.search(value=str(uuid4())) + events = self.user_misp_connector.search(value=str(uuid4())) # type: ignore[assignment] self.assertEqual(events, []) finally: # Delete events @@ -232,37 +239,37 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_value_attribute(self): + def test_search_value_attribute(self) -> None: '''Search value in attributes controller''' try: first, second, third = self.environment() # Search as admin - attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, pythonify=True) + attributes: list[MISPAttribute] = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, pythonify=True) # type: ignore[assignment] self.assertEqual(len(attributes), 2) for a in attributes: self.assertIn(a.event_id, [first.id, second.id]) # Search as user - attributes = self.user_misp_connector.search(controller='attributes', value=first.attributes[0].value) + attributes = self.user_misp_connector.search(controller='attributes', value=first.attributes[0].value) # type: ignore[assignment] self.assertEqual(len(attributes), 1) for a in attributes: self.assertIn(a.event_id, [second.id]) # Non-existing value - attributes = self.user_misp_connector.search(controller='attributes', value=str(uuid4())) + attributes = self.user_misp_connector.search(controller='attributes', value=str(uuid4())) # type: ignore[assignment] self.assertEqual(attributes, []) # Include context - search as user (can only see one event) - attributes = self.user_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_context=True, pythonify=True) + attributes = self.user_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_context=True, pythonify=True) # type: ignore[assignment] self.assertTrue(isinstance(attributes[0].Event, MISPEvent)) self.assertEqual(attributes[0].Event.uuid, second.uuid) # Include context - search as admin (can see both event) - attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_context=True, pythonify=True) + attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_context=True, pythonify=True) # type: ignore[assignment] self.assertTrue(isinstance(attributes[0].Event, MISPEvent)) self.assertEqual(attributes[0].Event.uuid, first.uuid) self.assertEqual(attributes[1].Event.uuid, second.uuid) # Include correlations - search as admin (can see both event) - attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_correlations=True, pythonify=True) + attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_correlations=True, pythonify=True) # type: ignore[assignment] self.assertTrue(isinstance(attributes[0].Event, MISPEvent)) self.assertEqual(attributes[0].Event.uuid, first.uuid) self.assertEqual(attributes[1].Event.uuid, second.uuid) @@ -270,8 +277,9 @@ class TestComprehensive(unittest.TestCase): self.assertEqual(attributes[1].RelatedAttribute[0].Event.uuid, first.uuid) # Include sightings - search as admin (can see both event) - self.admin_misp_connector.add_sighting({'value': first.attributes[0].value}) - attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_sightings=True, pythonify=True) + s: dict[str, Any] = {'value': first.attributes[0].value} + self.admin_misp_connector.add_sighting(s) + attributes = self.admin_misp_connector.search(controller='attributes', value=first.attributes[0].value, include_sightings=True, pythonify=True) # type: ignore[assignment] self.assertTrue(isinstance(attributes[0].Event, MISPEvent)) self.assertEqual(attributes[0].Event.uuid, first.uuid) self.assertEqual(attributes[1].Event.uuid, second.uuid) @@ -283,17 +291,21 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_type_event(self): + def test_search_type_event(self) -> None: '''Search multiple events, search events containing attributes with specific types''' try: first, second, third = self.environment() # Search as admin - events = self.admin_misp_connector.search(timestamp=first.timestamp.timestamp(), pythonify=True) + if isinstance(first.timestamp, datetime): + ts = first.timestamp.timestamp() + else: + ts = first.timestamp + events: list[MISPEvent] = self.admin_misp_connector.search(timestamp=ts, pythonify=True) # type: ignore[assignment] self.assertEqual(len(events), 3) for e in events: self.assertIn(e.id, [first.id, second.id, third.id]) attributes_types_search = self.admin_misp_connector.build_complex_query(or_parameters=['ip-src', 'ip-dst']) - events = self.admin_misp_connector.search(timestamp=first.timestamp.timestamp(), + events = self.admin_misp_connector.search(timestamp=ts, # type: ignore[assignment,type-var] type_attribute=attributes_types_search, pythonify=True) self.assertEqual(len(events), 2) for e in events: @@ -304,28 +316,32 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_index(self): + def test_search_index(self) -> None: try: first, second, third = self.environment() # Search as admin - events = self.admin_misp_connector.search_index(timestamp=first.timestamp.timestamp(), pythonify=True) + if isinstance(first.timestamp, datetime): + ts = first.timestamp.timestamp() + else: + ts = first.timestamp + events: MISPEvent = self.admin_misp_connector.search_index(timestamp=ts, pythonify=True) # type: ignore[assignment] self.assertEqual(len(events), 3) for e in events: self.assertIn(e.id, [first.id, second.id, third.id]) # Test limit and pagination - event_one = self.admin_misp_connector.search_index(timestamp=first.timestamp.timestamp(), limit=1, page=1, pythonify=True)[0] - event_two = self.admin_misp_connector.search_index(timestamp=first.timestamp.timestamp(), limit=1, page=2, pythonify=True)[0] + event_one: MISPEvent = self.admin_misp_connector.search_index(timestamp=ts, limit=1, page=1, pythonify=True)[0] # type: ignore[index,assignment] + event_two: MISPEvent = self.admin_misp_connector.search_index(timestamp=ts, limit=1, page=2, pythonify=True)[0] # type: ignore[index,assignment] self.assertTrue(event_one.id != event_two.id) two_events = self.admin_misp_connector.search_index(limit=2) self.assertTrue(len(two_events), 2) # Test ordering by the Info field. Can't use timestamp as each will likely have the same - event = self.admin_misp_connector.search_index(timestamp=first.timestamp.timestamp(), sort="info", desc=True, limit=1, pythonify=True)[0] + event: MISPEvent = self.admin_misp_connector.search_index(timestamp=ts, sort="info", desc=True, limit=1, pythonify=True)[0] # type: ignore[index,assignment] # First|Second|*Third* event self.assertEqual(event.id, third.id) # *First*|Second|Third event - event = self.admin_misp_connector.search_index(timestamp=first.timestamp.timestamp(), sort="info", desc=False, limit=1, pythonify=True)[0] + event = self.admin_misp_connector.search_index(timestamp=ts, sort="info", desc=False, limit=1, pythonify=True)[0] # type: ignore[index,assignment] self.assertEqual(event.id, first.id) finally: # Delete event @@ -333,7 +349,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_objects(self): + def test_search_objects(self) -> None: '''Search for objects''' try: first = self.create_simple_event() @@ -351,7 +367,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_search_type_attribute(self): + def test_search_type_attribute(self) -> None: '''Search multiple attributes, search attributes with specific types''' try: first, second, third = self.environment() @@ -375,7 +391,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_tag_event(self): + def test_search_tag_event(self) -> None: '''Search Tags at events level''' try: first, second, third = self.environment() @@ -409,7 +425,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_tag_attribute(self): + def test_search_tag_attribute(self) -> None: '''Search Tags at attributes level''' try: first, second, third = self.environment() @@ -436,7 +452,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_tag_advanced_event(self): + def test_search_tag_advanced_event(self) -> None: '''Advanced search Tags at events level''' try: first, second, third = self.environment() @@ -466,7 +482,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_tag_advanced_attributes(self): + def test_search_tag_advanced_attributes(self) -> None: '''Advanced search Tags at attributes level''' try: first, second, third = self.environment() @@ -485,7 +501,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_search_timestamp_event(self): + def test_search_timestamp_event(self) -> None: '''Search specific update timestamps at events level''' # Creating event 1 - timestamp 5 min ago first = self.create_simple_event(force_timestamps=True) @@ -521,7 +537,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_search_timestamp_attribute(self): + def test_search_timestamp_attribute(self) -> None: '''Search specific update timestamps at attributes level''' # Creating event 1 - timestamp 5 min ago first = self.create_simple_event(force_timestamps=True) @@ -559,7 +575,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_user_perms(self): + def test_user_perms(self) -> None: '''Test publish rights''' try: first = self.create_simple_event() @@ -575,7 +591,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_delete_with_update(self): + def test_delete_with_update(self) -> None: try: first = self.create_simple_event() obj = MISPObject('file') @@ -600,14 +616,14 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_get_non_exists_event(self): + def test_get_non_exists_event(self) -> None: event = self.user_misp_connector.get_event(0) # non exists id self.assertEqual(event['errors'][0], 404) event = self.user_misp_connector.get_event("ab2b6e28-fda5-4282-bf60-22b81de77851") # non exists uuid self.assertEqual(event['errors'][0], 404) - def test_delete_by_uuid(self): + def test_delete_by_uuid(self) -> None: try: first = self.create_simple_event() obj = MISPObject('file') @@ -644,7 +660,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_search_publish_timestamp(self): + def test_search_publish_timestamp(self) -> None: '''Search for a specific publication timestamp, an interval, and invalid values.''' # Creating event 1 first = self.create_simple_event() @@ -683,7 +699,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_search_decay(self): + def test_search_decay(self) -> None: # Creating event 1 first = self.create_simple_event() first.add_attribute('ip-dst', '8.8.8.8') @@ -708,7 +724,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_default_distribution(self): + def test_default_distribution(self) -> None: '''The default distributions on the VM are This community only for the events and Inherit from event for attr/obj)''' first = self.create_simple_event() del first.distribution @@ -750,7 +766,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_exists(self): + def test_exists(self) -> None: """Check event, attribute and object existence""" event = self.create_simple_event() misp_object = MISPObject('domain-ip') @@ -787,7 +803,7 @@ class TestComprehensive(unittest.TestCase): self.assertFalse(self.user_misp_connector.object_exists(misp_object)) self.assertFalse(self.user_misp_connector.object_exists(misp_object.id)) - def test_simple_event(self): + def test_simple_event(self) -> None: '''Search a bunch of parameters: * Value not existing * only return metadata @@ -1006,7 +1022,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_event_add_update_metadata(self): + def test_event_add_update_metadata(self) -> None: event = self.create_simple_event() event.add_attribute('ip-src', '9.9.9.9') try: @@ -1020,7 +1036,7 @@ class TestComprehensive(unittest.TestCase): finally: # cleanup self.admin_misp_connector.delete_event(event) - def test_extend_event(self): + def test_extend_event(self) -> None: first = self.create_simple_event() first.info = 'parent event' first.add_tag('tlp:amber___test') @@ -1041,7 +1057,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_edit_attribute(self): + def test_edit_attribute(self) -> None: first = self.create_simple_event() try: first.attributes[0].comment = 'This is the original comment' @@ -1061,7 +1077,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_sightings(self): + def test_sightings(self) -> None: first = self.create_simple_event() second = self.create_simple_event() try: @@ -1136,7 +1152,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_search_csv(self): + def test_search_csv(self) -> None: first = self.create_simple_event() first.attributes[0].comment = 'This is the original comment' second = self.create_simple_event() @@ -1216,7 +1232,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_search_text(self): + def test_search_text(self) -> None: first = self.create_simple_event() first.add_attribute('ip-src', '8.8.8.8') first.publish() @@ -1230,7 +1246,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_search_stix(self): + def test_search_stix(self) -> None: first = self.create_simple_event() first.add_attribute('ip-src', '8.8.8.8') try: @@ -1245,7 +1261,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_update_object(self): + def test_update_object(self) -> None: first = self.create_simple_event() ip_dom = MISPObject('domain-ip') ip_dom.add_attribute('domain', value='google.fr') @@ -1349,7 +1365,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_custom_template(self): + def test_custom_template(self) -> None: first = self.create_simple_event() try: with open('tests/viper-test-files/test_files/whoami.exe', 'rb') as f: @@ -1391,7 +1407,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_unknown_template(self): + def test_unknown_template(self) -> None: first = self.create_simple_event() attributeAsDict = [{'MyCoolAttribute': {'value': 'critical thing', 'type': 'text'}}, {'MyCoolerAttribute': {'value': 'even worse', 'type': 'text', 'disable_correlation': True}}] @@ -1425,7 +1441,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_domain_ip_object(self): + def test_domain_ip_object(self) -> None: first = self.create_simple_event() try: dom_ip_obj = DomainIPObject({'ip': ['1.1.1.1', {'value': '2.2.2.2', 'to_ids': False}], @@ -1439,7 +1455,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_asn_object(self): + def test_asn_object(self) -> None: first = self.create_simple_event() try: dom_ip_obj = ASNObject({'asn': '12345', @@ -1452,7 +1468,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_object_template(self): + def test_object_template(self) -> None: r = self.admin_misp_connector.update_object_templates() self.assertEqual(type(r), list) object_templates = self.admin_misp_connector.object_templates(pythonify=True) @@ -1471,7 +1487,7 @@ class TestComprehensive(unittest.TestCase): mo.add_attribute('domain', 'google.fr') self.assertEqual(mo.template_uuid, '4') - def test_tags(self): + def test_tags(self) -> None: # Get list tags = self.admin_misp_connector.tags(pythonify=True) self.assertTrue(isinstance(tags, list)) @@ -1560,7 +1576,7 @@ class TestComprehensive(unittest.TestCase): response = self.admin_misp_connector.delete_tag(tag_org_restricted) response = self.admin_misp_connector.delete_tag(tag_user_restricted) - def test_add_event_with_attachment_object_controller(self): + def test_add_event_with_attachment_object_controller(self) -> None: first = self.create_simple_event() try: first = self.user_misp_connector.add_event(first) @@ -1600,7 +1616,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_add_event_with_attachment_object_controller__hard(self): + def test_add_event_with_attachment_object_controller__hard(self) -> None: first = self.create_simple_event() try: first = self.user_misp_connector.add_event(first) @@ -1641,7 +1657,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_lief_and_sign(self): + def test_lief_and_sign(self) -> None: first = self.create_simple_event() try: first = self.user_misp_connector.add_event(first) @@ -1685,10 +1701,11 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_add_event_with_attachment(self): - first = self.create_simple_event() + def test_add_event_with_attachment(self) -> None: + first_send = self.create_simple_event() try: - first = self.user_misp_connector.add_event(first) + first = self.user_misp_connector.add_event(first_send) + self.assertTrue(isinstance(first, MISPEvent), first) file_obj, bin_obj, sections = make_binary_objects('tests/viper-test-files/test_files/whoami.exe', standalone=False) first.add_object(file_obj) first.add_object(bin_obj) @@ -1703,7 +1720,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_taxonomies(self): + def test_taxonomies(self) -> None: # Make sure we're up-to-date r = self.admin_misp_connector.update_taxonomies() self.assertEqual(r['name'], 'All taxonomy libraries are up to date already.') @@ -1743,7 +1760,7 @@ class TestComprehensive(unittest.TestCase): # Return back to default required status r = self.admin_misp_connector.set_taxonomy_required(tax, not tax.required) - def test_warninglists(self): + def test_warninglists(self) -> None: # Make sure we're up-to-date r = self.admin_misp_connector.update_warninglists() self.assertTrue('name' in r, msg=r) @@ -1772,7 +1789,7 @@ class TestComprehensive(unittest.TestCase): r = self.admin_misp_connector.disable_warninglist(testwl) self.assertEqual(r['success'], '1 warninglist(s) disabled') - def test_noticelists(self): + def test_noticelists(self) -> None: # Make sure we're up-to-date r = self.admin_misp_connector.update_noticelists() self.assertEqual(r['name'], 'All noticelists are up to date already.') @@ -1793,7 +1810,7 @@ class TestComprehensive(unittest.TestCase): r = self.admin_misp_connector.disable_noticelist(testnl) self.assertFalse(r['Noticelist']['enabled'], r) - def test_correlation_exclusions(self): + def test_correlation_exclusions(self) -> None: newce = MISPCorrelationExclusion() newce.value = "test-correlation-exclusion" r = self.admin_misp_connector.add_correlation_exclusion(newce, pythonify=True) @@ -1808,7 +1825,7 @@ class TestComprehensive(unittest.TestCase): r = self.admin_misp_connector.clean_correlation_exclusions() self.assertTrue(r['success']) - def test_galaxies(self): + def test_galaxies(self) -> None: # Make sure we're up-to-date r = self.admin_misp_connector.update_galaxies() self.assertEqual(r['name'], 'Galaxies updated.') @@ -1824,7 +1841,7 @@ class TestComprehensive(unittest.TestCase): # FIXME: Fails due to https://github.com/MISP/MISP/issues/4855 # self.assertTrue('GalaxyCluster' in r) - def test_zmq(self): + def test_zmq(self) -> None: first = self.create_simple_event() try: first = self.user_misp_connector.add_event(first) @@ -1834,7 +1851,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_csv_loader(self): + def test_csv_loader(self) -> None: csv1 = CSVLoader(template_name='file', csv_path=Path('tests/csv_testfiles/valid_fieldnames.csv')) event = MISPEvent() event.info = 'Test event from CSV loader' @@ -1852,7 +1869,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_user(self): + def test_user(self) -> None: # Get list users = self.admin_misp_connector.users(pythonify=True) self.assertTrue(isinstance(users, list)) @@ -1874,7 +1891,7 @@ class TestComprehensive(unittest.TestCase): key = self.user_misp_connector.get_new_authkey() self.assertTrue(isinstance(key, str)) - def test_organisation(self): + def test_organisation(self) -> None: # Get list orgs = self.admin_misp_connector.organisations(pythonify=True) self.assertTrue(isinstance(orgs, list)) @@ -1891,7 +1908,7 @@ class TestComprehensive(unittest.TestCase): organisation = self.admin_misp_connector.update_organisation(organisation, pythonify=True) self.assertEqual(organisation.name, 'blah', organisation) - def test_org_search(self): + def test_org_search(self) -> None: orgs = self.admin_misp_connector.organisations(pythonify=True) org_name = 'ORGNAME' # Search by the org name @@ -1902,7 +1919,7 @@ class TestComprehensive(unittest.TestCase): # This org should have the name ORGNAME self.assertEqual(orgs[0].name, org_name) - def test_user_search(self): + def test_user_search(self) -> None: users = self.admin_misp_connector.users(pythonify=True) emailAddr = users[0].email @@ -1918,7 +1935,7 @@ class TestComprehensive(unittest.TestCase): self.assertTrue(len(users) == 1) self.assertEqual(users[0].email, emailAddr) - def test_attribute(self): + def test_attribute(self) -> None: first = self.create_simple_event() second = self.create_simple_event() a = second.add_attribute('ip-src', '11.11.11.11') @@ -2087,7 +2104,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_search_type_event_csv(self): + def test_search_type_event_csv(self) -> None: try: first, second, third = self.environment() # Search as admin @@ -2106,7 +2123,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(third) @unittest.skip("Not very important, skip for now.") - def test_search_logs(self): + def test_search_logs(self) -> None: r = self.admin_misp_connector.update_user({'email': 'testusr-changed@user.local'}, self.test_usr) r = self.admin_misp_connector.search_logs(model='User', created=date.today(), pythonify=True) for entry in r[-1:]: @@ -2124,22 +2141,22 @@ class TestComprehensive(unittest.TestCase): else: raise Exception('Unable to find log entry after updating the user') - def test_db_schema(self): + def test_db_schema(self) -> None: diag = self.admin_misp_connector.db_schema_diagnostic() self.assertEqual(diag['actual_db_version'], diag['expected_db_version'], diag) - def test_live_acl(self): + def test_live_acl(self) -> None: missing_acls = self.admin_misp_connector.remote_acl() self.assertEqual(missing_acls, [], msg=missing_acls) - def test_roles(self): + def test_roles(self) -> None: role = self.admin_misp_connector.set_default_role(4) self.assertEqual(role['message'], 'Default role set.') self.admin_misp_connector.set_default_role(3) roles = self.admin_misp_connector.roles(pythonify=True) self.assertTrue(isinstance(roles, list)) - def test_describe_types(self): + def test_describe_types(self) -> None: remote = self.admin_misp_connector.describe_types_remote remote_types = remote.pop('types') remote_categories = remote.pop('categories') @@ -2158,12 +2175,12 @@ class TestComprehensive(unittest.TestCase): for typ in mapping: self.assertIn(typ, remote_types) - def test_versions(self): + def test_versions(self) -> None: self.assertEqual(self.user_misp_connector.version, self.user_misp_connector.pymisp_version_master) self.assertEqual(self.user_misp_connector.misp_instance_version['version'], self.user_misp_connector.misp_instance_version_master['version']) - def test_statistics(self): + def test_statistics(self) -> None: try: # Attributes first, second, third = self.environment() @@ -2212,7 +2229,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(second) self.admin_misp_connector.delete_event(third) - def test_direct(self): + def test_direct(self) -> None: try: r = self.user_misp_connector.direct_call('events/add', data={'info': 'foo'}) event = MISPEvent() @@ -2229,7 +2246,7 @@ class TestComprehensive(unittest.TestCase): finally: self.admin_misp_connector.delete_event(event) - def test_freetext(self): + def test_freetext(self) -> None: first = self.create_simple_event() try: self.admin_misp_connector.toggle_warninglist(warninglist_name='%dns resolv%', force_enable=True) @@ -2265,7 +2282,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_sharing_groups(self): + def test_sharing_groups(self) -> None: # add sg = MISPSharingGroup() sg.name = 'Testcases SG' @@ -2341,7 +2358,7 @@ class TestComprehensive(unittest.TestCase): self.assertFalse(self.admin_misp_connector.sharing_group_exists(sharing_group.id)) self.assertFalse(self.admin_misp_connector.sharing_group_exists(sharing_group.uuid)) - def test_sharing_group(self): + def test_sharing_group(self) -> None: # add sg = MISPSharingGroup() sg.name = 'Testcases SG' @@ -2366,7 +2383,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_sharing_group(sharing_group.id) self.assertFalse(self.admin_misp_connector.sharing_group_exists(sharing_group)) - def test_sharing_group_search(self): + def test_sharing_group_search(self) -> None: # Add sharing group sg = MISPSharingGroup() sg.name = 'Testcases SG' @@ -2413,7 +2430,7 @@ class TestComprehensive(unittest.TestCase): self.assertFalse(self.admin_misp_connector.sharing_group_exists(sharing_group)) - def test_feeds(self): + def test_feeds(self) -> None: # Add feed = MISPFeed() feed.name = 'TestFeed' @@ -2496,7 +2513,7 @@ class TestComprehensive(unittest.TestCase): self.assertFalse(updated_feed.enabled) self.assertEqual(updated_feed.settings, e_thread_csv_feed.settings) - def test_servers(self): + def test_servers(self) -> None: # add server = MISPServer() server.name = 'Test Server' @@ -2516,7 +2533,7 @@ class TestComprehensive(unittest.TestCase): r = self.admin_misp_connector.delete_server(server) self.assertEqual(r['name'], 'Server deleted') - def test_roles_expanded(self): + def test_roles_expanded(self) -> None: '''Test all possible things regarding roles 1. Use existing roles (ID in test VM): * Read only (6): Can only connect via API and see events visible by its organisation @@ -2634,7 +2651,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_user(test_roles_user) self.admin_misp_connector.delete_tag(test_tag) - def test_expansion(self): + def test_expansion(self) -> None: first = self.create_simple_event() try: md5_disk = hashlib.md5() @@ -2671,7 +2688,7 @@ class TestComprehensive(unittest.TestCase): # Delete event self.admin_misp_connector.delete_event(first) - def test_user_settings(self): + def test_user_settings(self) -> None: first = self.create_simple_event() first.distribution = 3 first.add_tag('test_publish_filter') @@ -2732,7 +2749,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_communities(self): + def test_communities(self) -> None: communities = self.admin_misp_connector.communities(pythonify=True) self.assertEqual(communities[0].name, 'CIRCL Private Sector Information Sharing Community - aka MISPPRIV') community = self.admin_misp_connector.get_community(communities[1], pythonify=True) @@ -2746,7 +2763,7 @@ class TestComprehensive(unittest.TestCase): # if k == 'To': # self.assertEqual(v, 'info@circl.lu') - def test_upload_stix(self): + def test_upload_stix(self) -> None: # FIXME https://github.com/MISP/MISP/issues/4892 try: r1 = self.user_misp_connector.upload_stix('tests/stix1.xml-utf8', version='1') @@ -2777,7 +2794,7 @@ class TestComprehensive(unittest.TestCase): except Exception: pass - def test_toggle_global_pythonify(self): + def test_toggle_global_pythonify(self) -> None: first = self.create_simple_event() second = self.create_simple_event() try: @@ -2792,7 +2809,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_event(second) - def test_first_last_seen(self): + def test_first_last_seen(self) -> None: event = MISPEvent() event.info = 'Test First Last seen' event.add_attribute('ip-dst', '8.8.8.8', first_seen='2020-01-03', last_seen='2020-01-04T12:30:34.323242+0800') @@ -2839,7 +2856,7 @@ class TestComprehensive(unittest.TestCase): finally: self.admin_misp_connector.delete_event(first) - def test_registrations(self): + def test_registrations(self) -> None: r = register_user(url, 'self_register@user.local', organisation=self.test_org, org_name=self.test_org.name, verify=verifycert) self.assertTrue(r['saved']) @@ -2869,7 +2886,7 @@ class TestComprehensive(unittest.TestCase): m = self.admin_misp_connector.discard_user_registration(registrations[1].id) self.assertEqual(m['name'], '1 registration(s) discarded.') - def test_search_workflow(self): + def test_search_workflow(self) -> None: first = self.create_simple_event() first.add_attribute('domain', 'google.com') tag = MISPTag() @@ -2918,7 +2935,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_tag(tag) - def test_search_workflow_ts(self): + def test_search_workflow_ts(self) -> None: first = self.create_simple_event() first.add_attribute('domain', 'google.com') tag = MISPTag() @@ -2967,14 +2984,14 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.delete_event(first) self.admin_misp_connector.delete_tag(tag) - def test_blocklists(self): + def test_blocklists(self) -> None: first = self.create_simple_event() second = self.create_simple_event() second.Orgc = self.test_org - to_delete = {'bl_events': [], 'bl_organisations': []} + to_delete: dict[str, MISPOrganisationBlocklist | MISPEventBlocklist] = {'bl_events': [], 'bl_organisations': []} try: # test events BL - ebl = self.admin_misp_connector.add_event_blocklist(uuids=[first.uuid]) + ebl: MISPEventBlocklist = self.admin_misp_connector.add_event_blocklist(uuids=[first.uuid]) self.assertEqual(ebl['result']['successes'][0], first.uuid, ebl) bl_events = self.admin_misp_connector.event_blocklists(pythonify=True) for ble in bl_events: @@ -3008,7 +3025,7 @@ class TestComprehensive(unittest.TestCase): blo.comment = 'This is a test' blo.org_name = 'bar' - blo = self.admin_misp_connector.update_organisation_blocklist(blo, pythonify=True) + blo: MISPOrganisationBlocklist = self.admin_misp_connector.update_organisation_blocklist(blo, pythonify=True) self.assertEqual(blo.org_name, 'bar') r = self.admin_misp_connector.delete_organisation_blocklist(blo) self.assertTrue(r['success']) @@ -3019,15 +3036,15 @@ class TestComprehensive(unittest.TestCase): for blo in to_delete['bl_organisations']: self.admin_misp_connector.delete_organisation_blocklist(blo) - def test_event_report(self): + def test_event_report(self) -> None: event = self.create_simple_event() - new_event_report = MISPEventReport() + new_event_report: MISPEventReport = MISPEventReport() new_event_report.name = "Test Event Report" new_event_report.content = "# Example report markdown" new_event_report.distribution = 5 # Inherit try: event = self.user_misp_connector.add_event(event) - new_event_report = self.user_misp_connector.add_event_report(event.id, new_event_report) + new_event_report = self.user_misp_connector.add_event_report(event.id, new_event_report) # type: ignore[assignment] # The event report should be linked by Event ID self.assertEqual(event.id, new_event_report.event_id) @@ -3037,12 +3054,12 @@ class TestComprehensive(unittest.TestCase): new_event_report.name = "Updated Event Report" new_event_report.content = "Updated content" - new_event_report = self.user_misp_connector.update_event_report(new_event_report) + new_event_report = self.user_misp_connector.update_event_report(new_event_report) # type: ignore[assignment] # The event report should be updatable self.assertTrue(new_event_report.name == "Updated Event Report") self.assertTrue(new_event_report.content == "Updated content") - event_reports = self.user_misp_connector.get_event_reports(event.id) + event_reports: list[MISPEventReport] = self.user_misp_connector.get_event_reports(event.id) # type: ignore[assignment] # The event report should be requestable by the Event ID self.assertEqual(new_event_report.id, event_reports[0].id) @@ -3057,33 +3074,41 @@ class TestComprehensive(unittest.TestCase): self.user_misp_connector.delete_event(event) self.user_misp_connector.delete_event_report(new_event_report) - def test_search_galaxy(self): + def test_search_galaxy(self) -> None: self.admin_misp_connector.toggle_global_pythonify() - galaxy = self.admin_misp_connector.galaxies()[0] + galaxies: list[MISPGalaxy] = self.admin_misp_connector.galaxies() # type: ignore[assignment] + galaxy: MISPGalaxy = galaxies[0] ret = self.admin_misp_connector.search_galaxy(value=galaxy.name) self.assertEqual(len(ret), 1) self.admin_misp_connector.toggle_global_pythonify() - def test_galaxy_cluster(self): + def test_galaxy_cluster(self) -> None: self.admin_misp_connector.toggle_global_pythonify() - galaxy = self.admin_misp_connector.galaxies()[0] - new_galaxy_cluster = MISPGalaxyCluster() + galaxies: list[MISPGalaxy] = self.admin_misp_connector.galaxies() # type: ignore[assignment] + galaxy: MISPGalaxy = galaxies[0] + new_galaxy_cluster: MISPGalaxyCluster = MISPGalaxyCluster() new_galaxy_cluster.value = "Test Cluster" new_galaxy_cluster.authors = ["MISP"] new_galaxy_cluster.distribution = 1 new_galaxy_cluster.description = "Example test cluster" try: - galaxy = self.admin_misp_connector.get_galaxy(galaxy.id, withCluster=True) + if gid := galaxy.id: + galaxy = self.admin_misp_connector.get_galaxy(gid, withCluster=True) # type: ignore[assignment] + else: + raise Exception("No galaxy found") existing_galaxy_cluster = galaxy.clusters[0] - new_galaxy_cluster = self.admin_misp_connector.add_galaxy_cluster(galaxy.id, new_galaxy_cluster) + if gid := galaxy.id: + new_galaxy_cluster = self.admin_misp_connector.add_galaxy_cluster(gid, new_galaxy_cluster) # type: ignore[assignment] + else: + raise Exception("No galaxy found") # The new galaxy cluster should be under the selected galaxy self.assertEqual(galaxy.id, new_galaxy_cluster.galaxy_id) # The cluster should have the right value self.assertEqual(new_galaxy_cluster.value, "Test Cluster") new_galaxy_cluster.add_cluster_element("synonyms", "Test2") - new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] # The cluster should have one element that is a synonym self.assertEqual(len(new_galaxy_cluster.cluster_elements), 1) @@ -3096,22 +3121,22 @@ class TestComprehensive(unittest.TestCase): # The cluster element should be updatable element.value = "Test3" - new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] element = new_galaxy_cluster.cluster_elements[0] self.assertEqual(element.value, "Test3") new_galaxy_cluster.add_cluster_element("synonyms", "ToDelete") - new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] # The cluster should have two elements self.assertEqual(len(new_galaxy_cluster.cluster_elements), 2) new_galaxy_cluster.cluster_elements = [e for e in new_galaxy_cluster.cluster_elements if e.value != "ToDelete"] - new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] # The cluster elements should be deletable self.assertEqual(len(new_galaxy_cluster.cluster_elements), 1) new_galaxy_cluster.add_cluster_relation(existing_galaxy_cluster, "is-tested-by") - new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] # The cluster should have a relationship self.assertEqual(len(new_galaxy_cluster.cluster_relations), 1) relation = new_galaxy_cluster.cluster_relations[0] @@ -3119,7 +3144,7 @@ class TestComprehensive(unittest.TestCase): self.assertEqual(relation.referenced_galaxy_cluster_uuid, existing_galaxy_cluster.uuid) relation.add_tag("tlp:amber") - new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.update_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] relation = new_galaxy_cluster.cluster_relations[0] # The relationship should have a tag of tlp:amber self.assertEqual(len(relation.tags), 1) @@ -3129,32 +3154,36 @@ class TestComprehensive(unittest.TestCase): resp = self.admin_misp_connector.delete_galaxy_cluster_relation(relation) self.assertTrue(resp['success']) # The cluster relation should no longer be present - new_galaxy_cluster = self.admin_misp_connector.get_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.get_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] self.assertEqual(len(new_galaxy_cluster.cluster_relations), 0) resp = self.admin_misp_connector.delete_galaxy_cluster(new_galaxy_cluster) # Galaxy clusters should be soft deletable self.assertTrue(resp['success']) - new_galaxy_cluster = self.admin_misp_connector.get_galaxy_cluster(new_galaxy_cluster) + new_galaxy_cluster = self.admin_misp_connector.get_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] self.assertTrue(isinstance(new_galaxy_cluster, MISPGalaxyCluster)) resp = self.admin_misp_connector.delete_galaxy_cluster(new_galaxy_cluster, hard=True) # Galaxy clusters should be hard deletable self.assertTrue(resp['success']) - resp = self.admin_misp_connector.get_galaxy_cluster(new_galaxy_cluster) + resp = self.admin_misp_connector.get_galaxy_cluster(new_galaxy_cluster) # type: ignore[assignment] self.assertTrue("errors" in resp) finally: self.admin_misp_connector.delete_galaxy_cluster_relation(relation) self.admin_misp_connector.delete_galaxy_cluster(new_galaxy_cluster, hard=True) self.admin_misp_connector.toggle_global_pythonify() - def test_event_galaxy(self): + def test_event_galaxy(self) -> None: self.admin_misp_connector.toggle_global_pythonify() event = self.create_simple_event() try: - galaxy = self.admin_misp_connector.galaxies()[0] - galaxy = self.admin_misp_connector.get_galaxy(galaxy.id, withCluster=True) - galaxy_cluster = galaxy.clusters[0] + galaxies: list[MISPGalaxy] = self.admin_misp_connector.galaxies() # type: ignore[assignment] + galaxy: MISPGalaxy = galaxies[0] + if gid := galaxy.id: + galaxy = self.admin_misp_connector.get_galaxy(gid, withCluster=True) # type: ignore[assignment] + else: + raise Exception("No galaxy found") + galaxy_cluster: MISPGalaxyCluster = galaxy.clusters[0] event.add_tag(galaxy_cluster.tag_name) event = self.admin_misp_connector.add_event(event) # The event should have a galaxy attached @@ -3169,7 +3198,7 @@ class TestComprehensive(unittest.TestCase): self.admin_misp_connector.toggle_global_pythonify() @unittest.skip("Internal use only") - def missing_methods(self): + def missing_methods(self) -> None: skip = [ "attributes/download", "attributes/add_attachment", diff --git a/tests/testlive_sync.py b/tests/testlive_sync.py index 24971c4..8d0e820 100644 --- a/tests/testlive_sync.py +++ b/tests/testlive_sync.py @@ -1,23 +1,19 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- + +from __future__ import annotations import time -import sys import unittest import subprocess -import urllib3 # type: ignore +import urllib3 import logging logging.disable(logging.CRITICAL) try: - from pymisp import ExpandedPyMISP, MISPOrganisation, MISPUser, MISPEvent, MISPObject, MISPSharingGroup, Distribution + from pymisp import PyMISP, MISPOrganisation, MISPUser, MISPEvent, MISPObject, MISPSharingGroup, Distribution except ImportError: - if sys.version_info < (3, 6): - print('This test suite requires Python 3.6+, breaking.') - sys.exit(0) - else: - raise + raise key = 'eYQdGTEWZJ8C2lm9EpnMqxQGwGiPNyoR75JvLdlE' verifycert = False @@ -73,7 +69,7 @@ fast_mode = True class MISPInstance(): def __init__(self, params): - self.initial_user_connector = ExpandedPyMISP(params['url'], params['key'], ssl=False, debug=False) + self.initial_user_connector = PyMISP(params['url'], params['key'], ssl=False, debug=False) # Git pull self.initial_user_connector.update_misp() # Set the default role (id 3 on the VM is normal user) @@ -101,7 +97,7 @@ class MISPInstance(): user.org_id = self.test_org.id user.role_id = 1 # Site admin self.test_site_admin = self.initial_user_connector.add_user(user) - self.site_admin_connector = ExpandedPyMISP(params['url'], self.test_site_admin.authkey, ssl=False, debug=False) + self.site_admin_connector = PyMISP(params['url'], self.test_site_admin.authkey, ssl=False, debug=False) self.site_admin_connector.toggle_global_pythonify() # Create org admin user = MISPUser() @@ -109,14 +105,14 @@ class MISPInstance(): user.org_id = self.test_org.id user.role_id = 2 # Org admin self.test_org_admin = self.site_admin_connector.add_user(user) - self.org_admin_connector = ExpandedPyMISP(params['url'], self.test_org_admin.authkey, ssl=False, debug=False) + self.org_admin_connector = PyMISP(params['url'], self.test_org_admin.authkey, ssl=False, debug=False) self.org_admin_connector.toggle_global_pythonify() # Create user user = MISPUser() user.email = params['email_user'] user.org_id = self.test_org.id self.test_usr = self.org_admin_connector.add_user(user) - self.user_connector = ExpandedPyMISP(params['url'], self.test_usr.authkey, ssl=False, debug=False) + self.user_connector = PyMISP(params['url'], self.test_usr.authkey, ssl=False, debug=False) self.user_connector.toggle_global_pythonify() # Setup external_baseurl @@ -141,7 +137,7 @@ class MISPInstance(): user.org_id = sync_org.id user.role_id = 5 # Org admin sync_user = self.site_admin_connector.add_user(user) - sync_user_connector = ExpandedPyMISP(self.site_admin_connector.root_url, sync_user.authkey, ssl=False, debug=False) + sync_user_connector = PyMISP(self.site_admin_connector.root_url, sync_user.authkey, ssl=False, debug=False) sync_server_config = sync_user_connector.get_sync_config(pythonify=True) self.sync.append((sync_org, sync_user, sync_server_config))