diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 848f4fb..0000000 --- a/.flake8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = E203, W503 -exclude = docs diff --git a/.gitignore b/.gitignore index 82f9275..aaa549e 100644 --- a/.gitignore +++ b/.gitignore @@ -151,6 +151,15 @@ dmypy.json # pytype static type analyzer .pytype/ +# local files +run.py +*/run.py +sage.py +*/sage.py + +# ruff +.ruff_cache/ + # Cython debug symbols cython_debug/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d170554..2728978 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,30 +3,51 @@ repos: rev: v4.3.0 hooks: - id: trailing-whitespace + exclude: (migrations/|tests/|docs/|static/|media/).* - id: end-of-file-fixer + exclude: (migrations/|tests/|docs/|static/|media/).* - id: check-added-large-files + exclude: (migrations/|tests/|docs/|static/|media/).* - id: check-case-conflict + exclude: (migrations/|tests/|docs/|static/|media/).* - id: check-merge-conflict + exclude: (migrations/|tests/|docs/|static/|media/).* - id: check-docstring-first - - - repo: https://github.com/psf/black - rev: 23.3.0 - hooks: - - id: black - exclude: docs/ + exclude: (migrations/|tests/|docs/|static/|media/).* - repo: https://github.com/pre-commit/mirrors-isort rev: v5.10.1 hooks: - id: isort - exclude: docs/ + exclude: (migrations/|tests/|docs/|static/|media/).* + + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + args: ["--config=pyproject.toml"] + exclude: (migrations/|tests/|docs/|static/|media/).* - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.991 hooks: - id: mypy - args: ["--config-file=mypy.ini"] - exclude: ^(docs/|stubs/|tests/) + args: ["--config-file=pyproject.toml"] + exclude: (migrations/|tests/|docs/|static/|media/).* + + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.28.0 + hooks: + - id: commitizen + exclude: (migrations/|tests/|docs/|static/|media/).* + + - repo: https://github.com/PyCQA/bandit + rev: 1.7.4 + hooks: + - id: bandit + args: ["-c", "pyproject.toml", "-r", "."] + additional_dependencies: [ "bandit[toml]" ] + exclude: (migrations/|tests/|docs/|static/|media/).* - repo: https://github.com/codespell-project/codespell # Configuration for codespell is in .codespellrc @@ -34,3 +55,29 @@ repos: hooks: - id: codespell exclude: locale|kickstarter-announcement.md|coreapi-0.1.1.js + + - repo: local + hooks: + - id: pytest + name: Pytest + entry: poetry run pytest -v + language: system + types: [python] + stages: [commit] + pass_filenames: false + always_run: true + + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + require_serial: true + args: + - "-rn" + - "-sn" + - "--rcfile=pyproject.toml" + files: ^sage_sms/ + +ci: + skip: [pylint] diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index e0fbb70..0000000 --- a/.pylintrc +++ /dev/null @@ -1,13 +0,0 @@ -[MASTER] -ignore=tests,docs,build,stubs -persistent=yes -ignore-patterns=^tests/.*, ^docs/.*, ^build/.*, ^stubs/.* - -[MESSAGES CONTROL] -disable=C0114,C0115,C0116,R0903 - -[FORMAT] -max-line-length=88 - -[DESIGN] -max-args=5 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 98486dd..927775c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -29,4 +29,4 @@ sphinx: # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - - requirements: requirements.txt + - requirements: requirements/dev.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..268a9c3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,166 @@ +## Contribution Guidelines + +Thank you for your interest in contributing to our package! This document outlines the tools and steps to follow to ensure a smooth and consistent workflow. + +## Contribution Workflow + +1. **Fork and Clone**: Fork the repository and clone it to your local machine. + ```bash + git clone https://github.com/sageteamorg/python-sage-imap.git + cd python-sage-imap + ``` + +2. **Create a Branch**: Create a new branch for your feature or bugfix. + ```bash + git checkout -b feature/your-feature-name + ``` + +3. **Install Dependencies**: Use Poetry to install dependencies. + ```bash + poetry install + ``` + +4. **Write Code and Tests**: Make your changes and write tests for your new code. + +5. **Run Code Quality Checks**: Ensure code quality with pre-commit, Ruff, and Pylint. + ```bash + ruff check . + ruff check --fix + ``` + +6. **Run Tests**: Ensure all tests pass using Poetry. + ```bash + poetry run pytest + ``` + +7. **Commit Changes**: Use Commitizen to commit your changes. + ```bash + cz commit + ``` + +8. **Push and Create a PR**: Push your changes and create a pull request. + ```bash + git push origin feature/your-feature-name + ``` + +9. **Bump Version**: Use Commitizen to bump the version. + ```bash + cz bump + ``` + +10. **Generate Changelog**: Use Commitizen to generate the changelog. + ```bash + cz changelog + ``` + +11. **Export Dependencies**: Export dependencies for development and production. + ```bash + poetry export -f requirements.txt --output packages/requirements.txt --without-hashes + poetry export -f requirements.txt --dev --output packages/requirements-dev.txt --without-hashes + ``` + +## Commitizen Message Rule + +Commitizen follows the Conventional Commits specification. The commit message should be structured as follows: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +Here are 10 examples of commit messages following the Commitizen Conventional Commits specification: + +### 1. Initialization of core +``` +feat(core): initialize the core module + +- Set up the core structure +- Added initial configurations and settings +- Created basic utility functions +``` + +### 2. Release with build and tag version +``` +chore(release): build and tag version 1.0.0 + +- Built the project for production +- Created a new tag for version 1.0.0 +- Updated changelog with release notes +``` + +### 3. Adding a new feature +``` +feat(auth): add user authentication + +- Implemented user login and registration +- Added JWT token generation and validation +- Created middleware for protected routes +``` + +### 4. Fixing a bug +``` +fix(api): resolve issue with data fetching + +- Fixed bug causing incorrect data responses +- Improved error handling in API calls +- Added tests for the fixed bug +``` + +### 5. Update a doc (Sphinx) +``` +docs(sphinx): update API documentation + +- Updated the Sphinx documentation for API changes +- Added examples for new endpoints +- Fixed typos and formatting issues +``` + +### 6. Update dependencies (packages) +``` +chore(deps): update project dependencies + +- Updated all outdated npm packages +- Resolved compatibility issues with new package versions +- Ran tests to ensure no breaking changes +``` + +### 7. Update version for build and publish +``` +chore(version): update version to 2.1.0 for build and publish + +- Incremented version number to 2.1.0 +- Updated package.json with the new version +- Prepared for publishing the new build +``` + +### 8. Adding unit tests +``` +test(auth): add unit tests for authentication module + +- Created tests for login functionality +- Added tests for registration validation +- Ensured 100% coverage for auth module +``` + +### 9. Refactoring codebase +``` +refactor(core): improve code structure and readability + +- Refactored core module to enhance readability +- Extracted utility functions into separate files +- Updated documentation to reflect code changes +``` + +### 10. Improving performance +``` +perf(parser): enhance parsing speed + +- Optimized parsing algorithm for better performance +- Reduced the time complexity of the parsing function +- Added benchmarks to track performance improvements +``` + +These examples cover various types of commits such as feature additions, bug fixes, documentation updates, dependency updates, versioning, testing, refactoring, and performance improvements. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 807c4a8..313ec54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,11 +4,13 @@ Thank you for considering contributing to `python-sage-imap`! We welcome contrib ## Table of Contents -- [Contributing to pyhton-sage-imap](#contributing-to-python-sage-imap) +- [Contributing to python-sage-imap](#contributing-to-python-sage-imap) - [Table of Contents](#table-of-contents) - [Getting Started](#getting-started) - [Running Tests](#running-tests) - [Code Style](#code-style) + - [Security Checks](#security-checks) + - [Running Bandit](#running-bandit) - [Pre-commit Hooks](#pre-commit-hooks) - [Setting Up Pre-commit Hooks](#setting-up-pre-commit-hooks) - [Submitting a Pull Request](#submitting-a-pull-request) @@ -25,7 +27,7 @@ Thank you for considering contributing to `python-sage-imap`! We welcome contrib ```bash git clone https://github.com/your-username/python-sage-imap.git - cd pyhton-sage-imap + cd python-sage-imap ``` 3. **Install dependencies using Poetry**: @@ -68,6 +70,18 @@ poetry run flake8 poetry run pylint django_sage_email ``` +## Security Checks + +We use `bandit` to perform security checks on our codebase. Bandit helps identify common security issues in Python code. + +### Running Bandit + +To run Bandit with the current configuration: + +```bash +poetry run bandit -c pyproject.toml +``` + ## Pre-commit Hooks We use `pre-commit` to ensure code quality and consistency. Pre-commit hooks will run automatically before each commit to check and format the code. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c971fbb..1419eea 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -9,6 +9,11 @@ We would like to thank the following people for their contributions to the `pyth - Email: sepehr@sageteam.org - Contributions: Project creator and lead maintainer. +- **Mohammad Fotouhi** + - GitHub: [github-mohammad](https://github.com/MohmdFo) + - Email: radinceorc77@gmail.com + - Contributions: Core contributor. + --- To be added to this list, please contribute to the project by submitting a pull request, opening issues, or helping improve the documentation. We appreciate all contributions, big and small! diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 4f3e3d3..0000000 --- a/mypy.ini +++ /dev/null @@ -1,10 +0,0 @@ -[mypy] -mypy_path = stubs -disallow_untyped_calls = true -disallow_untyped_defs = true -ignore_missing_imports = true -explicit_package_bases = true -exclude = ^(docs/source/conf.py|build/|tests/|stubs/) - -[mypy-sage_imap.*] -ignore_missing_imports = True \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 81d00a9..7baf88b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -36,6 +36,9 @@ files = [ {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + [[package]] name = "babel" version = "2.15.0" @@ -50,6 +53,30 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +[[package]] +name = "bandit" +version = "1.7.10" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"}, + {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0)"] +yaml = ["PyYAML"] + [[package]] name = "black" version = "24.4.2" @@ -87,6 +114,8 @@ mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -361,6 +390,9 @@ files = [ {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -430,6 +462,20 @@ files = [ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.15.4" @@ -446,22 +492,6 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] -[[package]] -name = "flake8" -version = "7.1.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, - {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.12.0,<2.13.0" -pyflakes = ">=3.2.0,<3.3.0" - [[package]] name = "identify" version = "2.6.0" @@ -540,6 +570,30 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.5" @@ -620,6 +674,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mypy" version = "1.11.1" @@ -658,6 +723,7 @@ files = [ [package.dependencies] mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.6.0" [package.extras] @@ -710,6 +776,17 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "pbr" +version = "6.1.0" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, + {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, +] + [[package]] name = "platformdirs" version = "4.2.2" @@ -773,28 +850,6 @@ files = [ [package.dependencies] wcwidth = "*" -[[package]] -name = "pycodestyle" -version = "2.12.0" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, - {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, -] - -[[package]] -name = "pyflakes" -version = "3.2.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, -] - [[package]] name = "pygments" version = "2.18.0" @@ -824,12 +879,14 @@ files = [ astroid = ">=3.2.4,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" [package.extras] @@ -849,6 +906,7 @@ files = [ [package.dependencies] packaging = ">=24.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2024.5.6)", "sphinx-autodoc-typehints (>=2.2.1)"] @@ -867,9 +925,11 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -987,6 +1047,52 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.9.3" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"}, + {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.7.1" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, + {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, + {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, + {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, + {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, + {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, + {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -1026,6 +1132,7 @@ sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] @@ -1159,6 +1266,20 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "stevedore" +version = "5.3.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, + {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, +] + +[package.dependencies] +pbr = ">=2.0.0" + [[package]] name = "termcolor" version = "2.4.0" @@ -1173,6 +1294,17 @@ files = [ [package.extras] tests = ["pytest", "pytest-cov"] +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + [[package]] name = "tomlkit" version = "0.13.0" @@ -1204,23 +1336,13 @@ packaging = ">=24.1" platformdirs = ">=4.2.2" pluggy = ">=1.5" pyproject-api = ">=1.7.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} virtualenv = ">=20.26.3" [package.extras] docs = ["furo (>=2024.5.6)", "sphinx (>=7.3.7)", "sphinx-argparse-cli (>=1.16)", "sphinx-autodoc-typehints (>=2.2.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] testing = ["build[virtualenv] (>=1.2.1)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=70.2)", "time-machine (>=2.14.2)", "wheel (>=0.43)"] -[[package]] -name = "types-setuptools" -version = "70.3.0.20240710" -description = "Typing stubs for setuptools" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-setuptools-70.3.0.20240710.tar.gz", hash = "sha256:842cbf399812d2b65042c9d6ff35113bbf282dee38794779aa1f94e597bafc35"}, - {file = "types_setuptools-70.3.0.20240710-py3-none-any.whl", hash = "sha256:bd0db2a4b9f2c49ac5564be4e0fb3125c4c46b1f73eafdcbceffa5b005cceca4"}, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -1292,5 +1414,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = ">=3.11,<4.0" -content-hash = "5a1de434adb77813810857813ad2af2bf1d42b2a950326bc29818072dd91384c" +python-versions = ">=3.10,<4.0" +content-hash = "6703dd2979e9c92ed41798945ac253e18b242f48c0360d63253e624fbaad7abe" diff --git a/pyproject.toml b/pyproject.toml index 449faa6..d859386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-sage-imap" -version = "0.4.6" +version = "0.4.7" description = "A Python package for managing IMAP connections and email operations." authors = ["Sepehr Akbarzadeh "] readme = "README.md" @@ -25,7 +25,7 @@ packages = [ "Issues" = "https://github.com/sageteamorg/python-sage-imap/issues" [tool.poetry.dependencies] -python = ">=3.11,<4.0" +python = ">=3.10,<4.0" requests = "^2.32.3" [tool.poetry.group.dev.dependencies] @@ -33,42 +33,49 @@ black = "^24.4.2" isort = "^5.13.2" mypy = "^1.10.1" pytest = "^8.2.2" -flake8 = "^7.1.0" tox = "^4.15.1" coverage = "^7.5.4" pre-commit = "^3.7.1" -sphinx = "^7.3.7" pylint = "^3.2.5" pytest-cov = "^5.0.0" commitizen = "^3.27.0" docformatter = "^1.7.5" -types-setuptools = "^70.2.0.20240704" codecov = "^2.1.13" sphinx-rtd-theme = "^2.0.0" +ruff = "^0.7.1" +bandit = {extras = ["toml"], version = "^1.7.10"} -[tool.black] -line-length = 88 -target-version = ['py38'] -exclude = ''' -/( - \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - | docs -)/ -''' +[tool.pytest.ini_options] +addopts = "--cov --cov-report=term-missing --cov-report=html --cov-fail-under=90" +python_files = ["tests.py", "test_*.py"] +testpaths = ["tests"] +norecursedirs = [ + "node_modules", + "env", + "venv", + ".venv", + "dist", + "build", +] -[tool.isort] -profile = "black" -line_length = 88 -known_first_party = ["sage_imap"] -skip = ["docs"] +[tool.bandit] +targets = ["./sage_sms"] +exclude_dirs = [ + "tests", + "migrations", +] +severity = "medium" +confidence = "medium" +max_lines = 500 +progress = true +reports = true +output_format = "screen" +output_file = "bandit_report.txt" +include = ["B101", "B102"] +exclude_tests = ["B301", "B302"] + +[tool.bandit.plugins] +B104 = { check_typed_list = true } [tool.mypy] mypy_path = "stubs" @@ -80,16 +87,123 @@ exclude = ''' ^docs/source/conf.py| ^build/| ^tests/| -^stubs/ +^stubs/| +^kernel/ +''' + +[tool.black] +line-length = 88 +exclude = ''' +/( + \.git + | \.venv + | build + | dist + | migrations + | venv + | env + | __pycache__ + | static + | media + | node_modules + | env + | kernel + | \.mypy_cache + | \.pytest_cache + | .*\.egg-info +)/ ''' +[tool.isort] +profile = "black" +line_length = 88 +skip = [ + "venv", + ".venv", + "build", + "dist", + ".git", + "__pycache__", + "*.egg-info", + ".mypy_cache", + ".pytest_cache", + "migrations", + "static", + "media", + "node_modules", + "env", + "kernel" +] + +[tool.coverage.run] +omit = [ + "*/migrations/*", + "kernel/*", + "*/apps.py", + "manage.py", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if self\\.debug", + "raise AssertionError", + "if 0:", + "if __name__ == .__main__.:" +] + [tool.commitizen] name = "cz_conventional_commits" version = "0.1.0" -[tool.pytest.ini_options] -addopts = "--strict-markers" -testpaths = ["tests"] +[tool.commitizen.settings] +increment_types = ["feat", "fix"] + +[tool.pylint] +disable = [ + "C0114", # Missing module docstring + "C0115", # Missing class docstring + "C0116", # Missing function or method docstring + "E1101", # Instance of 'Foo' has no 'bar' member (Django dynamic attributes) + "W0212", # Access to a protected member _foo of a client class + "C0330", # Wrong hanging indentation before block (conflicts with Black) +] +max-line-length = 88 +ignore = [ + "migrations/*", + "venv/*", + "build/*", + "dist/*", + ".git/*", + "__pycache__/*", + "*.egg-info/*", + ".mypy_cache/*", + ".pytest_cache/*" +] +load-plugins = [ + "pylint_django", + "pylint.extensions.docparams", +] +django-settings-module = "myproject.settings" +good-names = [ + "qs", # QuerySet abbreviation + "pk", # Primary key abbreviation + "id", # Identifier +] +suggestion-mode = true +const-rgx = "([A-Z_][A-Z0-9_]*)|(__.*__)" +attr-rgx = "[a-z_][a-z0-9_]{2,30}$" +variable-rgx = "[a-z_][a-z0-9_]{2,30}$" +argument-rgx = "[a-z_][a-z0-9_]{2,30}$" +argument-name-hint = [ + "cls", # class method first argument + "self", # instance method first argument +] +method-rgx = "[a-z_][a-z0-9_]{2,30}$" +function-rgx = "[a-z_][a-z0-9_]{2,30}$" +class-rgx = "[A-Z_][a-zA-Z0-9]+$" +module-rgx = "(([a-z_][a-z0-9_]*)|(__.*__))$" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 49dc1fb..0000000 --- a/requirements.txt +++ /dev/null @@ -1,384 +0,0 @@ -alabaster==0.7.16 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65 \ - --hash=sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92 -babel==2.15.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb \ - --hash=sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413 -certifi==2024.7.4 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ - --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 -cffi==1.16.0 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "linux" and platform_python_implementation != "PyPy" \ - --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ - --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ - --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ - --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ - --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ - --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ - --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ - --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ - --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ - --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ - --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ - --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ - --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ - --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ - --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ - --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ - --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ - --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ - --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ - --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ - --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ - --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ - --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ - --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ - --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ - --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ - --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ - --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ - --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ - --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ - --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ - --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ - --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ - --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ - --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ - --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ - --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ - --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ - --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ - --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ - --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ - --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ - --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ - --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ - --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ - --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ - --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ - --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ - --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ - --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ - --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ - --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 -charset-normalizer==3.3.2 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ - --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ - --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ - --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ - --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ - --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ - --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ - --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ - --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ - --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ - --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ - --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ - --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ - --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ - --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ - --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ - --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ - --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ - --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ - --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ - --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ - --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ - --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ - --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ - --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ - --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ - --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ - --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ - --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ - --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ - --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ - --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ - --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ - --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ - --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ - --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ - --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ - --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ - --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ - --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ - --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ - --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ - --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ - --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ - --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ - --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ - --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ - --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ - --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ - --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ - --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ - --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ - --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ - --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ - --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ - --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ - --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ - --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ - --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ - --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ - --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ - --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ - --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ - --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ - --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ - --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ - --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ - --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ - --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ - --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ - --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ - --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ - --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ - --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ - --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ - --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ - --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ - --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ - --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ - --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ - --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ - --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ - --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ - --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ - --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ - --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ - --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ - --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ - --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ - --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 -colorama==0.4.6 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "win32" \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 -cryptography==42.0.8 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "linux" \ - --hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \ - --hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \ - --hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \ - --hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \ - --hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \ - --hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \ - --hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \ - --hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \ - --hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \ - --hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \ - --hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \ - --hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \ - --hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \ - --hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \ - --hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \ - --hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \ - --hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \ - --hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \ - --hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \ - --hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \ - --hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \ - --hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \ - --hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \ - --hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \ - --hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \ - --hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \ - --hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \ - --hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \ - --hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \ - --hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \ - --hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \ - --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e -docutils==0.20.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ - --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b -idna==3.7 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ - --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 -imagesize==1.4.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ - --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a -importlib-metadata==7.2.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:509ecb2ab77071db5137c655e24ceb3eee66e7bbc6574165d0d114d9fc4bbe68 \ - --hash=sha256:ffef94b0b66046dd8ea2d619b701fe978d9264d38f3998bc4c27ec3b146a87c8 -jaraco-classes==3.4.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ - --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 -jaraco-context==5.3.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266 \ - --hash=sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2 -jaraco-functools==4.0.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664 \ - --hash=sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8 -jeepney==0.8.0 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "linux" \ - --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ - --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 -jinja2==3.1.4 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d -keyring==25.2.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50 \ - --hash=sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b -markdown-it-py==3.0.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb -markupsafe==2.1.5 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ - --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ - --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ - --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ - --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ - --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ - --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ - --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ - --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ - --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ - --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ - --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ - --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ - --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ - --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ - --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ - --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ - --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ - --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ - --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ - --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ - --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ - --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ - --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ - --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ - --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ - --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ - --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ - --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ - --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ - --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ - --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ - --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ - --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ - --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ - --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ - --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ - --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ - --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ - --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ - --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ - --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ - --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ - --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ - --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ - --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ - --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ - --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ - --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ - --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ - --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ - --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ - --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ - --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ - --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ - --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ - --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ - --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ - --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ - --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 -mdurl==0.1.2 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba -more-itertools==10.3.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463 \ - --hash=sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320 -nh3==0.2.17 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a \ - --hash=sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911 \ - --hash=sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb \ - --hash=sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a \ - --hash=sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc \ - --hash=sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028 \ - --hash=sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9 \ - --hash=sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3 \ - --hash=sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351 \ - --hash=sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10 \ - --hash=sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71 \ - --hash=sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f \ - --hash=sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b \ - --hash=sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a \ - --hash=sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062 \ - --hash=sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a -packaging==24.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 -pkginfo==1.10.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ - --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 -pycparser==2.22 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "linux" and platform_python_implementation != "PyPy" \ - --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ - --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc -pygments==2.18.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ - --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a -pywin32-ctypes==0.2.2 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "win32" \ - --hash=sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60 \ - --hash=sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7 -readme-renderer==43.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311 \ - --hash=sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9 -requests-toolbelt==1.0.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ - --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 -requests==2.32.3 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 -rfc3986==2.0.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ - --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c -rich==13.7.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ - --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 -secretstorage==3.3.3 ; python_version >= "3.12" and python_version < "4.0" and sys_platform == "linux" \ - --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ - --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 -setuptools==70.2.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \ - --hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1 -snowballstemmer==2.2.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ - --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a -sphinx-rtd-theme==2.0.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b \ - --hash=sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586 -sphinx==7.3.7 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3 \ - --hash=sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc -sphinxcontrib-applehelp==1.0.8 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619 \ - --hash=sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4 -sphinxcontrib-devhelp==1.0.6 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f \ - --hash=sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3 -sphinxcontrib-htmlhelp==2.0.5 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015 \ - --hash=sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04 -sphinxcontrib-jquery==4.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \ - --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae -sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ - --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 -sphinxcontrib-qthelp==1.0.7 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6 \ - --hash=sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182 -sphinxcontrib-serializinghtml==1.1.10 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7 \ - --hash=sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f -twine==5.1.1 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ - --hash=sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db -urllib3==2.2.2 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ - --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 -wheel==0.43.0 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ - --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 -zipp==3.19.2 ; python_version >= "3.12" and python_version < "4.0" \ - --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ - --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c diff --git a/sage_imap/exceptions.py b/sage_imap/exceptions.py index cd1ed77..fce0a95 100644 --- a/sage_imap/exceptions.py +++ b/sage_imap/exceptions.py @@ -1,7 +1,9 @@ from typing import Optional + class IMAPClientError(Exception): """Base class for all IMAP client exceptions.""" + status_code: int = 500 default_detail: str = "A server error occurred." default_code: str = "error" @@ -29,18 +31,23 @@ def __str__(self) -> str: # Configuration and connection errors class IMAPConfigurationError(IMAPClientError): """Exception raised for configuration errors.""" + status_code = 400 default_detail = "Invalid IMAP configuration." default_code = "configuration_error" + class IMAPConnectionError(IMAPClientError): """Exception raised for connection errors.""" + status_code = 502 default_detail = "Failed to connect to IMAP server." default_code = "connection_error" + class IMAPAuthenticationError(IMAPClientError): """Exception raised for authentication errors.""" + status_code = 401 default_detail = "Failed to authenticate with IMAP server." default_code = "authentication_error" @@ -49,30 +56,39 @@ class IMAPAuthenticationError(IMAPClientError): # Folder-related errors class IMAPFolderError(IMAPClientError): """Base class for IMAP folder errors.""" + status_code = 500 default_detail = "A folder-related error occurred." default_code = "folder_error" + class IMAPFolderOperationError(IMAPFolderError): """Exception raised for general folder operation errors.""" + status_code = 500 default_detail = "Failed to perform folder operation." default_code = "folder_operation_error" + class IMAPFolderNotFoundError(IMAPFolderError): """Exception raised when a folder is not found.""" + status_code = 404 default_detail = "Folder not found." default_code = "folder_not_found_error" + class IMAPFolderExistsError(IMAPFolderError): """Exception raised when a folder already exists.""" + status_code = 409 default_detail = "Folder already exists." default_code = "folder_exists_error" + class IMAPDefaultFolderError(IMAPFolderError): """Exception raised when trying to perform an operation on a default folder.""" + status_code = 403 default_detail = "Operation not allowed on default folder." default_code = "default_folder_error" @@ -81,6 +97,7 @@ class IMAPDefaultFolderError(IMAPFolderError): # Unexpected error class IMAPUnexpectedError(IMAPClientError): """Exception raised for unexpected IMAP errors.""" + status_code = 500 default_detail = "An unexpected error occurred with the IMAP server." default_code = "unexpected_error" @@ -89,12 +106,15 @@ class IMAPUnexpectedError(IMAPClientError): # Flag-related errors class IMAPFlagError(IMAPClientError): """Base class for IMAP flag errors.""" + status_code = 500 default_detail = "A flag-related error occurred." default_code = "flag_error" + class IMAPFlagOperationError(IMAPFlagError): """Exception raised for flag operation errors.""" + status_code = 500 default_detail = "Failed to perform flag operation." default_code = "flag_operation_error" @@ -103,84 +123,111 @@ class IMAPFlagOperationError(IMAPFlagError): # Mailbox-related errors class IMAPMailboxError(IMAPClientError): """Base class for IMAP mailbox errors.""" + status_code = 500 default_detail = "A mailbox-related error occurred." default_code = "mailbox_error" + class IMAPMailboxSelectionError(IMAPMailboxError): """Exception raised for mailbox selection errors.""" + status_code = 500 default_detail = "Failed to select mailbox." default_code = "mailbox_selection_error" + class IMAPMailboxClosureError(IMAPMailboxError): """Exception raised for mailbox closure errors.""" + status_code = 500 default_detail = "Failed to close mailbox." default_code = "mailbox_closure_error" + class IMAPMailboxCheckError(IMAPMailboxError): """Exception raised for mailbox check errors.""" + status_code = 500 default_detail = "Failed to perform mailbox check." default_code = "mailbox_check_error" + class IMAPMailboxDeleteError(IMAPMailboxError): """Exception raised for delete errors.""" + status_code = 500 default_detail = "Failed to delete email." default_code = "delete_error" + class IMAPMailboxPermanentDeleteError(IMAPMailboxDeleteError): """Exception raised for permanent delete errors.""" + status_code = 500 default_detail = "Failed to permanently delete email." default_code = "permanent_delete_error" + class IMAPMailboxMoveError(IMAPMailboxError): """Exception raised for move errors.""" + status_code = 500 default_detail = "Failed to move email." default_code = "move_error" + class IMAPSearchError(IMAPMailboxError): """Exception raised for search errors.""" + status_code = 500 default_detail = "Failed to search emails." default_code = "search_error" + class IMAPMailboxSaveSentError(IMAPMailboxError): """Exception raised for errors while saving sent emails.""" + status_code = 500 default_detail = "Failed to save sent email." default_code = "save_sent_error" + class IMAPMailboxStatusError(IMAPMailboxError): """Exception raised for errors while getting mailbox status.""" + status_code = 500 default_detail = "Failed to get mailbox status." default_code = "status_error" + class IMAPMailboxFetchError(IMAPMailboxError): """Exception raised for fetch operation errors.""" + status_code = 500 default_detail = "Failed to fetch email messages." default_code = "fetch_error" + class IMAPAppendError(IMAPMailboxError): """Exception raised when appending to the IMAP server fails.""" + status_code = 500 default_detail = "Failed to append to the IMAP server." default_code = "imap_append_error" + class IMAPMailboxUploadError(IMAPMailboxError): """Exception raised for upload file.""" + status_code = 500 default_detail = "Failed to upload email file." default_code = "upload_error" + class IMAPThreadError(IMAPMailboxError): """Exception raised for thread errors.""" + status_code = 500 default_detail = "Thread error." default_code = "thread_error" @@ -189,12 +236,15 @@ class IMAPThreadError(IMAPMailboxError): # Other errors class IMAPEmptyFileError(IMAPClientError): """Exception raised when the specified file is empty.""" + status_code = 400 default_detail = "The specified file is empty." default_code = "empty_file_error" + class IMAPInvalidEmailDateError(IMAPClientError): """Exception raised when the email does not have a valid Date header.""" + status_code = 400 default_detail = "The email does not have a valid Date header." default_code = "invalid_email_date_error" diff --git a/sage_imap/services/client.py b/sage_imap/services/client.py index 4433c23..9acab2d 100644 --- a/sage_imap/services/client.py +++ b/sage_imap/services/client.py @@ -67,7 +67,6 @@ class IMAPClient: >>> # Process messages >>> client.disconnect() """ - def __init__(self, host: str, username: str, password: str): self.host: str = host @@ -101,7 +100,7 @@ def connect(self) -> imaplib.IMAP4_SSL: if self.connection is not None: logger.warning("Already connected to the IMAP server.") return self.connection - + try: logger.debug("Resolving IMAP server hostname: %s", self.host) resolved_host = socket.gethostbyname(self.host) @@ -126,9 +125,9 @@ def connect(self) -> imaplib.IMAP4_SSL: raise IMAPAuthenticationError("IMAP login failed.") from e return self.connection - + def __enter__(self) -> imaplib.IMAP4_SSL: - """Establishes an IMAP connection and logs in (for context manager).""" + """Establishes an IMAP connection and logs in (for context manager).""" return self.connect() def disconnect(self) -> None: @@ -140,7 +139,7 @@ def disconnect(self) -> None: If an error occurs during logout, an appropriate custom exception is raised. After performing logout operation , the connection is set to None to point out that it has been closed. - + Raises ------ IMAPUnexpectedError @@ -166,4 +165,4 @@ def __exit__( traceback: Optional[object], ) -> None: """Logs out from the IMAP server and closes the connection (for context manager).""" - self.disconnect() \ No newline at end of file + self.disconnect() diff --git a/sage_imap/services/folder.py b/sage_imap/services/folder.py index 0043d42..e11cc49 100644 --- a/sage_imap/services/folder.py +++ b/sage_imap/services/folder.py @@ -1,14 +1,14 @@ import logging -from typing import List import re +from typing import List from sage_imap.exceptions import ( IMAPFolderExistsError, IMAPFolderNotFoundError, IMAPFolderOperationError, ) -from sage_imap.services.client import IMAPClient from sage_imap.helpers.typings import Mailbox +from sage_imap.services.client import IMAPClient logger = logging.getLogger(__name__) @@ -275,7 +275,7 @@ def list_folders(self) -> List[Mailbox]: folders.append(folder_name) else: # Handle cases where the folder name is not in the expected format - parts = folder_str.split(' ') + parts = folder_str.split(" ") folder_name = parts[-1].strip('"') folders.append(folder_name) diff --git a/sage_imap/services/mailbox.py b/sage_imap/services/mailbox.py index 89ddccb..c308b0a 100644 --- a/sage_imap/services/mailbox.py +++ b/sage_imap/services/mailbox.py @@ -118,7 +118,6 @@ def __combine_status_items(self, *status_items: MailboxStatusItems) -> str: class IMAPMailboxService(BaseMailboxService): - @mailbox_selection_required def search( self, criteria: IMAPSearchCriteria, charset: Optional[str] = "UTF-8" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 44f6b01..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[metadata] -description-file = README.md -mypy_path = stubs diff --git a/setup.py b/setup.py deleted file mode 100644 index 412bc84..0000000 --- a/setup.py +++ /dev/null @@ -1,28 +0,0 @@ -from setuptools import find_packages, setup - -with open("README.md", encoding="utf-8") as f: - long_description = f.read() - -setup( - name="python_sage_imap", - version="0.4.6", - author="Sepehr Akbarzadeh", - author_email="info@sageteam.org", - description="A Python package for managing IMAP connections and email operations.", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/sageteamorg/python-sage-imap", - project_urls={ - "Documentation": "https://python-sage-imap.readthedocs.io/en/latest/", - "Source Code": "https://github.com/sageteamorg/python-sage-imap", - "Issues": "https://github.com/sageteamorg/python-sage-imap/issues", - }, - packages=find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - ], - python_requires=">=3.11", -) diff --git a/stubs/sage_imap/exceptions.pyi b/stubs/sage_imap/exceptions.pyi index a0e38a0..fed6451 100644 --- a/stubs/sage_imap/exceptions.pyi +++ b/stubs/sage_imap/exceptions.pyi @@ -6,7 +6,12 @@ class IMAPClientError(Exception): default_code: str detail: Incomplete code: Incomplete - def __init__(self, detail: str | None = None, code: str | None = None, status_code: int | None = None) -> None: ... + def __init__( + self, + detail: str | None = None, + code: str | None = None, + status_code: int | None = None, + ) -> None: ... class IMAPConfigurationError(IMAPClientError): status_code: int diff --git a/stubs/sage_imap/models/email.pyi b/stubs/sage_imap/models/email.pyi index 988fdbf..ee2165e 100644 --- a/stubs/sage_imap/models/email.pyi +++ b/stubs/sage_imap/models/email.pyi @@ -2,7 +2,10 @@ import email from _typeshed import Incomplete from dataclasses import dataclass from sage_imap.helpers.enums import Flag as Flag -from sage_imap.helpers.typings import EmailAddress as EmailAddress, EmailDate as EmailDate +from sage_imap.helpers.typings import ( + EmailAddress as EmailAddress, + EmailDate as EmailDate, +) from typing import Any, Callable logger: Incomplete @@ -15,7 +18,15 @@ class Attachment: id: str | None = ... content_id: str | None = ... content_transfer_encoding: str | None = ... - def __init__(self, filename, content_type, payload, id=..., content_id=..., content_transfer_encoding=...) -> None: ... + def __init__( + self, + filename, + content_type, + payload, + id=..., + content_id=..., + content_transfer_encoding=..., + ) -> None: ... @dataclass class EmailMessage: @@ -44,14 +55,34 @@ class EmailMessage: def sanitize_message_id(self, message_id: str) -> str | None: ... def parse_date(self, date_str: str | None) -> EmailDate | None: ... def extract_body(self, message: email.message.EmailMessage) -> tuple[str, str]: ... - def extract_attachments(self, message: email.message.EmailMessage) -> list[Attachment]: ... + def extract_attachments( + self, message: email.message.EmailMessage + ) -> list[Attachment]: ... @staticmethod def extract_flags(flag_data: bytes) -> list[Flag]: ... def decode_payload(self, part: email.message.EmailMessage) -> str: ... def has_attachments(self) -> bool: ... def get_attachment_filenames(self) -> list[str]: ... def write_to_eml_file(self, file_path: str) -> None: ... - def __init__(self, message_id, subject=..., from_address=..., to_address=..., cc_address=..., bcc_address=..., date=..., raw=..., plain_body=..., html_body=..., attachments=..., flags=..., headers=..., size=..., sequence_number=..., uid=...) -> None: ... + def __init__( + self, + message_id, + subject=..., + from_address=..., + to_address=..., + cc_address=..., + bcc_address=..., + date=..., + raw=..., + plain_body=..., + html_body=..., + attachments=..., + flags=..., + headers=..., + size=..., + sequence_number=..., + uid=..., + ) -> None: ... class EmailIterator: def __init__(self, email_list: list[EmailMessage]) -> None: ... diff --git a/stubs/sage_imap/services/__init__.pyi b/stubs/sage_imap/services/__init__.pyi index 68aef46..fe3923f 100644 --- a/stubs/sage_imap/services/__init__.pyi +++ b/stubs/sage_imap/services/__init__.pyi @@ -1,4 +1,7 @@ from .client import IMAPClient as IMAPClient from .flag import IMAPFlagService as IMAPFlagService from .folder import IMAPFolderService as IMAPFolderService -from .mailbox import IMAPMailboxService as IMAPMailboxService, IMAPMailboxUIDService as IMAPMailboxUIDService +from .mailbox import ( + IMAPMailboxService as IMAPMailboxService, + IMAPMailboxUIDService as IMAPMailboxUIDService, +) diff --git a/stubs/sage_imap/services/client.pyi b/stubs/sage_imap/services/client.pyi index bac1490..84d740f 100644 --- a/stubs/sage_imap/services/client.pyi +++ b/stubs/sage_imap/services/client.pyi @@ -1,6 +1,10 @@ import imaplib from _typeshed import Incomplete -from sage_imap.exceptions import IMAPAuthenticationError as IMAPAuthenticationError, IMAPConnectionError as IMAPConnectionError, IMAPUnexpectedError as IMAPUnexpectedError +from sage_imap.exceptions import ( + IMAPAuthenticationError as IMAPAuthenticationError, + IMAPConnectionError as IMAPConnectionError, + IMAPUnexpectedError as IMAPUnexpectedError, +) logger: Incomplete @@ -11,4 +15,9 @@ class IMAPClient: connection: Incomplete def __init__(self, host: str, username: str, password: str) -> None: ... def __enter__(self) -> imaplib.IMAP4_SSL: ... - def __exit__(self, exc_type: type | None, exc_value: BaseException | None, traceback: object | None) -> None: ... + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + traceback: object | None, + ) -> None: ... diff --git a/stubs/sage_imap/services/folder.pyi b/stubs/sage_imap/services/folder.pyi index 18f4b6b..2be94b1 100644 --- a/stubs/sage_imap/services/folder.pyi +++ b/stubs/sage_imap/services/folder.pyi @@ -1,5 +1,9 @@ from _typeshed import Incomplete -from sage_imap.exceptions import IMAPFolderExistsError as IMAPFolderExistsError, IMAPFolderNotFoundError as IMAPFolderNotFoundError, IMAPFolderOperationError as IMAPFolderOperationError +from sage_imap.exceptions import ( + IMAPFolderExistsError as IMAPFolderExistsError, + IMAPFolderNotFoundError as IMAPFolderNotFoundError, + IMAPFolderOperationError as IMAPFolderOperationError, +) from sage_imap.helpers.typings import Mailbox as Mailbox from sage_imap.services.client import IMAPClient as IMAPClient diff --git a/stubs/sage_imap/services/mailbox.pyi b/stubs/sage_imap/services/mailbox.pyi index 0b4dc14..d02151c 100644 --- a/stubs/sage_imap/services/mailbox.pyi +++ b/stubs/sage_imap/services/mailbox.pyi @@ -7,33 +7,54 @@ from sage_imap.models.message import MessageSet from sage_imap.services.client import IMAPClient from typing import Any -__all__ = ['IMAPMailboxService'] +__all__ = ["IMAPMailboxService"] class BaseMailboxService: client: Incomplete current_selection: Incomplete def __init__(self, client: IMAPClient) -> None: ... def __enter__(self): ... - def __exit__(self, exc_type: type | None, exc_value: BaseException | None, traceback: Any | None) -> None: ... + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + traceback: Any | None, + ) -> None: ... def select(self, mailbox: str | None): ... def close(self) -> None: ... def check(self) -> None: ... def status(self, mailbox: Mailbox, *status_items: MailboxStatusItems): ... class IMAPMailboxService(BaseMailboxService): - def search(self, criteria: IMAPSearchCriteria, charset: str | None = 'UTF-8') -> MessageIDList: ... + def search( + self, criteria: IMAPSearchCriteria, charset: str | None = "UTF-8" + ) -> MessageIDList: ... def trash(self, msg_set: MessageSet, trash_mailbox: Mailbox): ... def delete(self, msg_set: MessageSet, trash_mailbox: Mailbox): ... def move(self, msg_set: MessageSet, destination_mailbox: Mailbox): ... - def restore(self, msg_set: MessageSet, trash_mailbox: Mailbox, safe_mailbox: Mailbox): ... + def restore( + self, msg_set: MessageSet, trash_mailbox: Mailbox, safe_mailbox: Mailbox + ): ... def fetch(self, msg_set: MessageSet, msg_part: MessagePart): ... - def save_sent(self, sent_mailbox: Mailbox, raw: RawEmail, flags: Flag = None, date_time: str = None): ... - def upload_eml(self, emails: EmailIterator | list[EmailMessage], flags: Flag, mailbox: Mailbox): ... + def save_sent( + self, + sent_mailbox: Mailbox, + raw: RawEmail, + flags: Flag = None, + date_time: str = None, + ): ... + def upload_eml( + self, emails: EmailIterator | list[EmailMessage], flags: Flag, mailbox: Mailbox + ): ... class IMAPMailboxUIDService(BaseMailboxService): - def uid_search(self, criteria: IMAPSearchCriteria, charset: str | None = 'UTF-8') -> MessageIDList: ... + def uid_search( + self, criteria: IMAPSearchCriteria, charset: str | None = "UTF-8" + ) -> MessageIDList: ... def uid_trash(self, msg_set: MessageSet, trash_mailbox: Mailbox): ... def uid_delete(self, msg_set: MessageSet, trash_mailbox: Mailbox): ... def uid_move(self, msg_set: MessageSet, destination_mailbox: Mailbox): ... - def uid_restore(self, msg_set: MessageSet, trash_mailbox: Mailbox, safe_mailbox: Mailbox): ... + def uid_restore( + self, msg_set: MessageSet, trash_mailbox: Mailbox, safe_mailbox: Mailbox + ): ... def uid_fetch(self, msg_set: MessageSet, msg_part: MessagePart): ... diff --git a/stubs/sage_imap/utils.pyi b/stubs/sage_imap/utils.pyi index a0c5c56..aae035f 100644 --- a/stubs/sage_imap/utils.pyi +++ b/stubs/sage_imap/utils.pyi @@ -1,6 +1,9 @@ from datetime import datetime from pathlib import Path -from sage_imap.models.email import EmailIterator as EmailIterator, EmailMessage as EmailMessage +from sage_imap.models.email import ( + EmailIterator as EmailIterator, + EmailMessage as EmailMessage, +) def convert_to_local_time(dt: datetime) -> datetime: ... def read_eml_files_from_directory(directory_path: Path) -> EmailIterator: ... diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a75710d --- /dev/null +++ b/tox.ini @@ -0,0 +1,19 @@ +[tox] +envlist = + py310, py311, py312 + +[gh-actions] +python = + 3.10: py310 + 3.11: py311 + 3.12: py312 + +[testenv] +description = Run Pytest tests +usedevelop = True +deps = + pytest + pytest-cov + +commands = + pytest --cov \ No newline at end of file