From a69ffcbb7ebdac6207a0a71bbde0f3f57da43d19 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 9 Mar 2021 21:14:43 +0100 Subject: [PATCH] Refactor AdGuard Home package --- .deepsource.toml | 18 + .flake8 | 5 +- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE.md | 20 + .github/PULL_REQUEST_TEMPLATE.md | 9 + .github/dependabot.yml | 7 +- .github/labels.yml | 85 + .github/release-drafter.yml | 57 + .github/workflows/ci.yml | 54 - .github/workflows/codeql.yaml | 21 + .github/workflows/labels.yaml | 22 + .github/workflows/linting.yaml | 128 ++ .github/workflows/lock.yaml | 21 + .github/workflows/pr-labels.yaml | 21 + .github/workflows/release-drafter.yaml | 18 + .github/workflows/release.yaml | 61 + .github/workflows/requirements.txt | 2 + .github/workflows/stale.yaml | 40 + .github/workflows/tests.yaml | 101 ++ .github/workflows/typing.yaml | 115 ++ .gitignore | 6 + .isort.cfg | 5 - .nvmrc | 1 + .pre-commit-config.yaml | 218 ++- .prettierignore | 1 + CODE_OF_CONDUCT.md | 159 +- LICENSE.md | 2 +- MANIFEST.in | 4 - Makefile | 114 -- README.md | 74 +- adguardhome/__init__.py | 7 - adguardhome/__version__.py | 3 - adguardhome/filtering.py | 120 -- adguardhome/parental.py | 34 - adguardhome/querylog.py | 55 - adguardhome/safebrowsing.py | 32 - adguardhome/safesearch.py | 32 - adguardhome/stats.py | 61 - examples/stats.py | 2 +- examples/status.py | 2 +- mypi.ini | 11 +- package-lock.json | 20 + package.json | 16 + poetry.lock | 1499 +++++++++++++++++ pylintrc | 383 ----- pyproject.toml | 125 ++ requirements.txt | 2 - requirements_dev.txt | 8 - requirements_test.txt | 10 - setup.py | 56 - src/adguardhome/__init__.py | 5 + .../adguardhome}/adguardhome.py | 144 +- .../adguardhome}/exceptions.py | 4 - src/adguardhome/filtering.py | 197 +++ src/adguardhome/parental.py | 58 + src/adguardhome/py.typed | 0 src/adguardhome/querylog.py | 89 + src/adguardhome/safebrowsing.py | 56 + src/adguardhome/safesearch.py | 56 + src/adguardhome/stats.py | 111 ++ tests/test_adguardhome.py | 50 +- tests/test_filtering.py | 7 +- tests/test_parental.py | 5 +- tests/test_querylog.py | 1 + tests/test_safebrowsing.py | 5 +- tests/test_safesearch.py | 5 +- tests/test_stats.py | 24 +- 67 files changed, 3465 insertions(+), 1220 deletions(-) create mode 100644 .deepsource.toml create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/labels.yml create mode 100644 .github/release-drafter.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yaml create mode 100644 .github/workflows/labels.yaml create mode 100644 .github/workflows/linting.yaml create mode 100644 .github/workflows/lock.yaml create mode 100644 .github/workflows/pr-labels.yaml create mode 100644 .github/workflows/release-drafter.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/requirements.txt create mode 100644 .github/workflows/stale.yaml create mode 100644 .github/workflows/tests.yaml create mode 100644 .github/workflows/typing.yaml delete mode 100644 .isort.cfg create mode 100644 .nvmrc create mode 120000 .prettierignore delete mode 100644 MANIFEST.in delete mode 100644 Makefile delete mode 100644 adguardhome/__init__.py delete mode 100644 adguardhome/__version__.py delete mode 100644 adguardhome/filtering.py delete mode 100644 adguardhome/parental.py delete mode 100644 adguardhome/querylog.py delete mode 100644 adguardhome/safebrowsing.py delete mode 100644 adguardhome/safesearch.py delete mode 100644 adguardhome/stats.py create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 poetry.lock delete mode 100644 pylintrc create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 requirements_dev.txt delete mode 100644 requirements_test.txt delete mode 100644 setup.py create mode 100644 src/adguardhome/__init__.py rename {adguardhome => src/adguardhome}/adguardhome.py (50%) rename {adguardhome => src/adguardhome}/exceptions.py (91%) create mode 100644 src/adguardhome/filtering.py create mode 100644 src/adguardhome/parental.py create mode 100644 src/adguardhome/py.typed create mode 100644 src/adguardhome/querylog.py create mode 100644 src/adguardhome/safebrowsing.py create mode 100644 src/adguardhome/safesearch.py create mode 100644 src/adguardhome/stats.py diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 00000000..8a3c96c8 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,18 @@ +version = 1 + +test_patterns = ["tests/**"] + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" + +[[analyzers]] +name = "test-coverage" +enabled = true + +[[analyzers]] +name = "secrets" +enabled = true diff --git a/.flake8 b/.flake8 index d5a9d438..508395d7 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,4 @@ [flake8] -max-line-length=88 -ignore=D202 +max-line-length = 88 +ignore = D202 +per-file-ignores = tests/*:DAR,S101 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..771559ad --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +.github/* @frenck diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..544da8f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +# Problem/Motivation + +> (Why the issue was filed) + +## Expected behavior + +> (What you expected to happen) + +## Actual behavior + +> (What actually happened) + +## Steps to reproduce + +> (How can someone else make/see it happen) + +## Proposed changes + +> (If you have a proposed change, workaround or fix, +> describe the rationale behind it) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..14f7b5bc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +# Proposed Changes + +> (Describe the changes and rationale behind them) + +## Related Issues + +> ([Github link][autolink-references] to related issues or pull requests) + +[autolink-references]: https://help.github.com/articles/autolinked-references-and-urls/ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fdc39438..398624d1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,10 +6,13 @@ updates: schedule: interval: daily time: "06:00" - open-pull-requests-limit: 10 + - package-ecosystem: pip + directory: "/.github/workflows" + schedule: + interval: daily + time: "06:00" - package-ecosystem: "github-actions" directory: "/" schedule: interval: daily time: "06:00" - open-pull-requests-limit: 10 diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..2d0f68ad --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,85 @@ +--- +- name: "breaking-change" + color: ee0701 + description: "A breaking change for existing users." +- name: "bugfix" + color: ee0701 + description: "Inconsistencies or issues which will cause a problem for users or implementors." +- name: "documentation" + color: 0052cc + description: "Solely about the documentation of the project." +- name: "enhancement" + color: 1d76db + description: "Enhancement of the code, not introducing new features." +- name: "refactor" + color: 1d76db + description: "Improvement of existing code, not introducing new features." +- name: "performance" + color: 1d76db + description: "Improving performance, not introducing new features." +- name: "new-feature" + color: 0e8a16 + description: "New features or options." +- name: "maintenance" + color: 2af79e + description: "Generic maintenance tasks." +- name: "ci" + color: 1d76db + description: "Work that improves the continue integration." +- name: "dependencies" + color: 1d76db + description: "Upgrade or downgrade of project dependencies." + +- name: "in-progress" + color: fbca04 + description: "Issue is currently being resolved by a developer." +- name: "stale" + color: fef2c0 + description: "There has not been activity on this issue or PR for quite some time." +- name: "no-stale" + color: fef2c0 + description: "This issue or PR is exempted from the stable bot." + +- name: "security" + color: ee0701 + description: "Marks a security issue that needs to be resolved asap." +- name: "incomplete" + color: fef2c0 + description: "Marks a PR or issue that is missing information." +- name: "invalid" + color: fef2c0 + description: "Marks a PR or issue that is missing information." + +- name: "beginner-friendly" + color: 0e8a16 + description: "Good first issue for people wanting to contribute to the project." +- name: "help-wanted" + color: 0e8a16 + description: "We need some extra helping hands or expertise in order to resolve this." + +- name: "hacktoberfest" + description: "Issues/PRs are participating in the Hacktoberfest." + color: fbca04 +- name: "hacktoberfest-accepted" + description: "Issues/PRs are participating in the Hacktoberfest." + color: fbca04 + +- name: "priority-critical" + color: ee0701 + description: "This should be dealt with ASAP. Not fixing this issue would be a serious error." +- name: "priority-high" + color: b60205 + description: "After critical issues are fixed, these should be dealt with before any further issues." +- name: "priority-medium" + color: 0e8a16 + description: "This issue may be useful, and needs some attention." +- name: "priority-low" + color: e4ea8a + description: "Nice addition, maybe... someday..." + +- name: "major" + color: b60205 + description: "This PR causes a major version bump in the version number." +- name: "minor" + color: 0e8a16 + description: "This PR causes a minor version bump in the version number." diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..cb404ea3 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,57 @@ +--- +name-template: "v$RESOLVED_VERSION" +tag-template: "v$RESOLVED_VERSION" +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" +sort-direction: ascending + +categories: + - title: "๐Ÿšจ Breaking changes" + labels: + - "breaking-change" + - title: "โœจ New features" + labels: + - "new-feature" + - title: "๐Ÿ› Bug fixes" + labels: + - "bugfix" + - title: "๐Ÿš€ Enhancements" + labels: + - "enhancement" + - "refactor" + - "performance" + - title: "๐Ÿงฐ Maintenance" + labels: + - "maintenance" + - "ci" + - title: "๐Ÿ“š Documentation" + labels: + - "documentation" + - title: "โฌ†๏ธ Dependency updates" + labels: + - "dependencies" + +version-resolver: + major: + labels: + - "major" + - "breaking-change" + minor: + labels: + - "minor" + - "new-feature" + patch: + labels: + - "bugfix" + - "chore" + - "ci" + - "dependencies" + - "documentation" + - "enhancement" + - "performance" + - "refactor" + default: patch + +template: | + ## Whatโ€™s changed + + $CHANGES diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 1ef234e2..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: Continuous Integration - -# yamllint disable-line rule:truthy -on: [push, pull_request] - -jobs: - linting: - name: Linting - runs-on: ubuntu-latest - steps: - - name: Checking out code from GitHub - uses: actions/checkout@v2.3.4 - - name: Set up Python 3.7 - uses: actions/setup-python@v2.2.1 - with: - python-version: 3.7 - - name: Install pre-commit - run: | - pip install pre-commit - pre-commit --version - - name: Run pre-commit on all files - run: | - pre-commit run --all-files --show-diff-on-failure - test: - name: Python ${{ matrix.python }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }}-latest - strategy: - matrix: - os: [ubuntu, macos, windows] - python: [3.7, 3.8] - steps: - - name: Checking out code from GitHub - uses: actions/checkout@v2.3.4 - - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2.2.1 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - pip install -r requirements_test.txt - pip install -r requirements.txt - pip list - - name: Pytest with coverage reporting - run: pytest --cov=adguardhome --cov-report=xml - - name: Upload coverage to Codecov - if: matrix.python == 3.7 && matrix.os == 'ubuntu' - uses: codecov/codecov-action@v1.2.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.xml - flags: unittests - name: codecov-umbrella diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 00000000..2a115a7c --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,21 @@ +--- +name: "CodeQL" + +# yamllint disable-line rule:truthy +on: + push: + pull_request: + schedule: + - cron: "30 1 * * 0" + +jobs: + codeql: + name: Scanning + runs-on: ubuntu-latest + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿ— Initialize CodeQL + uses: github/codeql-action/init@v1 + - name: ๐Ÿš€ Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/labels.yaml b/.github/workflows/labels.yaml new file mode 100644 index 00000000..5a6ab37c --- /dev/null +++ b/.github/workflows/labels.yaml @@ -0,0 +1,22 @@ +--- +name: Sync labels + +# yamllint disable-line rule:truthy +on: + push: + branches: + - main + paths: + - .github/labels.yml + +jobs: + labels: + name: โ™ป๏ธ Sync labels + runs-on: ubuntu-latest + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿš€ Run Label Syncer + uses: micnncim/action-label-syncer@v1.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml new file mode 100644 index 00000000..01c70a9e --- /dev/null +++ b/.github/workflows/linting.yaml @@ -0,0 +1,128 @@ +--- +name: Linting + +# yamllint disable-line rule:truthy +on: [push, pull_request] + +jobs: + precommit: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - id: bandit + name: Check with bandit + - id: black + name: Check code style + - id: blacken-docs + name: Check code style in documentation + - id: check-ast + name: Check Python AST + - id: check-case-conflict + name: Check for case conflicts + - id: check-docstring-first + name: Check docstring is first + - id: check-executables-have-shebangs + name: Check that executables have shebangs + - id: check-json + name: Check JSON files + - id: check-merge-conflict + name: Check for merge conflicts + - id: check-symlinks + name: Check for broken symlinks + - id: check-toml + name: Check TOML files + - id: check-xml + name: Check XML files + - id: check-yaml + name: Check YAML files + - id: codespell + name: Check code for common misspellings + - id: debug-statements + name: Debug Statements and imports (Python) + - id: detect-private-key + name: Detect Private Keys + - id: end-of-file-fixer + name: Check End of Files + - id: fix-byte-order-marker + name: Check UTF-8 byte order marker + - id: flake8 + name: Enforcing style guide with flake8 + - id: isort + name: Check imports are sorted + - id: poetry + name: Check pyproject file + - id: prettier + name: Check if code is prettier + - id: pylint + name: Check with pylint + - id: pyupgrade + name: Check for upgradable syntax + - id: trailing-whitespace + name: Trim Trailing Whitespace + - id: vulture + name: Check for unused Python code + - id: yamllint + name: Check YAML style + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿ— Set up Python 3.8 + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: 3.8 + - name: ๐Ÿ— Read .nvmrc + if: ${{ matrix.id == 'prettier' }} + id: nvm + run: echo "##[set-output name=nvmrc;]$(cat .nvmrc)" + - name: ๐Ÿ— Set up Node.js ${{ steps.nvm.outputs.nvmrc }} + if: ${{ matrix.id == 'prettier' }} + uses: actions/setup-node@v2.1.5 + with: + node-version: "${{ steps.nvm.outputs.nvmrc }}" + - name: ๐Ÿ— Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.4 + with: + path: .venv + key: >- + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install Python dependencies + run: poetry install --no-interaction + - name: ๐Ÿ— Get npm cache directory + if: ${{ matrix.id == 'prettier' }} + id: npm-cache + run: | + echo "::set-output name=dir::$(npm config get cache)" + - name: โคต๏ธ Restore cached node modules + if: ${{ matrix.id == 'prettier' }} + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: node-${{ runner.os }}-${{ steps.nvm.outputs.nvmrc }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + node-${{ runner.os }}-${{ steps.nvm.outputs.nvmrc }}- + - name: ๐Ÿ— Install NPM dependencies + if: ${{ matrix.id == 'prettier' }} + run: npm install + - name: ๐Ÿš€ Run pre-commit for ${{ matrix.id }} + run: poetry run pre-commit run ${{ matrix.id }} --all-files diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml new file mode 100644 index 00000000..ff147a20 --- /dev/null +++ b/.github/workflows/lock.yaml @@ -0,0 +1,21 @@ +--- +name: Lock + +# yamllint disable-line rule:truthy +on: + schedule: + - cron: "0 9 * * *" + workflow_dispatch: + +jobs: + lock: + name: ๐Ÿ”’ Lock closed issues and PRs + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2.0.3 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: "30" + issue-lock-reason: "" + pr-lock-inactive-days: "1" + pr-lock-reason: "" diff --git a/.github/workflows/pr-labels.yaml b/.github/workflows/pr-labels.yaml new file mode 100644 index 00000000..42082790 --- /dev/null +++ b/.github/workflows/pr-labels.yaml @@ -0,0 +1,21 @@ +--- +name: PR Labels + +# yamllint disable-line rule:truthy +on: + pull_request: + types: [opened, labeled, unlabeled, synchronize] + +jobs: + pr_labels: + name: Verify + runs-on: ubuntu-latest + steps: + - name: ๐Ÿท Verify PR has a valid label + uses: jesusvasquez333/verify-pr-label-action@v1.4.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + valid-labels: >- + breaking-change, bugfix, documentation, enhancement, + refactor, performance, new-feature, maintenance, ci, dependencies + disable-reviews: true diff --git a/.github/workflows/release-drafter.yaml b/.github/workflows/release-drafter.yaml new file mode 100644 index 00000000..e15b52e0 --- /dev/null +++ b/.github/workflows/release-drafter.yaml @@ -0,0 +1,18 @@ +--- +name: Release Drafter + +# yamllint disable-line rule:truthy +on: + push: + branches: + - main + +jobs: + update_release_draft: + name: โœ๏ธ Draft release + runs-on: ubuntu-latest + steps: + - name: ๐Ÿš€ Run Release Drafter + uses: release-drafter/release-drafter@v5.14.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..f19e8df8 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,61 @@ +--- +name: Release + +# yamllint disable-line rule:truthy +on: + release: + types: + - published + +jobs: + release: + name: Releasing to PyPi + runs-on: ubuntu-latest + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿ— Set up Python 3.8 + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: 3.8 + - name: ๐Ÿ— Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.4 + with: + path: .venv + key: >- + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install dependencies + run: poetry install --no-interaction + - name: ๐Ÿ— Set package version + run: | + version="${{ github.event.release.tag_name }}" + version="${version,,}" + version="${version#v}" + poetry version --no-interaction "${version}" + - name: ๐Ÿ— Build package + run: poetry build --no-interaction + - name: ๐Ÿš€ Publish to PyPi + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: | + poetry config pypi-token.pypi "${PYPI_TOKEN}" + poetry publish --no-interaction diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt new file mode 100644 index 00000000..444ae8c0 --- /dev/null +++ b/.github/workflows/requirements.txt @@ -0,0 +1,2 @@ +pip==21.0.1 +poetry==1.1.5 diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 00000000..e7cfe897 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,40 @@ +--- +name: Stale + +# yamllint disable-line rule:truthy +on: + schedule: + - cron: "0 8 * * *" + workflow_dispatch: + +jobs: + stale: + name: ๐Ÿงน Clean up stale issues and PRs + runs-on: ubuntu-latest + steps: + - name: ๐Ÿš€ Run stale + uses: actions/stale@v3.0.18 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 7 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale,help-wanted" + stale-issue-message: > + There hasn't been any activity on this issue recently, so we + clean up some of the older and inactive issues. + + Please make sure to update to the latest version and + check if that solves the issue. Let us know if that works for you + by leaving a comment ๐Ÿ‘ + + This issue has now been marked as stale and will be closed if no + further activity occurs. Thanks! + stale-pr-label: "stale" + exempt-pr-labels: "no-stale" + stale-pr-message: > + There hasn't been any activity on this pull request recently. This + pull request has been automatically marked as stale because of that + and will be closed if no further activity occurs within 7 days. + Thank you for your contributions. diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..a4537ca3 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,101 @@ +--- +name: Testing + +# yamllint disable-line rule:truthy +on: [push, pull_request] + +jobs: + pytest: + name: Python ${{ matrix.python }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest + strategy: + matrix: + os: [ubuntu, windows, macos] + python: [3.8, 3.9] + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿ— Set up Python ${{ matrix.python }} + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ matrix.python }} + - name: ๐Ÿ— Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + restore-keys: | + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install dependencies + run: poetry install --no-interaction + - name: ๐Ÿš€ Run pytest + run: poetry run pytest --cov adguardhome tests + - name: โฌ†๏ธ Upload coverage artifact + uses: actions/upload-artifact@v2.2.2 + with: + name: coverage-${{ matrix.python }}-${{ matrix.os }} + path: .coverage + + coverage: + runs-on: ubuntu-latest + needs: pytest + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: โฌ‡๏ธ Download coverage data + uses: actions/download-artifact@v2.0.8 + - name: ๐Ÿ— Set up Python 3.9 + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: 3.9 + - name: ๐Ÿ— Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.4 + with: + path: .venv + key: >- + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install dependencies + run: poetry install --no-interaction + - name: ๐Ÿš€ Process coverage results + run: | + poetry run coverage combine coverage*/.coverage* + poetry run coverage xml -i + - name: ๐Ÿš€ Upload coverage report + uses: codecov/codecov-action@v1.2.1 diff --git a/.github/workflows/typing.yaml b/.github/workflows/typing.yaml new file mode 100644 index 00000000..2a79d68b --- /dev/null +++ b/.github/workflows/typing.yaml @@ -0,0 +1,115 @@ +--- +name: Typing + +# yamllint disable-line rule:truthy +on: [push, pull_request] + +jobs: + mypy: + name: mypy on Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.8, 3.9] + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿ— Set up Python ${{ matrix.python }} + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ matrix.python }} + - name: ๐Ÿ— Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + restore-keys: | + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install dependencies + run: poetry install --no-interaction + - name: ๐Ÿš€ Run mypy + run: poetry run mypy examples src tests + + pyright: + name: pyright on Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.8, 3.9] + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: ๐Ÿ— Set up Python ${{ matrix.python }} + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ matrix.python }} + - name: ๐Ÿ— Read .nvmrc + id: nvm + run: echo "##[set-output name=nvmrc;]$(cat .nvmrc)" + - name: ๐Ÿ— Set up Node.js ${{ steps.nvm.outputs.nvmrc }} + uses: actions/setup-node@v2.1.5 + with: + node-version: "${{ steps.nvm.outputs.nvmrc }}" + - name: ๐Ÿ— Get pip cache directory + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: node-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + restore-keys: | + venv-${{ runner.os }}-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install Python dependencies + run: poetry install --no-interaction + - name: ๐Ÿ— Get npm cache directory + id: npm-cache + run: | + echo "::set-output name=dir::$(npm config get cache)" + - name: โคต๏ธ Restore cached node modules + uses: actions/cache@v2.1.4 + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: node-${{ runner.os }}-${{ steps.nvm.outputs.nvmrc }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + node-${{ runner.os }}-${{ steps.nvm.outputs.nvmrc }}- + - name: ๐Ÿ— Install dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry install + npm install + - name: ๐Ÿš€ Run pyright + run: poetry run npm run pyright diff --git a/.gitignore b/.gitignore index c006c80b..4abf6c5e 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,9 @@ target/ # Cookiecutter output/ python_boilerplate/ + +# Node +node_modules/ + +# Deepcode AI +.dccache diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 95291b59..00000000 --- a/.isort.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[settings] -multi_line_output=3 -line_length=88 -include_trailing_comma=True -project=adguardhome diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..8351c193 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +14 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index baf1f20a..90aa5baa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,64 +1,178 @@ --- repos: - - repo: https://github.com/ambv/black - rev: 19.10b0 + - repo: local hooks: + - id: bandit + name: ๐ŸŽฐ Checking using bandit + language: system + types: [python] + entry: poetry run bandit + files: ^src/ + require_serial: true - id: black - args: [--safe, --quiet, --target-version, py35] - - repo: https://github.com/asottile/blacken-docs - rev: v1.4.0 - hooks: + name: โ˜•๏ธ Format using black + language: system + types: [python] + entry: poetry run black + require_serial: true - id: blacken-docs - additional_dependencies: [black==19.3b0] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-merge-conflict - - id: debug-statements + name: โ˜•๏ธ Format documentation examples using black + language: system + files: '\.(rst|md|markdown|py|tex)$' + entry: poetry run blacken-docs + require_serial: true + - id: check-ast + name: ๐Ÿ Check Python AST + language: system + types: [python] + entry: poetry run check-ast + - id: check-case-conflict + name: ๐Ÿ”  Check for case conflicts + language: system + entry: poetry run check-case-conflict - id: check-docstring-first + name: โ„น๏ธ Check docstring is first + language: system + types: [python] + entry: poetry run check-docstring-first + - id: check-executables-have-shebangs + name: ๐Ÿง Check that executables have shebangs + language: system + types: [text, executable] + entry: poetry run check-executables-have-shebangs + stages: [commit, push, manual] - id: check-json + name: ๏ฝ› Check JSON files + language: system + types: [json] + entry: poetry run check-json + - id: check-merge-conflict + name: ๐Ÿ’ฅ Check for merge conflicts + language: system + types: [text] + entry: poetry run check-merge-conflict + - id: check-symlinks + name: ๐Ÿ”— Check for broken symlinks + language: system + types: [symlink] + entry: poetry run check-symlinks + - id: check-toml + name: โœ… Check TOML files + language: system + types: [toml] + entry: poetry run check-toml + - id: check-xml + name: โœ… Check XML files + entry: check-xml + language: system + types: [xml] - id: check-yaml - - id: requirements-txt-fixer - - id: check-byte-order-marker - - id: check-case-conflict - - id: check-executables-have-shebangs - - id: fix-encoding-pragma - args: ["--remove"] - - id: check-ast + name: โœ… Check YAML files + language: system + types: [yaml] + entry: poetry run check-yaml + - id: codespell + name: โœ… Check code for common misspellings + language: system + types: [text] + exclude: ^poetry\.lock$ + entry: poetry run codespell + - id: debug-statements + name: ๐Ÿชต Debug Statements and imports (Python) + language: system + types: [python] + entry: poetry run debug-statement-hook - id: detect-private-key - - id: forbid-new-submodules - - repo: https://github.com/pre-commit/pre-commit - rev: v1.21.0 - hooks: - - id: validate_manifest - - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v1.4.4 - hooks: - - id: autopep8 - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 - hooks: + name: ๐Ÿ•ต๏ธ Detect Private Keys + language: system + types: [text] + entry: poetry run detect-private-key + - id: end-of-file-fixer + name: โฎ Fix End of Files + language: system + types: [text] + entry: poetry run end-of-file-fixer + stages: [commit, push, manual] + - id: fix-byte-order-marker + name: ๐Ÿš Fix UTF-8 byte order marker + language: system + types: [text] + entry: poetry run fix-byte-order-marker + - id: flake8 + name: ๐Ÿ‘” Enforcing style guide with flake8 + language: system + types: [python] + entry: poetry run flake8 + require_serial: true - id: isort - - repo: https://github.com/pre-commit/mirrors-pylint - rev: v2.4.4 - hooks: - - id: pylint - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.761 - hooks: + name: ๐Ÿ”€ Sort all imports with isort + language: system + types: [python] + entry: poetry run isort - id: mypy - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.9 - hooks: - - id: flake8 - additional_dependencies: ["flake8-docstrings"] - - repo: https://github.com/adrienverge/yamllint.git - sha: v1.20.0 - hooks: - - id: yamllint - - repo: https://github.com/asottile/pyupgrade - rev: v1.25.2 - hooks: + name: ๐Ÿ†Ž Static type checking using mypy + language: system + types: [python] + entry: poetry run mypy + require_serial: true + - id: no-commit-to-branch + name: ๐Ÿ›‘ Don't commit to main branch + language: system + entry: poetry run no-commit-to-branch + pass_filenames: false + always_run: true + args: + - --branch=main + - id: poetry + name: ๐Ÿ“œ Check pyproject with Poetry + language: system + entry: poetry check + pass_filenames: false + always_run: true + - id: prettier + name: ๐Ÿ’„ Ensuring files are prettier + language: system + types: [text] + entry: npm run prettier + pass_filenames: false + - id: pylint + name: ๐ŸŒŸ Starring code with pylint + language: system + types: [python] + entry: poetry run pylint + - id: pyright + name: ๐Ÿ†Ž Static type checking using pyright + language: system + types: [python] + entry: npm run pyright + pass_filenames: false + - id: pytest + name: ๐Ÿงช Running tests and test coverage with pytest + language: system + types: [python] + entry: poetry run pytest + pass_filenames: false - id: pyupgrade + name: ๐Ÿ†™ Checking for upgradable syntax with pyupgrade + language: system + types: [python] + entry: poetry run pyupgrade + args: [--py36-plus] + - id: trailing-whitespace + name: โœ„ Trim Trailing Whitespace + language: system + types: [text] + entry: poetry run trailing-whitespace-fixer + stages: [commit, push, manual] + - id: vulture + name: ๐Ÿ” Find unused Python code with Vulture + language: system + types: [python] + entry: poetry run vulture + pass_filenames: false + require_serial: true + - id: yamllint + name: ๐ŸŽ— Check YAML files with yamllint + language: system + types: [yaml] + entry: poetry run yamllint diff --git a/.prettierignore b/.prettierignore new file mode 120000 index 00000000..3e4e48b0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0ac232b8..8b146f41 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,74 +1,133 @@ -# Code of conduct +# Contributor Covenant Code of Conduct -## Our pledge +## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. -## Our standards +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. -Examples of behavior that contributes to creating a positive environment -include: +## Our Standards -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members +Examples of behavior that contributes to a positive environment for our +community include: -Examples of unacceptable behavior by participants include: +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community -- The use of sexualized language or imagery and unwelcome sexual attention - or advances -- Trolling, insulting/derogatory comments, and personal or political attacks +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment -- Publishing others' private information, such as a physical or - electronic address, without explicit permission -- Other conduct which could reasonably be considered inappropriate - in a professional setting +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting -## Our responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project lead at frenck@addons.community. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project lead is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at +frenck@frenck.dev. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/LICENSE.md b/LICENSE.md index 9344a088..e9a1271a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2019-2020 Franck Nijhof +Copyright (c) 2019-2021 Franck Nijhof Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 8106cc68..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include README.md -include LICENSE.md -graft adguardhome -recursive-exclude * *.py[co] diff --git a/Makefile b/Makefile deleted file mode 100644 index 4cc5656b..00000000 --- a/Makefile +++ /dev/null @@ -1,114 +0,0 @@ -.DEFAULT_GOAL := help -export VENV := $(abspath venv) -export PATH := ${VENV}/bin:${PATH} - -define BROWSER_PYSCRIPT -import os, webbrowser, sys - -try: - from urllib import pathname2url -except: - from urllib.request import pathname2url - -webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) -endef -export BROWSER_PYSCRIPT - -BROWSER := python -c "$$BROWSER_PYSCRIPT" - -.PHONY: help -help: ## Shows this message. - @echo "Asynchronous Python client for the AdGuard Home API."; \ - echo; \ - echo "Usage:"; \ - awk -F ':|##' '/^[^\t].+?:.*?##/ {\ - printf "\033[36m make %-30s\033[0m %s\n", $$1, $$NF \ - }' $(MAKEFILE_LIST) - -.PHONY: dev -dev: install-dev install ## Set up a development environment. - -.PHONY: black -black: lint-black - -.PHONY: lint -lint: lint-black lint-flake8 lint-pylint lint-mypy ## Run all linters. - -.PHONY: lint-black -lint-black: ## Run linting using black & blacken-docs. - black --safe --target-version py35 adguardhome tests examples; \ - blacken-docs --target-version py35 - -.PHONY: lint-flake8 -lint-flake8: ## Run linting using flake8 (pycodestyle/pydocstyle). - flake8 adguardhome - -.PHONY: lint-pylint -lint-pylint: ## Run linting using PyLint. - pylint adguardhome - -.PHONY: lint-mypy -lint-mypy: ## Run linting using MyPy. - mypy -p adguardhome - -.PHONY: test -test: ## Run tests quickly with the default Python. - pytest --cov-report html --cov-report term --cov=adguardhome .; - -.PHONY: coverage -coverage: test ## Check code coverage quickly with the default Python. - $(BROWSER) htmlcov/index.html - -.PHONY: install -install: clean ## Install the package to the active Python's site-packages. - pip install -Ur requirements.txt; \ - pip install -e .; - -.PHONY: clean clean-all -clean: clean-build clean-pyc clean-test ## Removes build, test, coverage and Python artifacts. -clean-all: clean-build clean-pyc clean-test clean-venv ## Removes all venv, build, test, coverage and Python artifacts. - -.PHONY: clean-build -clean-build: ## Removes build artifacts. - rm -fr build/; \ - rm -fr dist/; \ - rm -fr .eggs/; \ - find . -name '*.egg-info' -exec rm -fr {} +; \ - find . -name '*.egg' -exec rm -fr {} +; - -.PHONY: clean-pyc -clean-pyc: ## Removes Python file artifacts. - find . -name '*.pyc' -delete; \ - find . -name '*.pyo' -delete; \ - find . -name '*~' -delete; \ - find . -name '__pycache__' -exec rm -fr {} +; - -.PHONY: clean-test -clean-test: ## Removes test and coverage artifacts. - rm -f .coverage; \ - rm -fr htmlcov/; \ - rm -fr .pytest_cache; - -.PHONY: clean-venv -clean-venv: ## Removes Python virtual environment artifacts. - rm -fr venv/; - -.PHONY: dist -dist: clean ## Builds source and wheel package. - python setup.py sdist; \ - python setup.py bdist_wheel; \ - ls -l dist; - -.PHONY: release -release: ## Release build on PyP - twine upload dist/* - -.PHONY: venv -venv: clean-venv ## Create Python venv environment. - python3 -m venv venv; - -.PHONY: install-dev -install-dev: clean - pip install -Ur requirements_dev.txt; \ - pip install -Ur requirements_test.txt; \ - pre-commit install; diff --git a/README.md b/README.md index 8e169a2f..c2f9f221 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Python: AdGuard Home API Client [![GitHub Release][releases-shield]][releases] +[![Python Versions][python-versions-shield]][pypi] ![Project Stage][project-stage-shield] ![Project Maintenance][maintenance-shield] [![License][license-shield]](LICENSE.md) @@ -8,6 +9,7 @@ [![Build Status][build-shield]][build] [![Code Coverage][codecov-shield]][codecov] [![Code Quality][code-quality-shield]][code-quality] +[![Deepcode.ai][deepcode-shield]][deepcode] [![Sponsor Frenck via GitHub Sponsors][github-sponsors-shield]][github-sponsors] @@ -65,12 +67,12 @@ functionality. The format of the log is based on [Keep a Changelog][keepchangelog]. Releases are based on [Semantic Versioning][semver], and use the format -of ``MAJOR.MINOR.PATCH``. In a nutshell, the version will be incremented +of `MAJOR.MINOR.PATCH`. In a nutshell, the version will be incremented based on the following: -- ``MAJOR``: Incompatible or major changes. -- ``MINOR``: Backwards-compatible new features and enhancements. -- ``PATCH``: Backwards-compatible bugfixes and package updates. +- `MAJOR`: Incompatible or major changes. +- `MINOR`: Backwards-compatible new features and enhancements. +- `PATCH`: Backwards-compatible bugfixes and package updates. ## Contributing @@ -84,42 +86,35 @@ Thank you for being involved! :heart_eyes: ## Setting up development environment -In case you'd like to contribute, a `Makefile` has been included to ensure a -quick start. +This Python project is fully managed using the [Poetry][poetry] dependency +manager. But also relies on the use of NodeJS for certain checks during +development. + +You need at least: + +- Python 3.7+ +- [Poetry][poetry-install] +- NodeJS 12+ (including NPM) + +To install all packages, including all development requirements: ```bash -make venv -source ./venv/bin/activate -make dev +npm install +poetry install ``` -Now you can start developing, run `make` without arguments to get an overview -of all make goals that are available (including description): +As this repository uses the [pre-commit][pre-commit] framework, all changes +are linted and tested with each commit. You can run all checks and tests +manually, using the following command: ```bash -$ make -Asynchronous Python client for the AdGuard Home API. +poetry run pre-commit run --all-files +``` + +To run just the Python tests: -Usage: - make help Shows this message. - make dev Set up a development environment. - make lint Run all linters. - make lint-black Run linting using black & blacken-docs. - make lint-flake8 Run linting using flake8 (pycodestyle/pydocstyle). - make lint-pylint Run linting using PyLint. - make lint-mypy Run linting using MyPy. - make test Run tests quickly with the default Python. - make coverage Check code coverage quickly with the default Python. - make install Install the package to the active Python's site-packages. - make clean Removes build, test, coverage and Python artifacts. - make clean-all Removes all venv, build, test, coverage and Python artifacts. - make clean-build Removes build artifacts. - make clean-pyc Removes Python file artifacts. - make clean-test Removes test and coverage artifacts. - make clean-venv Removes Python virtual environment artifacts. - make dist Builds source and wheel package. - make release Release build on PyP - make venv Create Python venv environment. +```bash +poetry run pytest ``` ## Authors & contributors @@ -133,7 +128,7 @@ check [the contributor's page][contributors]. MIT License -Copyright (c) 2019-2020 Franck Nijhof +Copyright (c) 2019-2021 Franck Nijhof Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -153,13 +148,15 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -[build-shield]: https://github.com/frenck/python-adguardhome/workflows/Continuous%20Integration/badge.svg -[build]: https://github.com/frenck/python-adguardhome/actions +[build-shield]: https://github.com/frenck/python-adguardhome/actions/workflows/tests.yaml/badge.svg +[build]: https://github.com/frenck/python-adguardhome/actions/workflows/tests.yaml [code-quality-shield]: https://img.shields.io/lgtm/grade/python/g/frenck/python-adguardhome.svg?logo=lgtm&logoWidth=18 [code-quality]: https://lgtm.com/projects/g/frenck/python-adguardhome/context:python [codecov-shield]: https://codecov.io/gh/frenck/python-adguardhome/branch/master/graph/badge.svg [codecov]: https://codecov.io/gh/frenck/python-adguardhome [contributors]: https://github.com/frenck/python-adguardhome/graphs/contributors +[deepcode-shield]: https://www.deepcode.ai/api/gh/badge?key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwbGF0Zm9ybTEiOiJnaCIsIm93bmVyMSI6ImZyZW5jayIsInJlcG8xIjoicHl0aG9uLWVsZ2F0byIsImluY2x1ZGVMaW50IjpmYWxzZSwiYXV0aG9ySWQiOjI4MDU1LCJpYXQiOjE2MTUxODgzODh9.hJsD6PTw8K8bnTmHUzroQi7XkXRi46bdt-oMqx2zXj0 +[deepcode]: https://www.deepcode.ai/app/gh/frenck/python-adguardhome/_/dashboard?utm_content=gh%2Ffrenck%2Fpython-adguardhome [frenck]: https://github.com/frenck [github-sponsors-shield]: https://frenck.dev/wp-content/uploads/2019/12/github_sponsor.png [github-sponsors]: https://github.com/sponsors/frenck @@ -172,3 +169,8 @@ SOFTWARE. [releases-shield]: https://img.shields.io/github/release/frenck/python-adguardhome.svg [releases]: https://github.com/frenck/python-adguardhome/releases [semver]: http://semver.org/spec/v2.0.0.html +[poetry-install]: https://python-poetry.org/docs/#installation +[poetry]: https://python-poetry.org +[pre-commit]: https://pre-commit.com/ +[pypi]: https://pypi.org/project/adguardhome/ +[python-versions-shield]: https://img.shields.io/pypi/pyversions/adguardhome diff --git a/adguardhome/__init__.py b/adguardhome/__init__.py deleted file mode 100644 index 467e5d4c..00000000 --- a/adguardhome/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from .adguardhome import ( # noqa - AdGuardHome, - AdGuardHomeConnectionError, - AdGuardHomeError, -) diff --git a/adguardhome/__version__.py b/adguardhome/__version__.py deleted file mode 100644 index 95193e0a..00000000 --- a/adguardhome/__version__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -__version__ = "0.4.2" diff --git a/adguardhome/filtering.py b/adguardhome/filtering.py deleted file mode 100644 index f8a0bae4..00000000 --- a/adguardhome/filtering.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from typing import Optional - -from .exceptions import AdGuardHomeError - - -class AdGuardHomeFiltering: - """Controls AdGuard Home filtering. Blocks domains.""" - - def __init__(self, adguard) -> None: - """Initialize object.""" - self._adguard = adguard - - async def _config( - self, enabled: Optional[bool] = None, interval: Optional[int] = None - ): - """Configure filtering on AdGuard Home.""" - if enabled is None: - enabled = await self.enabled() - if interval is None: - interval = await self.interval() - await self._adguard._request( - "filtering/config", - method="POST", - json_data={"enabled": enabled, "interval": interval}, - ) - - async def enabled(self) -> bool: - """Return if AdGuard Home filtering is enabled or not.""" - response = await self._adguard._request("filtering/status") - return response["enabled"] - - async def enable(self) -> None: - """Enable AdGuard Home filtering.""" - try: - await self._config(enabled=True) - except AdGuardHomeError as exception: - raise AdGuardHomeError("Enabling AdGuard Home filtering failed", exception) - - async def disable(self) -> None: - """Disable AdGuard Home filtering.""" - try: - await self._config(enabled=False) - except AdGuardHomeError as exception: - raise AdGuardHomeError("Disabling AdGuard Home filtering failed", exception) - - async def interval(self, interval: Optional[int] = None) -> int: - """Return or set the time period to keep query log data.""" - if interval: - await self._config(interval=interval) - return interval - - response = await self._adguard._request("filtering/status") - return response["interval"] - - async def rules_count(self) -> int: - """Return the number of rules loaded.""" - response = await self._adguard._request("filtering/status") - count = 0 - for filt in response["filters"]: - count += filt["rules_count"] - return count - - async def add_url(self, name: str, url: str) -> None: - """Add a new filter subscription to AdGuard Home.""" - response = await self._adguard._request( - "filtering/add_url", method="POST", json_data={"name": name, "url": url} - ) - if not response.startswith("OK"): - raise AdGuardHomeError( - "Failed adding URL to AdGuard Home filter", {"response": response} - ) - - async def remove_url(self, url: str) -> None: - """Remove a new filter subscription from AdGuard Home.""" - response = await self._adguard._request( - "filtering/remove_url", method="POST", json_data={"url": url} - ) - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Failed removing URL from AdGuard Home filter", {"response": response} - ) - - async def enable_url(self, url: str) -> None: - """Enable a filter subscription in AdGuard Home.""" - try: - await self._adguard._request( - "filtering/set_url", - method="POST", - json_data={"url": url, "enabled": True}, - ) - except AdGuardHomeError as exception: - raise AdGuardHomeError( - "Failed enabling URL on AdGuard Home filter", exception - ) - - async def disable_url(self, url: str) -> None: - """Disable a filter subscription in AdGuard Home.""" - try: - await self._adguard._request( - "filtering/set_url", - method="POST", - json_data={"url": url, "enabled": False}, - ) - except AdGuardHomeError as exception: - raise AdGuardHomeError( - "Failed disabling URL on AdGuard Home filter", exception - ) - - async def refresh(self, force=False) -> None: - """Reload filtering subscriptions from URLs specified in AdGuard Home.""" - force = "true" if force else "false" - response = await self._adguard._request( - "filtering/refresh", method="POST", params={"force": force} - ) - if not response.startswith("OK"): - raise AdGuardHomeError( - "Failed refreshing filter URLs in AdGuard Home", {"response": response} - ) diff --git a/adguardhome/parental.py b/adguardhome/parental.py deleted file mode 100644 index fa70c86b..00000000 --- a/adguardhome/parental.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from .exceptions import AdGuardHomeError - - -class AdGuardHomeParental: - """Controls AdGuard Home parental control.""" - - def __init__(self, adguard): - """Initialize object.""" - self._adguard = adguard - - async def enabled(self) -> bool: - """Return if AdGuard Home parental control is enabled or not .""" - response = await self._adguard._request("parental/status") - return response["enabled"] - - async def enable(self) -> None: - """Enable AdGuard Home parental control.""" - response = await self._adguard._request( - "parental/enable", method="POST", data="sensitivity=TEEN" - ) - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Enabling AdGuard Home parental control failed", {"response": response} - ) - - async def disable(self) -> None: - """Disable AdGuard Home parental control.""" - response = await self._adguard._request("parental/disable", method="POST") - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Disabling AdGuard Home parental control failed", {"response": response} - ) diff --git a/adguardhome/querylog.py b/adguardhome/querylog.py deleted file mode 100644 index 79944866..00000000 --- a/adguardhome/querylog.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from typing import Optional - -from .exceptions import AdGuardHomeError - - -class AdGuardHomeQueryLog: - """Controls AdGuard Home query log.""" - - def __init__(self, adguard) -> None: - """Initialize object.""" - self._adguard = adguard - - async def _config( - self, enabled: Optional[bool] = None, interval: Optional[int] = None - ): - """Configure query log on AdGuard Home.""" - if enabled is None: - enabled = await self.enabled() - if interval is None: - interval = await self.interval() - await self._adguard._request( - "querylog_config", - method="POST", - json_data={"enabled": enabled, "interval": interval}, - ) - - async def enabled(self) -> bool: - """Return if AdGuard Home query log is enabled or not.""" - response = await self._adguard._request("querylog_info") - return response["enabled"] - - async def enable(self) -> None: - """Enable AdGuard Home query log.""" - try: - await self._config(enabled=True) - except AdGuardHomeError as exception: - raise AdGuardHomeError("Enabling AdGuard Home query log failed", exception) - - async def interval(self, interval: Optional[int] = None) -> int: - """Return or set the time period to keep query log data.""" - if interval: - await self._config(interval=interval) - return interval - - response = await self._adguard._request("querylog_info") - return response["interval"] - - async def disable(self) -> None: - """Disable AdGuard Home query log.""" - try: - await self._config(enabled=False) - except AdGuardHomeError as exception: - raise AdGuardHomeError("Disabling AdGuard Home query log failed", exception) diff --git a/adguardhome/safebrowsing.py b/adguardhome/safebrowsing.py deleted file mode 100644 index cef1a26e..00000000 --- a/adguardhome/safebrowsing.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from .exceptions import AdGuardHomeError - - -class AdGuardHomeSafeBrowsing: - """Controls AdGuard Home browsing security.""" - - def __init__(self, adguard): - """Initialize object.""" - self._adguard = adguard - - async def enabled(self) -> bool: - """Return if AdGuard Home browsing security is enabled or not.""" - response = await self._adguard._request("safebrowsing/status") - return response["enabled"] - - async def enable(self) -> None: - """Enable AdGuard Home browsing security.""" - response = await self._adguard._request("safebrowsing/enable", method="POST") - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Enabling AdGuard Home safe browsing failed", {"response": response} - ) - - async def disable(self) -> None: - """Disable AdGuard Home browsing security.""" - response = await self._adguard._request("safebrowsing/disable", method="POST") - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Disabling AdGuard Home safe browsing failed", {"response": response} - ) diff --git a/adguardhome/safesearch.py b/adguardhome/safesearch.py deleted file mode 100644 index 15e9065f..00000000 --- a/adguardhome/safesearch.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from .exceptions import AdGuardHomeError - - -class AdGuardHomeSafeSearch: - """Controls AdGuard Home safe search enforcing.""" - - def __init__(self, adguard): - """Initialize object.""" - self._adguard = adguard - - async def enabled(self) -> bool: - """Return if AdGuard Home safe search enforcing is enabled or not.""" - response = await self._adguard._request("safesearch/status") - return response["enabled"] - - async def enable(self) -> None: - """Enable AdGuard Home safe search enforcing.""" - response = await self._adguard._request("safesearch/enable", method="POST") - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Enabling AdGuard Home safe search failed", {"response": response} - ) - - async def disable(self) -> None: - """Disable AdGuard Home safe search enforcing.""" - response = await self._adguard._request("safesearch/disable", method="POST") - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Disabling AdGuard Home safe search failed", {"response": response} - ) diff --git a/adguardhome/stats.py b/adguardhome/stats.py deleted file mode 100644 index 9bf8f5c6..00000000 --- a/adguardhome/stats.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Asynchronous Python client for the AdGuard Home API.""" - -from .exceptions import AdGuardHomeError - - -class AdGuardHomeStats: - """Provides stats of AdGuard Home.""" - - def __init__(self, adguard): - """Initialize object.""" - self._adguard = adguard - - async def dns_queries(self) -> int: - """Return number of DNS queries.""" - response = await self._adguard._request("stats") - return response["num_dns_queries"] - - async def blocked_filtering(self) -> int: - """Return number of blocked DNS queries.""" - response = await self._adguard._request("stats") - return response["num_blocked_filtering"] - - async def blocked_percentage(self) -> float: - """Return the blocked percentage ratio of DNS queries.""" - response = await self._adguard._request("stats") - if not response["num_dns_queries"]: - return 0.0 - return (response["num_blocked_filtering"] / response["num_dns_queries"]) * 100.0 - - async def replaced_safebrowsing(self) -> int: - """Return number of blocked pages by safe browsing.""" - response = await self._adguard._request("stats") - return response["num_replaced_safebrowsing"] - - async def replaced_parental(self) -> int: - """Return number of blocked pages by parental control.""" - response = await self._adguard._request("stats") - return response["num_replaced_parental"] - - async def replaced_safesearch(self) -> int: - """Return number of enforced safe searches.""" - response = await self._adguard._request("stats") - return response["num_replaced_safesearch"] - - async def avg_processing_time(self) -> float: - """Return avarage processing time of DNS queries (in ms).""" - response = await self._adguard._request("stats") - return round(response["avg_processing_time"] * 1000, 2) - - async def period(self) -> int: - """Return the time period to keep data (in days).""" - response = await self._adguard._request("stats_info") - return response["interval"] - - async def reset(self) -> None: - """Reset all stats.""" - response = await self._adguard._request("stats_reset", method="POST") - if response.rstrip() != "OK": - raise AdGuardHomeError( - "Resetting AdGuard Home stats failed", {"response": response} - ) diff --git a/examples/stats.py b/examples/stats.py index d0ced64e..7876b923 100644 --- a/examples/stats.py +++ b/examples/stats.py @@ -16,7 +16,7 @@ async def main(): print("Stats period:", period) result = await adguard.stats.avg_processing_time() - print("Avarage processing time per query in ms:", result) + print("Average processing time per query in ms:", result) result = await adguard.stats.dns_queries() print("DNS queries:", result) diff --git a/examples/status.py b/examples/status.py index 4a0ad771..56fac0f3 100644 --- a/examples/status.py +++ b/examples/status.py @@ -8,7 +8,7 @@ async def main(): """Show example how to get status of your AdGuard Home instance.""" - async with AdGuardHome("192.168.1.2") as adguard: + async with AdGuardHome(host="192.168.1.2") as adguard: version = await adguard.version() print("AdGuard version:", version) diff --git a/mypi.ini b/mypi.ini index 15620967..edab2910 100644 --- a/mypi.ini +++ b/mypi.ini @@ -2,7 +2,7 @@ # Specify the target platform details in config, so your developers are # free to run mypy on Windows, Linux, or macOS and get consistent # results. -python_version=3.5 +python_version=3.7 platform=linux # flake8-mypy expects the two following for sensible formatting @@ -16,20 +16,23 @@ ignore_missing_imports=True # be strict check_untyped_defs=True +disallow_any_generics=True +disallow_incomplete_defs=True +disallow_subclassing_any=True disallow_untyped_calls=True disallow_untyped_defs=True +disallow-untyped-decorators=True no_implicit_optional=True +no-implicit-reexport=True strict_equality=True strict_optional=True warn_incomplete_stub=True warn_no_return=True warn_redundant_casts=True -warn_redundant_casts=True -warn_return_any=True warn_return_any=True warn_unused_configs=True warn_unused_ignores=True -warn_unused_ignores=True +warn-return-any=True # No incremental mode cache_dir=/dev/null diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f5c100a3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "adguardhome", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, + "pyright": { + "version": "1.1.118", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.118.tgz", + "integrity": "sha512-nUBcMqJqzcXbNmXPA3BLa5E77lG+APARhBbY0d4q2KGs3Od9FR6YTABK6sUq3O1rVQf4MScz8ji4KbEBsD8FNg==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..38712bb1 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "adguardhome", + "version": "0.0.0", + "private": true, + "description": "AdGuard Home API Client", + "scripts": { + "pyright": "pyright", + "prettier": "prettier --write **/*.{json,js,md,yml,yaml} *.{json,js,md,yml,yaml}" + }, + "author": "Franck Nijhof ", + "license": "MIT", + "devDependencies": { + "prettier": "^2.2.1", + "pyright": "^1.1.108" + } +} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..a92b9443 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1499 @@ +[[package]] +name = "aiohttp" +version = "3.7.4.post0" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<5.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "aresponses" +version = "2.1.4" +description = "Asyncio response mocking. Similar to the responses library used for 'requests'" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiohttp = ">=3.1.0,<4.0.0" +pytest-asyncio = "*" + +[[package]] +name = "astor" +version = "0.8.1" +description = "Read/rewrite/write Python ASTs" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[[package]] +name = "astroid" +version = "2.5.1" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = ">=1.11,<1.13" + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "bandit" +version = "1.7.0" +description = "Security oriented static analyser for python code." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +GitPython = ">=1.0.1" +PyYAML = ">=5.3.1" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "blacken-docs" +version = "1.10.0" +description = "Run `black` on python code blocks in documentation files" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +black = ">=19.3b0" + +[[package]] +name = "cfgv" +version = "3.2.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "codespell" +version = "2.0.0" +description = "Codespell" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"] +hard-encoding-detection = ["chardet"] + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.dependencies] +toml = {version = "*", optional = true, markers = "extra == \"toml\""} + +[package.extras] +toml = ["toml"] + +[[package]] +name = "darglint" +version = "1.7.0" +description = "A utility for ensuring Google-style docstrings stay up to date with the source code." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "distlib" +version = "0.3.1" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "dparse" +version = "0.5.1" +description = "A parser for Python dependency files" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +packaging = "*" +pyyaml = "*" +toml = "*" + +[package.extras] +pipenv = ["pipenv"] + +[[package]] +name = "eradicate" +version = "2.0.0" +description = "Removes commented-out code." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +name = "flake8-bandit" +version = "2.1.2" +description = "Automated security testing with bandit and flake8." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +bandit = "*" +flake8 = "*" +flake8-polyfill = "*" +pycodestyle = "*" + +[[package]] +name = "flake8-bugbear" +version = "21.3.1" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=3.0.0" + +[package.extras] +dev = ["coverage", "black", "hypothesis", "hypothesmith"] + +[[package]] +name = "flake8-builtins" +version = "1.5.3" +description = "Check for python builtins being used as variables or parameters." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +flake8 = "*" + +[package.extras] +test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] + +[[package]] +name = "flake8-comprehensions" +version = "3.3.1" +description = "A flake8 plugin to help you write better list/set/dict comprehensions." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +flake8 = ">=3.0,<3.2.0 || >3.2.0,<4" + +[[package]] +name = "flake8-docstrings" +version = "1.5.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +flake8 = ">=3" +pydocstyle = ">=2.1" + +[[package]] +name = "flake8-eradicate" +version = "1.0.0" +description = "Flake8 plugin to find commented out code" +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +attrs = "*" +eradicate = ">=2.0,<3.0" +flake8 = ">=3.5,<4.0" + +[[package]] +name = "flake8-markdown" +version = "0.2.0" +description = "Lints Python code blocks in Markdown files using flake8" +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +flake8 = ">=3.7,<4.0" + +[[package]] +name = "flake8-polyfill" +version = "1.0.2" +description = "Polyfill package for Flake8 plugins" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +flake8 = "*" + +[[package]] +name = "flake8-simplify" +version = "0.14.0" +description = "flake8 plugin which checks for code that can be simpified" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +astor = ">=0.1" +flake8 = ">=3.7" + +[[package]] +name = "gitdb" +version = "4.0.5" +description = "Git Object Database" +category = "dev" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +smmap = ">=3.0.1,<4" + +[[package]] +name = "gitpython" +version = "3.1.14" +description = "Python Git Library" +category = "dev" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[[package]] +name = "identify" +version = "2.1.0" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.extras] +license = ["editdistance"] + +[[package]] +name = "idna" +version = "3.1" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.4" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.7.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.5.2" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "multidict" +version = "5.1.0" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mypy" +version = "0.812" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.5.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pbr" +version = "5.5.1" +description = "Python Build Reasonableness" +category = "dev" +optional = false +python-versions = ">=2.6" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "pre-commit" +version = "2.11.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "pre-commit-hooks" +version = "3.4.0" +description = "Some out-of-the-box hooks for pre-commit." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +"ruamel.yaml" = ">=0.15" +toml = "*" + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pydocstyle" +version = "5.1.1" +description = "Python docstring style checker" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +snowballstemmer = "*" + +[[package]] +name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pylint" +version = "2.7.2" +description = "python code static checker" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +astroid = ">=2.5.1,<2.6" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +toml = ">=0.7.1" + +[package.extras] +docs = ["sphinx (==3.5.1)", "python-docs-theme (==2020.12)"] + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.14.0" +description = "Pytest support for asyncio." +category = "dev" +optional = false +python-versions = ">= 3.5" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "2.11.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pyupgrade" +version = "2.10.0" +description = "A tool to automatically upgrade syntax for newer versions." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +tokenize-rt = ">=3.2.0" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.15.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "ruamel.yaml" +version = "0.16.13" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.10\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel.yaml.clib" +version = "0.2.2" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "safety" +version = "1.10.3" +description = "Checks installed dependencies for known vulnerabilities." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +Click = ">=6.0" +dparse = ">=0.5.1" +packaging = "*" +requests = "*" + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "smmap" +version = "3.0.5" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "snowballstemmer" +version = "2.1.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "stevedore" +version = "3.3.0" +description = "Manage dynamic plugins for Python applications" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "tokenize-rt" +version = "4.1.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "typed-ast" +version = "1.4.2" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "virtualenv" +version = "20.4.2" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +name = "vulture" +version = "2.3" +description = "Find dead code" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +toml = "*" + +[[package]] +name = "wrapt" +version = "1.12.1" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "yamllint" +version = "1.26.0" +description = "A linter for YAML files." +category = "dev" +optional = false +python-versions = ">=3.5.*" + +[package.dependencies] +pathspec = ">=0.5.3" +pyyaml = "*" + +[[package]] +name = "yarl" +version = "1.6.3" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "f4255eb95892d09555232ab37d0c150f418d5ff7ae86d087afed1a85ab601059" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, + {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, +] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +aresponses = [ + {file = "aresponses-2.1.4-py3-none-any.whl", hash = "sha256:2a5a100c9b39e559bf55c26cc837a8ce64ab160ee086afa01ee9c4ef07f245db"}, + {file = "aresponses-2.1.4.tar.gz", hash = "sha256:39674af90700f1bfe2c7c9049cd8116f5c10d34d2e2427fd744b88d9e8644c94"}, +] +astor = [ + {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, + {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, +] +astroid = [ + {file = "astroid-2.5.1-py3-none-any.whl", hash = "sha256:21d735aab248253531bb0f1e1e6d068f0ee23533e18ae8a6171ff892b98297cf"}, + {file = "astroid-2.5.1.tar.gz", hash = "sha256:cfc35498ee64017be059ceffab0a25bedf7548ab76f2bea691c5565896e7128d"}, +] +async-timeout = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +bandit = [ + {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, + {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +blacken-docs = [ + {file = "blacken_docs-1.10.0-py2.py3-none-any.whl", hash = "sha256:149197a0b17e83121fc10aca9eda1417728fdccebde930a6722f97d87ed30f4b"}, + {file = "blacken_docs-1.10.0.tar.gz", hash = "sha256:e2121c95bf2f8a3ebb3110776d276f850f63b8e5753773ba2b4d0f415d862f23"}, +] +cfgv = [ + {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, + {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +codespell = [ + {file = "codespell-2.0.0-py3-none-any.whl", hash = "sha256:a10b8bbb9f678e4edff7877af1f654fdc9e27c205f952c3ddee2981ad02ec5f2"}, + {file = "codespell-2.0.0.tar.gz", hash = "sha256:dd9983e096b9f7ba89dd2d2466d1fc37231d060f19066331b9571341363c77b8"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, +] +darglint = [ + {file = "darglint-1.7.0-py3-none-any.whl", hash = "sha256:3bf16e78e2909ecdb737afd45fcd6a3f8993b092c2ba2b7cd7b179cceee87a43"}, + {file = "darglint-1.7.0.tar.gz", hash = "sha256:e49b36ac9b4272a9a988b508d23e9f31c29f80a5fc030f1023b46740b5deab31"}, +] +distlib = [ + {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, + {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, +] +dparse = [ + {file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"}, + {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"}, +] +eradicate = [ + {file = "eradicate-2.0.0.tar.gz", hash = "sha256:27434596f2c5314cc9b31410c93d8f7e8885747399773cd088d3adea647a60c8"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, +] +flake8-bandit = [ + {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"}, +] +flake8-bugbear = [ + {file = "flake8-bugbear-21.3.1.tar.gz", hash = "sha256:ea08deb8922486e856b38c9338936de1e1de4ad9e7c912be142673d5afe5ac89"}, + {file = "flake8_bugbear-21.3.1-py36.py37.py38-none-any.whl", hash = "sha256:0c5b949dc1f33d2adae284294d39a81dd11534fbf689890fb22ee532cab959d9"}, +] +flake8-builtins = [ + {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"}, + {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"}, +] +flake8-comprehensions = [ + {file = "flake8-comprehensions-3.3.1.tar.gz", hash = "sha256:e734bf03806bb562886d9bf635d23a65a1a995c251b67d7e007a7b608af9bd22"}, + {file = "flake8_comprehensions-3.3.1-py3-none-any.whl", hash = "sha256:6d80dfafda0d85633f88ea5bc7de949485f71f1e28db7af7719563fe5f62dcb1"}, +] +flake8-docstrings = [ + {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"}, + {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"}, +] +flake8-eradicate = [ + {file = "flake8-eradicate-1.0.0.tar.gz", hash = "sha256:fe7167226676823d50cf540532302a6f576c5a398c5260692571a05ef72c5f5b"}, + {file = "flake8_eradicate-1.0.0-py3-none-any.whl", hash = "sha256:0fc4ab858a18c7ed630621b5345254c8f55be6060ea5c44a25e384d613618d1f"}, +] +flake8-markdown = [ + {file = "flake8-markdown-0.2.0.tar.gz", hash = "sha256:bca29422c66815c2f360fea3efce7356d42832e925d8ceb0525983f4e94a591c"}, + {file = "flake8_markdown-0.2.0-py3-none-any.whl", hash = "sha256:6727f28c71b941f0026bc7e08675a71002bfbf69018317bcec81e0502a33db3f"}, +] +flake8-polyfill = [ + {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, + {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, +] +flake8-simplify = [ + {file = "flake8_simplify-0.14.0-py3-none-any.whl", hash = "sha256:5793b3c7bd826d7489580f8146bbd40d5bface15d9be5020ddbf07980b844709"}, + {file = "flake8_simplify-0.14.0.tar.gz", hash = "sha256:365d0b812fc708af2286a8364ed7b23715bb873431c7f3188bc0b4cd6dcb3c0a"}, +] +gitdb = [ + {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, + {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, +] +gitpython = [ + {file = "GitPython-3.1.14-py3-none-any.whl", hash = "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b"}, + {file = "GitPython-3.1.14.tar.gz", hash = "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61"}, +] +identify = [ + {file = "identify-2.1.0-py2.py3-none-any.whl", hash = "sha256:2a5fdf2f5319cc357eda2550bea713a404392495961022cf2462624ce62f0f46"}, + {file = "identify-2.1.0.tar.gz", hash = "sha256:2179e7359471ab55729f201b3fdf7dc2778e221f868410fedcb0987b791ba552"}, +] +idna = [ + {file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"}, + {file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, + {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.5.2.tar.gz", hash = "sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4"}, + {file = "lazy_object_proxy-1.5.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315"}, + {file = "lazy_object_proxy-1.5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871"}, + {file = "lazy_object_proxy-1.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9"}, + {file = "lazy_object_proxy-1.5.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128"}, + {file = "lazy_object_proxy-1.5.2-cp35-cp35m-win32.whl", hash = "sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847"}, + {file = "lazy_object_proxy-1.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108"}, + {file = "lazy_object_proxy-1.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068"}, + {file = "lazy_object_proxy-1.5.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7"}, + {file = "lazy_object_proxy-1.5.2-cp36-cp36m-win32.whl", hash = "sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb"}, + {file = "lazy_object_proxy-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a"}, + {file = "lazy_object_proxy-1.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39"}, + {file = "lazy_object_proxy-1.5.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0"}, + {file = "lazy_object_proxy-1.5.2-cp37-cp37m-win32.whl", hash = "sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef"}, + {file = "lazy_object_proxy-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302"}, + {file = "lazy_object_proxy-1.5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163"}, + {file = "lazy_object_proxy-1.5.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f"}, + {file = "lazy_object_proxy-1.5.2-cp38-cp38-win32.whl", hash = "sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694"}, + {file = "lazy_object_proxy-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa"}, + {file = "lazy_object_proxy-1.5.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c"}, + {file = "lazy_object_proxy-1.5.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d"}, + {file = "lazy_object_proxy-1.5.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458"}, + {file = "lazy_object_proxy-1.5.2-cp39-cp39-win32.whl", hash = "sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166"}, + {file = "lazy_object_proxy-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +multidict = [ + {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, + {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, + {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, + {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, + {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, + {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, + {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, + {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, + {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, + {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, + {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, + {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, + {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, + {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, + {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, + {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, + {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, +] +mypy = [ + {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"}, + {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"}, + {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"}, + {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"}, + {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"}, + {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"}, + {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"}, + {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"}, + {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"}, + {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"}, + {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"}, + {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"}, + {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"}, + {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"}, + {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"}, + {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"}, + {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"}, + {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"}, + {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"}, + {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"}, + {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"}, + {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nodeenv = [ + {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"}, + {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pbr = [ + {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"}, + {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +pre-commit = [ + {file = "pre_commit-2.11.0-py2.py3-none-any.whl", hash = "sha256:c4853e29c24b27d81359357fae891e00bc83e6a0575c93d815e0e8077bd53b3b"}, + {file = "pre_commit-2.11.0.tar.gz", hash = "sha256:06f8cb95e29e56788fb5dc98d7ca7a714969dc96633e1ba654ccd5953b64c195"}, +] +pre-commit-hooks = [ + {file = "pre_commit_hooks-3.4.0-py2.py3-none-any.whl", hash = "sha256:b1d329fc712f53f56af7c4a0ac08c414a7fcfd634dbd829c3a03f39cfb9c3574"}, + {file = "pre_commit_hooks-3.4.0.tar.gz", hash = "sha256:57e377b931aceead550e4a7bdbe8065e79e371e80f593b5b6d1129e63a77154f"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pydocstyle = [ + {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, + {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pylint = [ + {file = "pylint-2.7.2-py3-none-any.whl", hash = "sha256:d09b0b07ba06bcdff463958f53f23df25e740ecd81895f7d2699ec04bbd8dc3b"}, + {file = "pylint-2.7.2.tar.gz", hash = "sha256:0e21d3b80b96740909d77206d741aa3ce0b06b41be375d92e1f3244a274c1f8a"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, + {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"}, + {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"}, +] +pytest-cov = [ + {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, + {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, +] +pyupgrade = [ + {file = "pyupgrade-2.10.0-py2.py3-none-any.whl", hash = "sha256:b26a00db6e2d745fe5a949e1fd02c5286c3999edaf804f746c69d559c8f8b365"}, + {file = "pyupgrade-2.10.0.tar.gz", hash = "sha256:601427033f280d50b5b102fed1013b96f91244777772114aeb7e191762cd6050"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, +] +requests = [ + {file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"}, + {file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"}, +] +"ruamel.yaml" = [ + {file = "ruamel.yaml-0.16.13-py2.py3-none-any.whl", hash = "sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3"}, + {file = "ruamel.yaml-0.16.13.tar.gz", hash = "sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942"}, +] +"ruamel.yaml.clib" = [ + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-win32.whl", hash = "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"}, + {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"}, + {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"}, + {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f"}, + {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"}, + {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"}, + {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"}, + {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"}, + {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3"}, + {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"}, + {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"}, + {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"}, + {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"}, + {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4"}, + {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"}, + {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"}, + {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"}, + {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"}, + {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923"}, + {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"}, + {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"}, + {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"}, + {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"}, + {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c"}, + {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win32.whl", hash = "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd"}, + {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb"}, + {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"}, +] +safety = [ + {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"}, + {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +smmap = [ + {file = "smmap-3.0.5-py2.py3-none-any.whl", hash = "sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714"}, + {file = "smmap-3.0.5.tar.gz", hash = "sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, + {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, +] +stevedore = [ + {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, + {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, +] +tokenize-rt = [ + {file = "tokenize_rt-4.1.0-py2.py3-none-any.whl", hash = "sha256:b37251fa28c21e8cce2e42f7769a35fba2dd2ecafb297208f9a9a8add3ca7793"}, + {file = "tokenize_rt-4.1.0.tar.gz", hash = "sha256:ab339b5ff829eb5e198590477f9c03c84e762b3e455e74c018956e7e326cbc70"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typed-ast = [ + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, + {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, + {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, + {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, + {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, + {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, + {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, + {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, + {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, + {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] +virtualenv = [ + {file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"}, + {file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"}, +] +vulture = [ + {file = "vulture-2.3-py2.py3-none-any.whl", hash = "sha256:f39de5e6f1df1f70c3b50da54f1c8d494159e9ca3d01a9b89eac929600591703"}, + {file = "vulture-2.3.tar.gz", hash = "sha256:03d5a62bcbe9ceb9a9b0575f42d71a2d414070229f2e6f95fa6e7c71aaaed967"}, +] +wrapt = [ + {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, +] +yamllint = [ + {file = "yamllint-1.26.0-py2.py3-none-any.whl", hash = "sha256:8a5f8e442f49309eaf3e9d7232ce76f2fc8026f5c0c0b164b83f33fed1399637"}, + {file = "yamllint-1.26.0.tar.gz", hash = "sha256:b0e4c89985c7f5f8451c2eb8c67d804d10ac13a4abe031cbf49bdf3465d01087"}, +] +yarl = [ + {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, + {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, + {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, + {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, + {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, + {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, + {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, + {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, + {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, + {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, + {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, + {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, + {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, +] diff --git a/pylintrc b/pylintrc deleted file mode 100644 index a7020741..00000000 --- a/pylintrc +++ /dev/null @@ -1,383 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=tests - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" - -disable= - attribute-defined-outside-init, - duplicate-code, - fixme, - import-error, - invalid-name, - missing-docstring, - protected-access, - too-few-public-methods, - too-many-arguments, - too-many-branches, - too-many-instance-attributes, - too-many-lines, - too-many-locals, - too-many-public-methods, - too-many-return-statements, - too-many-statements, - unnecessary-pass, - # handled by black - format - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=88 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=2000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=__.*__ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# List of decorators that define properties, such as abc.abstractproperty. -property-classes=abc.abstractproperty - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis -ignored-modules= - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). -ignored-classes= - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# List of decorators that create context managers from functions, such as -# contextlib.contextmanager. -contextmanager-decorators=contextlib.contextmanager - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=10 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=25 - -# Maximum number of return / yield for function / method body -max-returns=11 - -# Maximum number of branch for function / method body -max-branches=26 - -# Maximum number of statements in function / method body -max-statements=100 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=11 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=25 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..8d0811ce --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,125 @@ +[tool.poetry] +name = "adguardhome" +version = "0.0.0" +description = "Asynchronous Python client for the AdGuard Home API." +authors = ["Franck Nijhof "] +maintainers = ["Franck Nijhof "] +license = "MIT" +readme = "README.md" +homepage = "https://github.com/frenck/python-adguardhome" +repository = "https://github.com/frenck/python-adguardhome" +documentation = "https://github.com/frenck/python-adguardhome" +keywords = ["adguard home", "adguard", "api", "async", "client"] +classifiers = [ + "Development Status :: 4 - Beta", + "Framework :: AsyncIO", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", +] +packages = [ + { include = "adguardhome", from = "src" }, +] + +[tool.poetry.dependencies] +python = "^3.8" +aiohttp = ">=3.0.0" +yarl = ">=1.6.0" + +[tool.poetry.dev-dependencies] +aresponses = "^2.1.4" +black = "^20.8b1" +blacken-docs = "^1.10.0" +coverage = {version = "^5.5", extras = ["toml"]} +flake8 = "^3.8.4" +flake8-docstrings = "^1.5.0" +isort = "^5.7.0" +mypy = "^0.812" +pre-commit = "^2.11.0" +pre-commit-hooks = "^3.4.0" +pylint = "^2.7.2" +pytest = "^6.2.2" +pytest-asyncio = "^0.14.0" +pytest-cov = "^2.11.1" +yamllint = "^1.26.0" +pyupgrade = "^2.10.0" +flake8-simplify = "^0.14.0" +vulture = "^2.3" +flake8-bandit = "^2.1.2" +flake8-bugbear = "^21.3.1" +flake8-builtins = "^1.5.3" +flake8-comprehensions = "^3.3.1" +flake8-eradicate = "^1.0.0" +flake8-markdown = "^0.2.0" +darglint = "^1.7.0" +safety = "^1.10.3" +codespell = "^2.0.0" +bandit = "^1.7.0" + +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/frenck/python-adguardhome/issues" +Changelog = "https://github.com/frenck/python-adguardhome/releases" + +[tool.black] +target-version = ['py37'] + +[tool.coverage.paths] +source = ["src"] + +[tool.coverage.report] +show_missing = true + +[tool.coverage.run] +branch = true +source = ["adguardhome"] + +[tool.isort] +profile = "black" +multi_line_output = 3 + +[tool.pylint.MASTER] +ignore= [ + "tests" +] + +[tool.pylint.BASIC] +good-names = [ + "_", + "ex", + "fp", + "i", + "id", + "j", + "k", + "on", + "Run", + "T", +] + +[tool.pylint."MESSAGES CONTROL"] +disable= [ + "format", + "unsubscriptable-object", + "duplicate-code", +] + +[tool.pylint.SIMILARITIES] +ignore-imports = true + +[tool.pylint.FORMAT] +max-line-length=88 + +[tool.pytest.ini_options] +addopts = "--cov" + +[tool.vulture] +min_confidence = 80 +paths = ["src", "tests"] +verbose = true + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e01f6861..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -aiohttp==3.7.4.post0 -yarl==1.6.3 diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index 46989649..00000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,8 +0,0 @@ -autopep8==1.5.5 -black==20.8b1 -blacken-docs==1.10.0 -pip==21.0.1 -pre-commit==2.11.0 -twine==3.3.0 -wheel==0.36.2 -yamllint==1.26.0 diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index 116b6d76..00000000 --- a/requirements_test.txt +++ /dev/null @@ -1,10 +0,0 @@ -aresponses==2.1.4 -coverage==5.5 -flake8==3.8.4 -flake8-docstrings==1.5.0 -isort==5.7.0 -mypy==0.812 -pylint==2.7.2 -pytest==6.2.2 -pytest-asyncio==0.14.0 -pytest-cov==2.11.1 diff --git a/setup.py b/setup.py deleted file mode 100644 index a9f0229d..00000000 --- a/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -"""The setup script.""" -import io -import os -import re -import sys - -from setuptools import find_packages, setup - - -def get_version(): - """Get current version from code.""" - regex = r"__version__\s=\s\"(?P[\d\.]+?)\"" - path = ("adguardhome", "__version__.py") - return re.search(regex, read(*path)).group("version") - - -def read(*parts): - """Read file.""" - filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts) - sys.stdout.write(filename) - with io.open(filename, encoding="utf-8", mode="rt") as fp: - return fp.read() - - -with open("README.md") as readme_file: - readme = readme_file.read() - -setup( - author="Franck Nijhof", - author_email="opensource@frenck.dev", - classifiers=[ - "Development Status :: 4 - Beta", - "Framework :: AsyncIO", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - description="Asynchronous Python client for the AdGuard Home API.", - include_package_data=True, - install_requires=["aiohttp>=3.0.0", "yarl"], - keywords=["adguard home", "adguard", "api", "async", "client"], - license="MIT license", - long_description_content_type="text/markdown", - long_description=readme, - name="adguardhome", - packages=find_packages(include=["adguardhome"]), - test_suite="tests", - url="https://github.com/frenck/python-adguardhome", - version=get_version(), - zip_safe=False, -) diff --git a/src/adguardhome/__init__.py b/src/adguardhome/__init__.py new file mode 100644 index 00000000..81965bb5 --- /dev/null +++ b/src/adguardhome/__init__.py @@ -0,0 +1,5 @@ +"""Asynchronous Python client for the AdGuard Home API.""" + +from .adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError + +__all__ = ["AdGuardHome", "AdGuardHomeConnectionError", "AdGuardHomeError"] diff --git a/adguardhome/adguardhome.py b/src/adguardhome/adguardhome.py similarity index 50% rename from adguardhome/adguardhome.py rename to src/adguardhome/adguardhome.py index 1750b727..068923e7 100644 --- a/adguardhome/adguardhome.py +++ b/src/adguardhome/adguardhome.py @@ -1,14 +1,16 @@ """Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + import asyncio import json import socket -from typing import Any, Mapping, Optional +from importlib import metadata +from typing import Any, Mapping import aiohttp import async_timeout from yarl import URL -from .__version__ import __version__ from .exceptions import AdGuardHomeConnectionError, AdGuardHomeError from .filtering import AdGuardHomeFiltering from .parental import AdGuardHomeParental @@ -18,23 +20,41 @@ from .stats import AdGuardHomeStats +# pylint: disable=too-many-instance-attributes class AdGuardHome: """Main class for handling connections with AdGuard Home.""" def __init__( self, host: str, + *, base_path: str = "/control", password: str = None, port: int = 3000, request_timeout: int = 10, - session: aiohttp.client.ClientSession = None, + session: aiohttp.client.ClientSession | None = None, tls: bool = False, + user_agent: str = None, username: str = None, verify_ssl: bool = True, - user_agent: str = None, ) -> None: - """Initialize connection with AdGuard Home.""" + """Initialize connection with AdGuard Home. + + Class constructor for setting up an AdGuard Home object to + communicate with an AdGuard Home instance. + + Args: + host: Hostname or IP address of the AdGuard Home instance. + base_path: Base path of the API, usually `/control`, which is the default. + password: Password for HTTP auth, if enabled. + port: Port on which the API runs, usually 3000. + request_timeout: Max timeout to wait for a response from the API. + session: Optional, shared, aiohttp client session. + tls: True, when TLS/SSL should be used. + user_agent: Defaults to PythonAdGuardHome/. + username: Username for HTTP auth, if enabled. + verify_ssl: Can be set to false, when TLS with self-signed cert is used. + """ self._session = session self._close_session = False @@ -49,7 +69,8 @@ def __init__( self.user_agent = user_agent if user_agent is None: - self.user_agent = "PythonAdGuardHome/{}".format(__version__) + version = metadata.version(__package__) + self.user_agent = f"PythonAdGuardHome/{version}" if self.base_path[-1] != "/": self.base_path += "/" @@ -61,15 +82,37 @@ def __init__( self.safesearch = AdGuardHomeSafeSearch(self) self.stats = AdGuardHomeStats(self) - async def _request( + # pylint: disable=too-many-arguments + async def request( self, uri: str, method: str = "GET", - data: Optional[Any] = None, - json_data: Optional[dict] = None, - params: Optional[Mapping[str, str]] = None, - ) -> Any: - """Handle a request to the AdGuard Home instance.""" + data: Any | None = None, + json_data: dict | None = None, + params: Mapping[str, str] | None = None, + ) -> dict[str, Any]: + """Handle a request to the AdGuard Home instance. + + Make a request against the AdGuard Home API and handles the response. + + Args: + uri: The request URI on the AdGuard Home API to call. + method: HTTP method to use for the request; e.g., GET, POST. + data: RAW HTTP request data to send with the request. + json_data: Dictionary of data to send as JSON with the request. + params: Mapping of request parameters to send with the request. + + Returns: + The response from the API. In case the response is a JSON response, + the method will return a decoded JSON response as a Python + dictionary. In other cases, it will return the RAW text response. + + Raises: + AdGuardHomeConnectionError: An error occurred while communicating + with the AdGuard Home instance (connection issues). + AdGuardHomeError: An error occurred while processing the + response from the AdGuard Home instance (invalid data). + """ scheme = "https" if self.tls else "http" url = URL.build( scheme=scheme, host=self.host, port=self.port, path=self.base_path @@ -125,34 +168,59 @@ async def _request( if "application/json" in content_type: return await response.json() - # Workaround for incorrect content-type headers for the stats call - # https://github.com/AdguardTeam/AdGuardHome/issues/1086 text = await response.text() - if uri == "stats": - return json.loads(text) - - return text + return {"message": text} async def protection_enabled(self) -> bool: - """Return if AdGuard Home protection is enabled or not.""" - response = await self._request("status") + """Return if AdGuard Home protection is enabled or not. + + Returns: + The status of the protection of the AdGuard Home instance. + """ + response = await self.request("status") return response["protection_enabled"] async def enable_protection(self) -> None: - """Enable AdGuard Home protection.""" - await self._request( - "dns_config", method="POST", json_data={"protection_enabled": True}, - ) + """Enable AdGuard Home protection. + + Raises: + AdGuardHomeError: Failed enabling AdGuard Home protection. + """ + try: + await self.request( + "dns_config", + method="POST", + json_data={"protection_enabled": True}, + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed enabling AdGuard Home protection" + ) from exception async def disable_protection(self) -> None: - """Disable AdGuard Home protection.""" - await self._request( - "dns_config", method="POST", json_data={"protection_enabled": False}, - ) + """Disable AdGuard Home protection. + + Raises: + AdGuardHomeError: Failed disabling the AdGuard Home protection. + """ + try: + await self.request( + "dns_config", + method="POST", + json_data={"protection_enabled": False}, + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed disabling AdGuard Home protection" + ) from exception async def version(self) -> str: - """Return the current version of the AdGuard Home instance.""" - response = await self._request("status") + """Return the current version of the AdGuard Home instance. + + Returns: + The version number of the connected AdGuard Home instance. + """ + response = await self.request("status") return response["version"] async def close(self) -> None: @@ -160,10 +228,18 @@ async def close(self) -> None: if self._session and self._close_session: await self._session.close() - async def __aenter__(self) -> "AdGuardHome": - """Async enter.""" + async def __aenter__(self) -> AdGuardHome: + """Async enter. + + Returns: + The AdGuard Home object. + """ return self - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" + async def __aexit__(self, *_exc_info) -> None: + """Async exit. + + Args: + _exc_info: Exec type. + """ await self.close() diff --git a/adguardhome/exceptions.py b/src/adguardhome/exceptions.py similarity index 91% rename from adguardhome/exceptions.py rename to src/adguardhome/exceptions.py index a06fab3c..0daccd1e 100644 --- a/adguardhome/exceptions.py +++ b/src/adguardhome/exceptions.py @@ -4,10 +4,6 @@ class AdGuardHomeError(Exception): """Generic AdGuard Home exception.""" - pass - class AdGuardHomeConnectionError(AdGuardHomeError): """AdGuard Home connection exception.""" - - pass diff --git a/src/adguardhome/filtering.py b/src/adguardhome/filtering.py new file mode 100644 index 00000000..4b484a3c --- /dev/null +++ b/src/adguardhome/filtering.py @@ -0,0 +1,197 @@ +"""Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .exceptions import AdGuardHomeError + +if TYPE_CHECKING: + from . import AdGuardHome + + +class AdGuardHomeFiltering: + """Controls AdGuard Home filtering. Blocks domains.""" + + def __init__(self, adguard: AdGuardHome) -> None: + """Initialize object. + + Args: + adguard: The AdGuard Home instance. + """ + self._adguard = adguard + + async def _config(self, enabled: bool | None = None, interval: int | None = None): + """Configure filtering on AdGuard Home. + + Args: + enabled: Enable/Disable AdGuard Home filtering. + interval: Number of days to keep data in the logs. + """ + if enabled is None: + enabled = await self.enabled() + if interval is None: + interval = await self.interval() + + await self._adguard.request( + "filtering/config", + method="POST", + json_data={"enabled": enabled, "interval": interval}, + ) + + async def enabled(self) -> bool: + """Return if AdGuard Home filtering is enabled or not. + + Returns: + The current state of the AdGuard Home filtering. + """ + response = await self._adguard.request("filtering/status") + return response["enabled"] + + async def enable(self) -> None: + """Enable AdGuard Home filtering. + + Raises: + AdGuardHomeError: If enabling the filtering didn't succeed. + """ + try: + await self._config(enabled=True) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Enabling AdGuard Home filtering failed" + ) from exception + + async def disable(self) -> None: + """Disable AdGuard Home filtering. + + Raises: + AdGuardHomeError: If disabling the filtering didn't succeed. + """ + try: + await self._config(enabled=False) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Disabling AdGuard Home filtering failed" + ) from exception + + async def interval(self, interval: int | None = None) -> int: + """Return or set the time period to keep query log data. + + Args: + interval: Set the time period (in days) to keep query log data. + + Returns: + The current set time period to keep query log data. + """ + if interval: + await self._config(interval=interval) + return interval + + response = await self._adguard.request("filtering/status") + return response["interval"] + + async def rules_count(self) -> int: + """Return the number of rules loaded. + + Returns: + The number of filtering rules currently loaded in the AdGuard + Home instance. + """ + response = await self._adguard.request("filtering/status") + return sum(fil["rules_count"] for fil in response["filters"]) + + async def add_url(self, name: str, url: str) -> None: + """Add a new filter subscription to AdGuard Home. + + Args: + name: The name of the filter subscription. + url: The URL of the filter list. + + Raises: + AdGuardHomeError: Failed adding the filter subscription. + """ + try: + await self._adguard.request( + "filtering/add_url", method="POST", json_data={"name": name, "url": url} + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed adding URL to AdGuard Home filter" + ) from exception + + async def remove_url(self, url: str) -> None: + """Remove a new filter subscription from AdGuard Home. + + Args: + url: Filter subscription URL to remove from AdGuard Home. + + Raises: + AdGuardHomeError: Failed removing the filter subscription. + """ + try: + await self._adguard.request( + "filtering/remove_url", method="POST", json_data={"url": url} + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed removing URL from AdGuard Home filter" + ) from exception + + async def enable_url(self, url: str) -> None: + """Enable a filter subscription in AdGuard Home. + + Args: + url: Filter subscription URL to enable on AdGuard Home. + + Raises: + AdGuardHomeError: Failed enabling filter subscription. + """ + try: + await self._adguard.request( + "filtering/set_url", + method="POST", + json_data={"url": url, "enabled": True}, + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed enabling URL on AdGuard Home filter" + ) from exception + + async def disable_url(self, url: str) -> None: + """Disable a filter subscription in AdGuard Home. + + Args: + url: Filter subscription URL to disable on AdGuard Home. + + Raises: + AdGuardHomeError: Failed disabling filter subscription. + """ + try: + await self._adguard.request( + "filtering/set_url", + method="POST", + json_data={"url": url, "enabled": False}, + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed disabling URL on AdGuard Home filter" + ) from exception + + async def refresh(self, force=False) -> None: + """Reload filtering subscriptions from URLs specified in AdGuard Home. + + Args: + force: Force the reload of all filter subscriptions. + + Raises: + AdGuardHomeError: Failed to refresh filter subscriptions. + """ + force = "true" if force else "false" + + try: + await self._adguard.request( + "filtering/refresh", method="POST", params={"force": force} + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Failed refreshing filter URLs in AdGuard Home" + ) from exception diff --git a/src/adguardhome/parental.py b/src/adguardhome/parental.py new file mode 100644 index 00000000..e3790d70 --- /dev/null +++ b/src/adguardhome/parental.py @@ -0,0 +1,58 @@ +"""Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .exceptions import AdGuardHomeError + +if TYPE_CHECKING: + from . import AdGuardHome + + +class AdGuardHomeParental: + """Controls AdGuard Home parental control.""" + + def __init__(self, adguard: AdGuardHome) -> None: + """Initialize object. + + Args: + adguard: The AdGuard Home instance. + """ + self._adguard = adguard + + async def enabled(self) -> bool: + """Return if AdGuard Home parental control is enabled or not. + + Returns: + The current state of the AdGuard Home parental control. + """ + response = await self._adguard.request("parental/status") + return response["enabled"] + + async def enable(self) -> None: + """Enable AdGuard Home parental control. + + Raises: + AdGuardHomeError: If enabling parental control failed. + """ + try: + await self._adguard.request( + "parental/enable", method="POST", data="sensitivity=TEEN" + ) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Enabling AdGuard Home parental control failed" + ) from exception + + async def disable(self) -> None: + """Disable AdGuard Home parental control. + + Raises: + AdGuardHomeError: If disabling parental control failed. + """ + try: + await self._adguard.request("parental/disable", method="POST") + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Disabling AdGuard Home parental control failed" + ) from exception diff --git a/src/adguardhome/py.typed b/src/adguardhome/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/src/adguardhome/querylog.py b/src/adguardhome/querylog.py new file mode 100644 index 00000000..d318f1f5 --- /dev/null +++ b/src/adguardhome/querylog.py @@ -0,0 +1,89 @@ +"""Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .exceptions import AdGuardHomeError + +if TYPE_CHECKING: + from . import AdGuardHome + + +class AdGuardHomeQueryLog: + """Controls AdGuard Home query log.""" + + def __init__(self, adguard: AdGuardHome) -> None: + """Initialize object. + + Args: + adguard: The AdGuard Home instance. + """ + self._adguard = adguard + + async def _config(self, enabled: bool | None = None, interval: int | None = None): + """Configure query log on AdGuard Home. + + Args: + enabled: Enable/disable AdGuard Home query log. + interval: Number of day to keep data in the logs. + """ + if enabled is None: + enabled = await self.enabled() + if interval is None: + interval = await self.interval() + await self._adguard.request( + "querylog_config", + method="POST", + json_data={"enabled": enabled, "interval": interval}, + ) + + async def enabled(self) -> bool: + """Return if AdGuard Home query log is enabled or not. + + Returns: + The current state of the AdGuard Home query log. + """ + response = await self._adguard.request("querylog_info") + return response["enabled"] + + async def enable(self) -> None: + """Enable AdGuard Home query log. + + Raises: + AdGuardHomeError: If enabling the query log didn't succeed. + """ + try: + await self._config(enabled=True) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Enabling AdGuard Home query log failed" + ) from exception + + async def interval(self, interval: int | None = None) -> int: + """Return or set the time period to keep query log data. + + Args: + interval: Set the time period (in days) to keep query log data. + + Returns: + The current set time period to keep query log data. + """ + if interval: + await self._config(interval=interval) + return interval + + response = await self._adguard.request("querylog_info") + return response["interval"] + + async def disable(self) -> None: + """Disable AdGuard Home query log. + + Raises: + AdGuardHomeError: If disabling the query filter log didn't succeed. + """ + try: + await self._config(enabled=False) + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Disabling AdGuard Home query log failed" + ) from exception diff --git a/src/adguardhome/safebrowsing.py b/src/adguardhome/safebrowsing.py new file mode 100644 index 00000000..bf8e8928 --- /dev/null +++ b/src/adguardhome/safebrowsing.py @@ -0,0 +1,56 @@ +"""Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .exceptions import AdGuardHomeError + +if TYPE_CHECKING: + from . import AdGuardHome + + +class AdGuardHomeSafeBrowsing: + """Controls AdGuard Home browsing security.""" + + def __init__(self, adguard: AdGuardHome) -> None: + """Initialize object. + + Args: + adguard: The AdGuard Home instance. + """ + self._adguard = adguard + + async def enabled(self) -> bool: + """Return if AdGuard Home browsing security is enabled or not. + + Returns: + The current state of the AdGuard safe browsing feature. + """ + response = await self._adguard.request("safebrowsing/status") + return response["enabled"] + + async def enable(self) -> None: + """Enable AdGuard Home browsing security. + + Raises: + AdGuardHomeError: If enabling the safe browsing didn't succeed. + """ + try: + await self._adguard.request("safebrowsing/enable", method="POST") + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Enabling AdGuard Home safe browsing failed" + ) from exception + + async def disable(self) -> None: + """Disable AdGuard Home browsing security. + + Raises: + AdGuardHomeError: If disabling the safe browsing didn't succeed. + """ + try: + await self._adguard.request("safebrowsing/disable", method="POST") + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Disabling AdGuard Home safe browsing failed" + ) from exception diff --git a/src/adguardhome/safesearch.py b/src/adguardhome/safesearch.py new file mode 100644 index 00000000..7cfe1742 --- /dev/null +++ b/src/adguardhome/safesearch.py @@ -0,0 +1,56 @@ +"""Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .exceptions import AdGuardHomeError + +if TYPE_CHECKING: + from . import AdGuardHome + + +class AdGuardHomeSafeSearch: + """Controls AdGuard Home safe search enforcing.""" + + def __init__(self, adguard: AdGuardHome) -> None: + """Initialize object. + + Args: + adguard: The AdGuard Home instance. + """ + self._adguard = adguard + + async def enabled(self) -> bool: + """Return if AdGuard Home safe search enforcing is enabled or not. + + Returns: + The current state of the AdGuard Home safe search. + """ + response = await self._adguard.request("safesearch/status") + return response["enabled"] + + async def enable(self) -> None: + """Enable AdGuard Home safe search enforcing. + + Raises: + AdGuardHomeError: If enabling the safe search didn't succeed. + """ + try: + await self._adguard.request("safesearch/enable", method="POST") + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Enabling AdGuard Home safe search failed" + ) from exception + + async def disable(self) -> None: + """Disable AdGuard Home safe search enforcing. + + Raises: + AdGuardHomeError: If disabling the safe search didn't succeed. + """ + try: + await self._adguard.request("safesearch/disable", method="POST") + except AdGuardHomeError as exception: + raise AdGuardHomeError( + "Disabling AdGuard Home safe search failed" + ) from exception diff --git a/src/adguardhome/stats.py b/src/adguardhome/stats.py new file mode 100644 index 00000000..1af85904 --- /dev/null +++ b/src/adguardhome/stats.py @@ -0,0 +1,111 @@ +"""Asynchronous Python client for the AdGuard Home API.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .exceptions import AdGuardHomeError + +if TYPE_CHECKING: + from . import AdGuardHome + + +class AdGuardHomeStats: + """Provides stats of AdGuard Home.""" + + def __init__(self, adguard: AdGuardHome) -> None: + """Initialize object. + + Args: + adguard: AdGuard Home instance + """ + self._adguard = adguard + + async def dns_queries(self) -> int: + """Return number of DNS queries. + + Returns: + The number of DNS queries performed by the AdGuard Home instance. + """ + response = await self._adguard.request("stats") + return response["num_dns_queries"] + + async def blocked_filtering(self) -> int: + """Return number of blocked DNS queries. + + Returns: + The number of DNS queries blocked by the AdGuard Home instance. + """ + response = await self._adguard.request("stats") + return response["num_blocked_filtering"] + + async def blocked_percentage(self) -> float: + """Return the blocked percentage ratio of DNS queries. + + Returns: + The percentage ratio of blocked DNS queries by the AdGuard Home + instance. + """ + response = await self._adguard.request("stats") + if not response["num_dns_queries"]: + return 0.0 + return (response["num_blocked_filtering"] / response["num_dns_queries"]) * 100.0 + + async def replaced_safebrowsing(self) -> int: + """Return number of blocked pages by safe browsing. + + Returns: + The number of times a page was blocked by the safe + browsing feature of the AdGuard Home instance. + """ + response = await self._adguard.request("stats") + return response["num_replaced_safebrowsing"] + + async def replaced_parental(self) -> int: + """Return number of blocked pages by parental control. + + Returns: + The number of times a page was blocked by the parental control + feature of the AdGuard Home instance. + """ + response = await self._adguard.request("stats") + return response["num_replaced_parental"] + + async def replaced_safesearch(self) -> int: + """Return number of enforced safe searches. + + Returns: + The number of times a safe search was enforced by the + AdGuard Home instance. + """ + response = await self._adguard.request("stats") + return response["num_replaced_safesearch"] + + async def avg_processing_time(self) -> float: + """Return average processing time of DNS queries (in ms). + + Returns: + The averages processing time (in milliseconds) of DNS queries + as performed by the AdGuard Home instance. + """ + response = await self._adguard.request("stats") + return round(response["avg_processing_time"] * 1000, 2) + + async def period(self) -> int: + """Return the time period to keep data (in days). + + Returns: + The time period of data this AdGuard Home instance keeps. + """ + response = await self._adguard.request("stats_info") + return response["interval"] + + async def reset(self) -> None: + """Reset all stats. + + Raises: + AdGuardHomeError: Restting the AdGuard Home stats did not succeed. + """ + try: + await self._adguard.request("stats_reset", method="POST") + except AdGuardHomeError as exception: + raise AdGuardHomeError("Resetting AdGuard Home stats failed") from exception diff --git a/tests/test_adguardhome.py b/tests/test_adguardhome.py index ec6ffbf3..2697862e 100644 --- a/tests/test_adguardhome.py +++ b/tests/test_adguardhome.py @@ -3,8 +3,8 @@ import aiohttp import pytest + from adguardhome import AdGuardHome -from adguardhome.__version__ import __version__ from adguardhome.exceptions import AdGuardHomeConnectionError, AdGuardHomeError @@ -23,7 +23,7 @@ async def test_json_request(aresponses): ) async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", session=session) - response = await adguard._request("/") + response = await adguard.request("/") assert response["status"] == "ok" @@ -41,10 +41,13 @@ async def test_authenticated_request(aresponses): ), ) async with aiohttp.ClientSession() as session: - adguard = AdGuardHome( - "example.com", username="frenck", password="zerocool", session=session, + adguard = AdGuardHome( # noqa: S106 + "example.com", + username="frenck", + password="zerocool", + session=session, ) - response = await adguard._request("/") + response = await adguard.request("/") assert response["status"] == "ok" @@ -56,8 +59,8 @@ async def test_text_request(aresponses): ) async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", session=session) - response = await adguard._request("/") - assert response == "OK" + response = await adguard.request("/") + assert response == {"message": "OK"} @pytest.mark.asyncio @@ -74,7 +77,7 @@ async def test_internal_session(aresponses): ), ) async with AdGuardHome("example.com") as adguard: - response = await adguard._request("/") + response = await adguard.request("/") assert response["status"] == "ok" @@ -86,8 +89,8 @@ async def test_post_request(aresponses): ) async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", session=session) - response = await adguard._request("/", method="POST") - assert response == "OK" + response = await adguard.request("/", method="POST") + assert response == {"message": "OK"} @pytest.mark.asyncio @@ -102,8 +105,8 @@ async def test_request_port(aresponses): async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", port=3333, session=session) - response = await adguard._request("/") - assert response == "OMG PUPPIES!" + response = await adguard.request("/") + assert response == {"message": "OMG PUPPIES!"} @pytest.mark.asyncio @@ -118,8 +121,8 @@ async def test_request_base_path(aresponses): async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", base_path="/admin", session=session) - response = await adguard._request("status") - assert response == "OMG PUPPIES!" + response = await adguard.request("status") + assert response == {"message": "OMG PUPPIES!"} @pytest.mark.asyncio @@ -127,16 +130,14 @@ async def test_request_user_agent(aresponses): """Test AdGuard Home client sending correct user agent headers.""" # Handle to run asserts on request in async def response_handler(request): - assert request.headers["User-Agent"] == "PythonAdGuardHome/{}".format( - __version__ - ) + assert request.headers["User-Agent"] == "PythonAdGuardHome/0.0.0" return aresponses.Response(text="TEDDYBEAR", status=200) aresponses.add("example.com:3000", "/", "GET", response_handler) async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", base_path="/", session=session) - await adguard._request("/") + await adguard.request("/") @pytest.mark.asyncio @@ -151,9 +152,12 @@ async def response_handler(request): async with aiohttp.ClientSession() as session: adguard = AdGuardHome( - "example.com", base_path="/", session=session, user_agent="LoremIpsum/1.0", + "example.com", + base_path="/", + session=session, + user_agent="LoremIpsum/1.0", ) - await adguard._request("/") + await adguard.request("/") @pytest.mark.asyncio @@ -169,7 +173,7 @@ async def response_handler(_): async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", session=session, request_timeout=1) with pytest.raises(AdGuardHomeConnectionError): - assert await adguard._request("/") + assert await adguard.request("/") @pytest.mark.asyncio @@ -185,7 +189,7 @@ async def test_http_error400(aresponses): async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", session=session) with pytest.raises(AdGuardHomeError): - assert await adguard._request("/") + assert await adguard.request("/") @pytest.mark.asyncio @@ -205,7 +209,7 @@ async def test_http_error500(aresponses): async with aiohttp.ClientSession() as session: adguard = AdGuardHome("example.com", session=session) with pytest.raises(AdGuardHomeError): - assert await adguard._request("/") + assert await adguard.request("/") @pytest.mark.asyncio diff --git a/tests/test_filtering.py b/tests/test_filtering.py index 11c99fcd..8a2f1b90 100644 --- a/tests/test_filtering.py +++ b/tests/test_filtering.py @@ -1,6 +1,7 @@ """Tests for `adguardhome.filtering`.""" import aiohttp import pytest + from adguardhome import AdGuardHome from adguardhome.exceptions import AdGuardHomeError @@ -235,7 +236,7 @@ async def response_handler(request): "example.com:3000", "/control/filtering/add_url", "POST", - aresponses.Response(status=200, text="Invalid URL"), + aresponses.Response(status=400, text="Invalid URL"), ) async with aiohttp.ClientSession() as session: @@ -261,7 +262,7 @@ async def response_handler(request): "example.com:3000", "/control/filtering/remove_url", "POST", - aresponses.Response(status=200, text="Invalid URL"), + aresponses.Response(status=400, text="Invalid URL"), ) async with aiohttp.ClientSession() as session: @@ -344,7 +345,7 @@ async def test_refresh(aresponses): "example.com:3000", "/control/filtering/refresh?force=false", "POST", - aresponses.Response(status=200, text="Not OK"), + aresponses.Response(status=400, text="Not OK"), match_querystring=True, ) diff --git a/tests/test_parental.py b/tests/test_parental.py index 1c17f981..b0dcccbe 100644 --- a/tests/test_parental.py +++ b/tests/test_parental.py @@ -1,6 +1,7 @@ """Tests for `adguardhome.parental`.""" import aiohttp import pytest + from adguardhome import AdGuardHome from adguardhome.exceptions import AdGuardHomeError @@ -52,7 +53,7 @@ async def response_handler(request): "example.com:3000", "/control/parental/enable", "POST", - aresponses.Response(status=200, text="NOT OK"), + aresponses.Response(status=400, text="NOT OK"), ) async with aiohttp.ClientSession() as session: @@ -75,7 +76,7 @@ async def test_disable(aresponses): "example.com:3000", "/control/parental/disable", "POST", - aresponses.Response(status=200, text="NOT OK"), + aresponses.Response(status=400, text="NOT OK"), ) async with aiohttp.ClientSession() as session: diff --git a/tests/test_querylog.py b/tests/test_querylog.py index c56ba91b..f280c85a 100644 --- a/tests/test_querylog.py +++ b/tests/test_querylog.py @@ -1,6 +1,7 @@ """Tests for `adguardhome.querylog`.""" import aiohttp import pytest + from adguardhome import AdGuardHome from adguardhome.exceptions import AdGuardHomeError diff --git a/tests/test_safebrowsing.py b/tests/test_safebrowsing.py index 771519ee..13b52866 100644 --- a/tests/test_safebrowsing.py +++ b/tests/test_safebrowsing.py @@ -1,6 +1,7 @@ """Tests for `adguardhome.safebrowsing`.""" import aiohttp import pytest + from adguardhome import AdGuardHome from adguardhome.exceptions import AdGuardHomeError @@ -49,7 +50,7 @@ async def test_enable(aresponses): "example.com:3000", "/control/safebrowsing/enable", "POST", - aresponses.Response(status=200, text="NOT OK"), + aresponses.Response(status=400, text="NOT OK"), ) async with aiohttp.ClientSession() as session: @@ -72,7 +73,7 @@ async def test_disable(aresponses): "example.com:3000", "/control/safebrowsing/disable", "POST", - aresponses.Response(status=200, text="NOT OK"), + aresponses.Response(status=400, text="NOT OK"), ) async with aiohttp.ClientSession() as session: diff --git a/tests/test_safesearch.py b/tests/test_safesearch.py index 4d733ba7..c56bca01 100644 --- a/tests/test_safesearch.py +++ b/tests/test_safesearch.py @@ -1,6 +1,7 @@ """Tests for `adguardhome.safesearch`.""" import aiohttp import pytest + from adguardhome import AdGuardHome from adguardhome.exceptions import AdGuardHomeError @@ -49,7 +50,7 @@ async def test_enable(aresponses): "example.com:3000", "/control/safesearch/enable", "POST", - aresponses.Response(status=200, text="NOT OK"), + aresponses.Response(status=400, text="NOT OK"), ) async with aiohttp.ClientSession() as session: @@ -72,7 +73,7 @@ async def test_disable(aresponses): "example.com:3000", "/control/safesearch/disable", "POST", - aresponses.Response(status=200, text="NOT OK"), + aresponses.Response(status=400, text="NOT OK"), ) async with aiohttp.ClientSession() as session: diff --git a/tests/test_stats.py b/tests/test_stats.py index 4dacfff5..882aa5ce 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -1,6 +1,7 @@ """Tests for `adguardhome.stats`.""" import aiohttp import pytest + from adguardhome import AdGuardHome from adguardhome.exceptions import AdGuardHomeError @@ -145,7 +146,7 @@ async def test_replaced_safesearch(aresponses): @pytest.mark.asyncio async def test_avg_processing_time(aresponses): - """Test requesting AdGuard Home DNS avarage processing time stats.""" + """Test requesting AdGuard Home DNS average processing time stats.""" aresponses.add( "example.com:3000", "/control/stats", @@ -195,7 +196,7 @@ async def test_reset(aresponses): "example.com:3000", "/control/stats_reset", "POST", - aresponses.Response(status=200, text="Not OK"), + aresponses.Response(status=400, text="Not OK"), ) async with aiohttp.ClientSession() as session: @@ -203,22 +204,3 @@ async def test_reset(aresponses): await adguard.stats.reset() with pytest.raises(AdGuardHomeError): await adguard.stats.reset() - - -@pytest.mark.asyncio -async def test_content_type_workarond(aresponses): - """Test for working around content-type issue in AdGuard Home v0.99.0.""" - aresponses.add( - "example.com:3000", - "/control/stats", - "GET", - aresponses.Response( - status=200, - headers={"Content-Type": "text/plain; charset=utf-8"}, - text='{"avg_processing_time": 0.03141}', - ), - ) - async with aiohttp.ClientSession() as session: - adguard = AdGuardHome("example.com", session=session) - result = await adguard.stats.avg_processing_time() - assert result == 31.41