diff --git a/.github/actions/black/Dockerfile b/.github/actions/black/Dockerfile new file mode 100644 index 00000000..8b0b652e --- /dev/null +++ b/.github/actions/black/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3 + +RUN pip install black==19.10b0 + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/black/entrypoint.sh b/.github/actions/black/entrypoint.sh new file mode 100755 index 00000000..210664af --- /dev/null +++ b/.github/actions/black/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +sh -c "black $*" diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml new file mode 100644 index 00000000..2e4f98a2 --- /dev/null +++ b/.github/workflows/first-interaction.yml @@ -0,0 +1,19 @@ +name: "First Time Contributor" +on: + schedule: + - cron: "*/10 * * * *" + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: fjeremic/cron-first-interaction@0.2.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: | + Thank you for supporting ScanAPI, and congratulations on your first contribution! A project committer will shortly review your contribution. + + In the mean time, if you haven't had a chance please skim over the [First Pull Request Guide](https://github.com/scanapi/scanapi/wiki/First-Pull-Request) which all pull requests must adhere to. + + We hope to see you around! + pr-label: first contribution diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c2143f71..6ae67300 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,6 +7,6 @@ jobs: steps: - uses: actions/checkout@v1 - name: "Black Code Formatter" - uses: "lgeiger/black-action@v1.0.1" + uses: "./.github/actions/black" with: args: ". --check" diff --git a/.github/workflows/run-examples.yml b/.github/workflows/run-examples.yml new file mode 100644 index 00000000..7b5d05df --- /dev/null +++ b/.github/workflows/run-examples.yml @@ -0,0 +1,89 @@ +on: pull_request +name: ScanAPI Examples +jobs: + poke-api: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v1 + with: + python-version: 3.6.12 + - name: Poetry Setup + uses: dschep/install-poetry-action@v1.3 + with: + version: 1.0.9 + - name: Checkout ScanAPI repository + uses: actions/checkout@v2 + - name: Checkout ScanAPI Examples repository + uses: actions/checkout@v2 + with: + repository: scanapi/examples + path: examples + - name: Install Dependencies + run: poetry install + - name: Run ScanAPI for PokeAPI + run: "poetry run scanapi run ./examples/pokeapi/scanapi.yaml -c ./examples/pokeapi/scanapi.conf -o pokeapi.html" + - name: Upload PokeAPI ScanAPI Report + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: PokeAPI ScanAPI Report + path: pokeapi.html + + httpbin-api: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v1 + with: + python-version: 3.6.12 + - name: Poetry Setup + uses: dschep/install-poetry-action@v1.3 + with: + version: 1.0.9 + - name: Checkout ScanAPI repository + uses: actions/checkout@v2 + - name: Checkout ScanAPI Examples repository + uses: actions/checkout@v2 + with: + repository: scanapi/examples + path: examples + - name: Install Dependencies + run: poetry install + - name: Run ScanAPI for httpbin.org + run: "poetry run scanapi run ./examples/httpbin-api/httpbin.yaml -c ./examples/httpbin-api/scanapi.conf -o httpbin.html" + - name: Upload httpbin.org ScanAPI Report + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: httpbin.org ScanAPI Report + path: httpbin.html + + demo-api: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v1 + with: + python-version: 3.6.12 + - name: Poetry Setup + uses: dschep/install-poetry-action@v1.3 + with: + version: 1.0.9 + - name: Checkout ScanAPI repository + uses: actions/checkout@v2 + - name: Checkout ScanAPI Examples repository + uses: actions/checkout@v2 + with: + repository: scanapi/examples + path: examples + - name: Install Dependencies + run: poetry install + - name: Run ScanAPI for Demo API + env: + BASE_URL: "http://demo.scanapi.dev/api/" + DEMO_KEY: "demoKEY123" + run: "poetry run scanapi run ./examples/demo-api/scanapi.yaml -c ./examples/demo-api/scanapi.conf -o demo-api.html" + - name: Upload Demo API ScanAPI Report + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: Demo API ScanAPI Report + path: demo-api.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a3aaf95..03c080f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,3 +4,7 @@ repos: hooks: - id: black language_version: python3.7 + - repo: https://github.com/pre-commit/mirrors-isort + rev: v5.5.3 + hooks: + - id: isort diff --git a/CHANGELOG.md b/CHANGELOG.md index 70eb3b1f..438f24bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] - 2020-10-06 +### Added +- Add a `delay` key option to perform a delay between each request. [#266](https://github.com/scanapi/scanapi/issues/266) + +### Changed +- Changed relative path to show absolute path to the report in CLI. [#277](https://github.com/scanapi/scanapi/pull/277) +- Considering `-` (dash) in variable names. [#281](https://github.com/scanapi/scanapi/pull/281) +- Moved bandit to dev section [#285](https://github.com/scanapi/scanapi/pull/285) +- Increased Test coverage for `/scanapi/evaluators/spec_evaluator.py` [#291](https://github.com/scanapi/scanapi/pull/291) + +### Fixed +- When there is no `body` specified, sending it as `None` instead of `{}`. [#280](https://github.com/scanapi/scanapi/pull/280) +- Removed unused imports. [#294](https://github.com/scanapi/scanapi/pull/294) + ## [2.0.0] - 2020-08-25 ### Added - JSON response is now properly rendered, instead of plain text. [#213](https://github.com/scanapi/scanapi/pull/213) @@ -14,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bandit security audit tool. [#219](https://github.com/scanapi/scanapi/pull/219) - Add Sphinx auto-documentation. [#230](https://github.com/scanapi/scanapi/pull/230) - Add workflow to package/publish to Test PyPi. [#239](https://github.com/scanapi/scanapi/pull/239) +- Add Github Action workflow for First-time contributors. [#290](https://github.com/scanapi/scanapi/pull/290) +- Add button to copy data from the report page. [#295](https://github.com/scanapi/scanapi/pull/295) ### Changed - Renamed `api.(yaml|json)` to `scanapi.yaml`. [#222](https://github.com/scanapi/scanapi/issues/20://github.com/scanapi/scanapi/pull/222) @@ -22,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed command `scanapi spec-file.yaml` to `scanapi run spec-file.yaml`. [#247](https://github.com/scanapi/scanapi/pull/247) - Moved Documentation from README.md to the website. [#250](https://github.com/scanapi/scanapi/pull/250) - Local and global configuration. [#254](https://github.com/scanapi/scanapi/pull/254) +- Moved `bandit` to `dev` in `pyproject.toml`. [#286](https://github.com/scanapi/scanapi/pull/286) ### Fixed - Updated language use in README.md and CONTRIBUTING.md plus fix broken links. [#220](https://github.com/scanapi/scanapi/pull/220) @@ -170,7 +187,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix vars interpolation. -[Unreleased]: https://github.com/camilamaia/scanapi/compare/v2.0.0...HEAD +[Unreleased]: https://github.com/camilamaia/scanapi/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/camilamaia/scanapi/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/camilamaia/scanapi/compare/v1.0.5...v2.0.0 [1.0.5]: https://github.com/camilamaia/scanapi/compare/v1.0.4...v1.0.5 [1.0.4]: https://github.com/camilamaia/scanapi/compare/v1.0.3...v1.0.4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 748a5512..0161bd5a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,165 +2,4 @@ Thanks for taking the time to contribute! ๐Ÿ™‡โ€โ™€๏ธ๐Ÿ™‡โ€โ™‚๏ธ Every little bit of help counts! -## Install - -### Requirements: -- [Python 3][python] -- [Poetry][poetry] - -Clone the repository and enter into the project's folder: - -```shell -$ git clone git@github.com:scanapi/scanapi.git -$ cd scanapi -``` - -Create a [virtualenv][virtualenv] for ScanAPI and activate it: - -```shell -$ make sh -``` - -Install the dependencies: - -```shell -$ make install -``` - -## Run - -Run the ScanAPI: - -```shell -$ poetry run scanapi -``` - -For help, run: - -```shell -$ poetry run scanapi --help -``` - -As you may noticed, you need an API specification file to run ScanAPI properly. -Otherwise you will receive this error: - -```shell -$ poetry run scanapi -ERROR:scanapi:Could not find API spec file: scanapi.yaml. [Errno 2] No such file or directory: 'scanapi.yaml' -``` - -For that, we have the [ScanAPI Examples][scanapi-examples] repository, with some API specification -examples that you can use. - -### Clone ScanAPI Examples - -In another terminal tab, outside `scanapi` folder, clone the [ScanAPI examples][scanapi-examples] project: - -```shell -$ git clone git@github.com:scanapi/examples.git -``` - -Your workspace should have these both folders now: - -```shell -โ–ถ ls -scanapi examples -``` - -Activate the virtualenv created before: - -```shell -$ cd scanapi -$ make sh -``` - -Run the ScanAPI for the API example you prefer: - -**PokรจAPI** - -```shell -$ poetry run scanapi ../examples/pokeapi/scanapi.yaml -c ../examples/pokeapi/.scanapi.yaml -o ../examples/pokeapi/scanapi-report.html -``` - -**Demo-API** - -```shell -$ source ../examples/demo-api/.env -$ poetry run scanapi ../examples/demo-api/api.yaml -c ../examples/demo-api/.scanapi.yaml -o ../examples/demo-api/scanapi-report.html -``` - -## Tests - -To run the tests, run: - -```shell -$ make test -``` - -For testing, we use [pytest](https://docs.pytest.org/en/stable/). We also use classes to give some -context to the tests, something inspired in [BDD](https://www.departmentofproduct.com/blog/writing-bdd-test-scenarios/): - -```python -class TestFileName: # example: TestRegistration - class TestFunctionName: # example TestRegisterAccount - class TestContext: # example TestWhenDataIsIncomplete - def test_expect_behavior(self): # example test_should_return_422 - pass -``` - -## Add new dependencies - -For adding new dependencies, we use Poetry. You can check the official documentation: [https://python-poetry.org/docs/basic-usage/#specifying-dependencies](https://python-poetry.org/docs/basic-usage/#specifying-dependencies) - -## Deploy - -Steps: -1. Release PR -2. Deploy on GitHub - -### 1. Release PR - -### Bump the lib Version - -Check the last release number at [https://pypi.org/project/scanapi/#history](https://pypi.org/project/scanapi/#history) - -Increment the version number in `pyproject.toml` according to the version you have just got. - -### Update the CHANGELOG.md - -Add a new version title with the new version number and the current date, like [this](https://github.com/camilamaia/scanapi/commit/86e89e6ab52bbf64e058c02dbfdbbb1500066bff#diff-4ac32a78649ca5bdd8e0ba38b7006a1eR9-R10) - -And add the version links, like [this](https://github.com/camilamaia/scanapi/commit/86e89e6ab52bbf64e058c02dbfdbbb1500066bff#diff-4ac32a78649ca5bdd8e0ba38b7006a1eR69-R70) - -### Create the PR - -Create a PR named `Release ` containing these two changes above. - -[Example of Release PR](https://github.com/camilamaia/scanapi/commit/86e89e6ab52bbf64e058c02dbfdbbb1500066bff) - -### Merge the PR - -Once the PR has been accepted and passed on all checks, merge it. - -### 2. Deploy on GitHub - -Deploy on GitHub is done when a [new release is created][creating-releases]. - -- The `tag version` should be `v`, like for example `v0.0.18`. -- The `release title` should be the same as tag version,(e.g `v0.0.18`). -- The `release description` should be the content copied from the CHANGELOG.md file from the -corresponding version section. - -Real examples are available at: https://github.com/scanapi/scanapi/releases. - -When the Deploy on GitHub is done, the new version will be automatically deployed on [Docker Hub][scanapi-on-docker-hub] and [PyPI][scanapi-on-pypi]. -Check if everything run as expected for both and that is it, the deploy is done ๐ŸŽ‰ - - -[creating-releases]: https://help.github.com/en/enterprise/2.13/user/articles/creating-releases -[poetry]: https://python-poetry.org/docs/#installation -[python]: https://www.python.org/downloads/ -[scanapi-examples]: https://github.com/scanapi/examples -[scanapi-on-docker-hub]: https://hub.docker.com/r/camilamaia/scanapi -[scanapi-on-pypi]: https://pypi.org/project/scanapi/ -[virtualenv]: https://virtualenv.pypa.io/en/latest/ +Our Contributing documentation is available at our [Wiki](https://github.com/scanapi/scanapi/wiki). Please, check our [Newcomers Guide](https://github.com/scanapi/scanapi/wiki/Newcomers) to find some help with the first steps and also to be aware of our best practices. diff --git a/Dockerfile b/Dockerfile index 942cf894..b0ff205d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ ENV PATH="~/.local/bin:${PATH}" RUN pip install pip setuptools --upgrade -RUN pip install scanapi +RUN pip install scanapi==2.0.0 COPY . /app diff --git a/Makefile b/Makefile index 000c958d..511dde09 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ check: @black -l 80 --check . --exclude=.venv format: - @black -l . --exclude=.venv + @black -l 80 . --exclude=.venv install: @poetry install diff --git a/README.md b/README.md index de5115ee..3c839697 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,12 @@ You can find complete examples at [scanapi/examples][scanapi-examples]! ## Contributing -Collaboration is super welcome! We prepared the [CONTRIBUTING.md][contributing-file] file to help -you in the first steps. Every little bit of help counts! Feel free to create new GitHub issues and -interact here. +Collaboration is super welcome! We prepared the [Newcomers Guide][newcomers-guide] to help you in the first steps. Every little bit of help counts! Feel free to create new [GitHub issues][github-issues] and interact here. Let's build it together ๐Ÿš€ -[contributing-file]: https://github.com/scanapi/scanapi/blob/master/CONTRIBUTING.md +[github-issues]: https://github.com/scanapi/scanapi/issues +[newcomers-guide]: https://github.com/scanapi/scanapi/wiki/Newcomers [pip-installation]: https://pip.pypa.io/en/stable/installing/ [scanapi-examples]: https://github.com/scanapi/examples [website]: https://scanapi.dev diff --git a/poetry.lock b/poetry.lock index 5cc2c4d9..35b473cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,75 +1,74 @@ [[package]] -category = "dev" -description = "A configurable sidebar-enabled Sphinx theme" name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = "*" -version = "0.7.12" [[package]] -category = "main" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" 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.*" -version = "1.4.0" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.2.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "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]] -category = "dev" -description = "Internationalization utilities" name = "babel" +version = "2.8.0" +description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8.0" [package.dependencies] pytz = ">=2015.7" [[package]] -category = "main" -description = "Security oriented static analyser for python code." name = "bandit" +version = "1.6.2" +description = "Security oriented static analyser for python code." +category = "dev" optional = false python-versions = "*" -version = "1.6.2" [package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=3.13" -colorama = ">=0.3.9" six = ">=1.10.0" stevedore = ">=1.20.0" [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "19.10b0" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "19.10b0" [package.dependencies] appdirs = "*" @@ -84,173 +83,170 @@ typed-ast = ">=1.4.0" d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.6.20" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.6.20" [[package]] -category = "dev" -description = "Validate configuration and produce human readable error messages." name = "cfgv" +version = "3.2.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.6.1" -version = "3.2.0" [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = "*" -version = "3.0.4" [[package]] -category = "main" -description = "Composable command line interface toolkit" name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] -category = "dev" -description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" name = "codecov" +version = "2.1.7" +description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.1.7" [package.dependencies] coverage = "*" requests = ">=2.7.9" [[package]] -category = "main" -description = "Cross-platform colored terminal text." -marker = "platform_system == \"Windows\" or sys_platform == \"win32\"" name = "colorama" +version = "0.4.3" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" [[package]] -category = "dev" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.3" +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" -version = "5.2.1" [package.extras] toml = ["toml"] [[package]] -category = "main" -description = "Library to convert python requests object to curl command." name = "curlify" +version = "2.2.1" +description = "Library to convert python requests object to curl command." +category = "main" optional = false python-versions = "*" -version = "2.2.1" [package.dependencies] requests = "*" [[package]] -category = "dev" -description = "Distribution utilities" name = "distlib" +version = "0.3.1" +description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" -version = "0.3.1" [[package]] -category = "dev" -description = "Docutils -- Python Documentation Utilities" name = "docutils" +version = "0.16" +description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.16" [[package]] -category = "dev" -description = "A platform independent file lock." name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" optional = false python-versions = "*" -version = "3.0.12" [[package]] -category = "dev" -description = "Let your Python tests travel through time" name = "freezegun" +version = "1.0.0" +description = "Let your Python tests travel through time" +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.3.15" +python-versions = ">=3.5" [package.dependencies] -python-dateutil = ">=1.0,<2.0 || >2.0" -six = "*" +python-dateutil = ">=2.7" [[package]] -category = "main" -description = "Git Object Database" name = "gitdb" +version = "4.0.5" +description = "Git Object Database" +category = "dev" optional = false python-versions = ">=3.4" -version = "4.0.5" [package.dependencies] smmap = ">=3.0.1,<4" [[package]] -category = "main" -description = "Python Git Library" name = "gitpython" +version = "3.1.9" +description = "Python Git Library" +category = "dev" optional = false python-versions = ">=3.4" -version = "3.1.7" [package.dependencies] gitdb = ">=4.0.1,<5" [[package]] -category = "dev" -description = "File identification library for Python" name = "identify" +version = "1.5.5" +description = "File identification library for Python" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.27" [package.extras] license = ["editdistance"] [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "dev" -description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" +version = "1.2.0" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.2.0" [[package]] -category = "main" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "1.7.0" +description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.7.0" [package.dependencies] zipp = ">=0.5" @@ -260,29 +256,47 @@ docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] -category = "dev" -description = "Read resources from Python packages" -marker = "python_version < \"3.7\"" name = "importlib-resources" +version = "3.0.0" +description = "Read resources from Python packages" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.0.0" [package.dependencies] -[package.dependencies.zipp] -python = "<3.8" -version = ">=0.4" +zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "rst.linker", "jaraco.packaging"] [[package]] -category = "main" -description = "A very fast and expressive template engine." +name = "iniconfig" +version = "1.0.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.5.4" +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 = "jinja2" +version = "2.11.2" +description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -291,154 +305,135 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" version = "1.1.1" - -[[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false -python-versions = ">=3.5" -version = "8.4.0" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] -category = "dev" -description = "Node.js virtual environment builder" name = "nodeenv" +version = "1.5.0" +description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = "*" -version = "1.4.0" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "20.4" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" six = "*" [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.8.0" +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.*" -version = "0.8.0" [[package]] -category = "main" -description = "Python Build Reasonableness" name = "pbr" +version = "5.5.0" +description = "Python Build Reasonableness" +category = "dev" optional = false -python-versions = "*" -version = "5.4.5" +python-versions = ">=2.6" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" 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.*" -version = "0.13.1" [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] -category = "dev" -description = "A framework for managing and maintaining multi-language pre-commit hooks." name = "pre-commit" +version = "2.6.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.6.1" -version = "2.6.0" [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = "*", markers = "python_version < \"3.7\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" toml = "*" virtualenv = ">=20.0.8" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - -[package.dependencies.importlib-resources] -python = "<3.7" -version = "*" - [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.9.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.*" -version = "1.9.0" [[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." name = "pygments" +version = "2.7.1" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.5" -version = "2.6.1" [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "6.1.0" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.4.3" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" -colorama = "*" -more-itertools = ">=4.0.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" packaging = "*" pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +py = ">=1.8.2" +toml = "*" [package.extras] -checkqa-mypy = ["mypy (v0.761)"] +checkqa_mypy = ["mypy (0.780)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "dev" -description = "Pytest plugin for measuring coverage." name = "pytest-cov" +version = "2.10.0" +description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.10.0" [package.dependencies] coverage = ">=4.4" @@ -448,24 +443,24 @@ pytest = ">=4.6" testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] -category = "dev" -description = "Wrap tests with fixtures in freeze_time" name = "pytest-freezegun" +version = "0.4.1" +description = "Wrap tests with fixtures in freeze_time" +category = "dev" optional = false python-versions = "*" -version = "0.4.1" [package.dependencies] freezegun = ">0.3" pytest = ">=3.0.0" [[package]] -category = "dev" -description = "Thin-wrapper around the mock package for easier use with pytest" name = "pytest-mock" +version = "3.2.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.5" -version = "3.2.0" [package.dependencies] pytest = ">=2.7" @@ -474,47 +469,47 @@ pytest = ">=2.7" dev = ["pre-commit", "tox", "pytest-asyncio"] [[package]] -category = "dev" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "dev" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2020.1" +description = "World timezone definitions, modern and historical" +category = "dev" optional = false python-versions = "*" -version = "2020.1" [[package]] -category = "main" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2020.9.27" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.7.14" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.24.0" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" [package.dependencies] certifi = ">=2017.4.17" @@ -527,12 +522,12 @@ security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] -category = "dev" -description = "Mock out responses from the requests package" name = "requests-mock" +version = "1.8.0" +description = "Mock out responses from the requests package" +category = "dev" optional = false python-versions = "*" -version = "1.8.0" [package.dependencies] requests = ">=2.3,<3" @@ -543,48 +538,47 @@ fixture = ["fixtures"] test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" 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.*" -version = "1.15.0" [[package]] -category = "main" -description = "A pure Python implementation of a sliding window memory map manager" name = "smmap" +version = "3.0.4" +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.*" -version = "3.0.4" [[package]] -category = "dev" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." name = "snowballstemmer" +version = "2.0.0" +description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" -version = "2.0.0" [[package]] -category = "dev" -description = "Python documentation generator" name = "sphinx" +version = "3.1.2" +description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=3.5" -version = "3.1.2" [package.dependencies] -Jinja2 = ">=2.3" -Pygments = ">=2.0" alabaster = ">=0.7,<0.8" babel = ">=1.3" -colorama = ">=0.3.5" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.12" imagesize = "*" +Jinja2 = ">=2.3" packaging = "*" +Pygments = ">=2.0" requests = ">=2.5.0" -setuptools = "*" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" @@ -599,12 +593,12 @@ lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-s test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"] [[package]] -category = "dev" -description = "Read the Docs theme for Sphinx" name = "sphinx-rtd-theme" +version = "0.5.0" +description = "Read the Docs theme for Sphinx" +category = "dev" optional = false python-versions = "*" -version = "0.5.0" [package.dependencies] sphinx = "*" @@ -613,114 +607,111 @@ sphinx = "*" dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] [[package]] -category = "dev" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.2" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "dev" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.2" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "dev" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" name = "sphinxcontrib-htmlhelp" +version = "1.0.3" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.3" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest", "html5lib"] [[package]] -category = "dev" -description = "A sphinx extension which renders display math in HTML via JavaScript" name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.1" [package.extras] test = ["pytest", "flake8", "mypy"] [[package]] -category = "dev" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.3" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "dev" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." name = "sphinxcontrib-serializinghtml" +version = "1.1.4" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.1.4" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "main" -description = "Manage dynamic plugins for Python applications" name = "stevedore" +version = "3.2.2" +description = "Manage dynamic plugins for Python applications" +category = "dev" optional = false python-versions = ">=3.6" -version = "3.2.0" [package.dependencies] +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=1.7.0" - [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.1" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = "*" -version = "0.10.1" [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.1" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.25.10" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.10" [package.extras] brotli = ["brotlipy (>=0.6.0)"] @@ -728,56 +719,41 @@ secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0 socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Virtual Python Environment builder" name = "virtualenv" +version = "20.0.31" +description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.30" [package.dependencies] appdirs = ">=1.4.3,<2" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" +importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} six = ">=1.9.0,<2" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12,<2" - -[package.dependencies.importlib-resources] -python = "<3.7" -version = ">=1.0" - [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 (>=5)", "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)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.5" - -[[package]] -category = "main" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "3.2.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=3.6" -version = "3.1.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "79db8d6d7d207b2f721c5e9a1edc7964b32a44f51345c6b31c1002979b22c20b" -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.6.1" +content-hash = "bc762abd4cbdffe6274e4a976bf208824e3585e43e1308c5663d6de57064844f" [metadata.files] alabaster = [ @@ -793,8 +769,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, + {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, ] babel = [ {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, @@ -834,40 +810,40 @@ colorama = [ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] coverage = [ - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"}, - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"}, - {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"}, - {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"}, - {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"}, - {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"}, - {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"}, - {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"}, - {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"}, - {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"}, - {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"}, - {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"}, - {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"}, - {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"}, - {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"}, - {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"}, - {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"}, - {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"}, - {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"}, - {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] curlify = [ {file = "curlify-2.2.1.tar.gz", hash = "sha256:0d3f02e7235faf952de8ef45ef469845196d30632d5838bcd5aee217726ddd6d"}, @@ -885,20 +861,20 @@ filelock = [ {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] freezegun = [ - {file = "freezegun-0.3.15-py2.py3-none-any.whl", hash = "sha256:82c757a05b7c7ca3e176bfebd7d6779fd9139c7cb4ef969c38a28d74deef89b2"}, - {file = "freezegun-0.3.15.tar.gz", hash = "sha256:e2062f2c7f95cc276a834c22f1a17179467176b624cc6f936e8bc3be5535ad1b"}, + {file = "freezegun-1.0.0-py2.py3-none-any.whl", hash = "sha256:02b35de52f4699a78f6ac4518e4cd3390dddc43b0aeb978335a8f270a2d9668b"}, + {file = "freezegun-1.0.0.tar.gz", hash = "sha256:1cf08e441f913ff5e59b19cc065a8faa9dd1ddc442eaf0375294f344581a0643"}, ] 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.7-py3-none-any.whl", hash = "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5"}, - {file = "GitPython-3.1.7.tar.gz", hash = "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858"}, + {file = "GitPython-3.1.9-py3-none-any.whl", hash = "sha256:138016d519bf4dd55b22c682c904ed2fd0235c3612b2f8f65ce218ff358deed8"}, + {file = "GitPython-3.1.9.tar.gz", hash = "sha256:a03f728b49ce9597a6655793207c6ab0da55519368ff5961e4a74ae475b9fa8e"}, ] identify = [ - {file = "identify-1.4.27-py2.py3-none-any.whl", hash = "sha256:f9ea81ccbade4f12d5f3960522c508aabf31709a48efa4f6c6b700e0c7366ff2"}, - {file = "identify-1.4.27.tar.gz", hash = "sha256:4c3646d765127b003d2bed8db1e125d68f5f83ad0cd85e21c908ef87a5e24be1"}, + {file = "identify-1.5.5-py2.py3-none-any.whl", hash = "sha256:da683bfb7669fa749fc7731f378229e2dbf29a1d1337cbde04106f02236eb29d"}, + {file = "identify-1.5.5.tar.gz", hash = "sha256:7c22c384a2c9b32c5cc891d13f923f6b2653aa83e2d75d8f79be240d6c86c4f4"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -916,6 +892,14 @@ importlib-resources = [ {file = "importlib_resources-3.0.0-py2.py3-none-any.whl", hash = "sha256:d028f66b66c0d5732dae86ba4276999855e162a749c92620a38c1d779ed138a7"}, {file = "importlib_resources-3.0.0.tar.gz", hash = "sha256:19f745a6eca188b490b1428c8d1d4a0d2368759f32370ea8fb89cad2ab1106c3"}, ] +iniconfig = [ + {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"}, + {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"}, +] +isort = [ + {file = "isort-5.5.4-py3-none-any.whl", hash = "sha256:36f0c6659b9000597e92618d05b72d4181104cf59472b1c6a039e3783f930c95"}, + {file = "isort-5.5.4.tar.gz", hash = "sha256:ba040c24d20aa302f78f4747df549573ae1eaf8e1084269199154da9c483f07f"}, +] jinja2 = [ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, @@ -955,12 +939,9 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] -more-itertools = [ - {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, - {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, -] nodeenv = [ - {file = "nodeenv-1.4.0-py2.py3-none-any.whl", hash = "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"}, + {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.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, @@ -971,8 +952,8 @@ pathspec = [ {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, ] pbr = [ - {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, - {file = "pbr-5.4.5.tar.gz", hash = "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c"}, + {file = "pbr-5.5.0-py2.py3-none-any.whl", hash = "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"}, + {file = "pbr-5.5.0.tar.gz", hash = "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -987,16 +968,16 @@ py = [ {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, + {file = "Pygments-2.7.1-py3-none-any.whl", hash = "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998"}, + {file = "Pygments-2.7.1.tar.gz", hash = "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"}, ] 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-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, + {file = "pytest-6.1.0-py3-none-any.whl", hash = "sha256:1cd09785c0a50f9af72220dd12aa78cfa49cbffc356c61eab009ca189e018a33"}, + {file = "pytest-6.1.0.tar.gz", hash = "sha256:d010e24666435b39a4cf48740b039885642b6c273a3f77be3e7e03554d2806b7"}, ] pytest-cov = [ {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"}, @@ -1032,27 +1013,27 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] regex = [ - {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"}, - {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"}, - {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"}, - {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"}, - {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"}, - {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"}, - {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"}, - {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"}, - {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"}, + {file = "regex-2020.9.27-cp27-cp27m-win32.whl", hash = "sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3"}, + {file = "regex-2020.9.27-cp27-cp27m-win_amd64.whl", hash = "sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19"}, + {file = "regex-2020.9.27-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be"}, + {file = "regex-2020.9.27-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b"}, + {file = "regex-2020.9.27-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc"}, + {file = "regex-2020.9.27-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b"}, + {file = "regex-2020.9.27-cp36-cp36m-win32.whl", hash = "sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63"}, + {file = "regex-2020.9.27-cp36-cp36m-win_amd64.whl", hash = "sha256:9bc13e0d20b97ffb07821aa3e113f9998e84994fe4d159ffa3d3a9d1b805043b"}, + {file = "regex-2020.9.27-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1b3afc574a3db3b25c89161059d857bd4909a1269b0b3cb3c904677c8c4a3f7"}, + {file = "regex-2020.9.27-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc"}, + {file = "regex-2020.9.27-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c"}, + {file = "regex-2020.9.27-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100"}, + {file = "regex-2020.9.27-cp37-cp37m-win32.whl", hash = "sha256:eda4771e0ace7f67f58bc5b560e27fb20f32a148cbc993b0c3835970935c2707"}, + {file = "regex-2020.9.27-cp37-cp37m-win_amd64.whl", hash = "sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab"}, + {file = "regex-2020.9.27-cp38-cp38-manylinux1_i686.whl", hash = "sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef"}, + {file = "regex-2020.9.27-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121"}, + {file = "regex-2020.9.27-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b"}, + {file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"}, + {file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"}, + {file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"}, + {file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"}, ] requests = [ {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, @@ -1107,8 +1088,8 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, ] stevedore = [ - {file = "stevedore-3.2.0-py3-none-any.whl", hash = "sha256:c8f4f0ebbc394e52ddf49de8bcc3cf8ad2b4425ebac494106bbc5e3661ac7633"}, - {file = "stevedore-3.2.0.tar.gz", hash = "sha256:38791aa5bed922b0a844513c5f9ed37774b68edc609e5ab8ab8d8fe0ce4315e5"}, + {file = "stevedore-3.2.2-py3-none-any.whl", hash = "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62"}, + {file = "stevedore-3.2.2.tar.gz", hash = "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0"}, ] toml = [ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, @@ -1142,14 +1123,10 @@ urllib3 = [ {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] virtualenv = [ - {file = "virtualenv-20.0.30-py2.py3-none-any.whl", hash = "sha256:8cd7b2a4850b003a11be2fc213e206419efab41115cc14bca20e69654f2ac08e"}, - {file = "virtualenv-20.0.30.tar.gz", hash = "sha256:7b54fd606a1b85f83de49ad8d80dbec08e983a2d2f96685045b262ebc7481ee5"}, -] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, + {file = "virtualenv-20.0.31-py2.py3-none-any.whl", hash = "sha256:e0305af10299a7fb0d69393d8f04cb2965dda9351140d11ac8db4e5e3970451b"}, + {file = "virtualenv-20.0.31.tar.gz", hash = "sha256:43add625c53c596d38f971a465553f6318decc39d98512bc100fa1b1e839c8dc"}, ] zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, + {file = "zipp-3.2.0-py3-none-any.whl", hash = "sha256:43f4fa8d8bb313e65d8323a3952ef8756bf40f9a5c3ea7334be23ee4ec8278b6"}, + {file = "zipp-3.2.0.tar.gz", hash = "sha256:b52f22895f4cfce194bc8172f3819ee8de7540aa6d873535a8668b730b8b411f"}, ] diff --git a/pyproject.toml b/pyproject.toml index d65d5859..e7f6367f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scanapi" -version = "2.0.0" +version = "2.1.0" description = "Automated Testing and Documentation for your REST API" authors = ["Camila Maia "] license = "MIT" @@ -20,12 +20,11 @@ curlify = "2.2.1" jinja2 = "2.11.2" pyyaml = "5.3.1" requests = "2.24.0" -bandit = "^1.6.2" appdirs = "^1.4.4" [tool.poetry.dev-dependencies] codecov = "2.1.7" -pytest = "5.4.3" +pytest = "6.1.0" pytest-cov = "2.10.0" pytest-freezegun = "0.4.1" pytest-mock = "3.2.0" @@ -34,6 +33,15 @@ black = "19.10b0" sphinx = "3.1.2" sphinx_rtd_theme = "^0.5.0" pre-commit = "2.6.0" +isort = "^5.5.3" +bandit = "^1.6.2" + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true + +[tool.pytest.ini_options] +addopts="-svvl" [build-system] requires = ["poetry>=1.0.9"] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 12334b3a..00000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts=-svvl diff --git a/scanapi/__main__.py b/scanapi/__main__.py index 12fef833..f35b5225 100644 --- a/scanapi/__main__.py +++ b/scanapi/__main__.py @@ -1,5 +1,6 @@ -import click import logging + +import click import yaml from scanapi.exit_code import ExitCode diff --git a/scanapi/config_loader.py b/scanapi/config_loader.py index 3cc16b45..0a90996c 100644 --- a/scanapi/config_loader.py +++ b/scanapi/config_loader.py @@ -2,9 +2,10 @@ Code based on solution https://gist.github.com/joshbode/569627ced3076931b02f """ -from typing import Any, IO import logging import os +from typing import IO, Any + import yaml from scanapi.errors import EmptyConfigFileError diff --git a/scanapi/evaluators/code_evaluator.py b/scanapi/evaluators/code_evaluator.py index 39d26e5e..68f39dd2 100644 --- a/scanapi/evaluators/code_evaluator.py +++ b/scanapi/evaluators/code_evaluator.py @@ -1,15 +1,14 @@ -import logging -import re - -from scanapi.errors import InvalidPythonCodeError - # Available imports to be used dinamically in the API spec import datetime +import logging import math import random +import re import time import uuid +from scanapi.errors import InvalidPythonCodeError + logger = logging.getLogger(__name__) diff --git a/scanapi/evaluators/spec_evaluator.py b/scanapi/evaluators/spec_evaluator.py index 6ab9e7e6..329e60d5 100644 --- a/scanapi/evaluators/spec_evaluator.py +++ b/scanapi/evaluators/spec_evaluator.py @@ -1,5 +1,5 @@ -from functools import singledispatch import logging +from functools import singledispatch from scanapi.evaluators.string_evaluator import StringEvaluator diff --git a/scanapi/evaluators/string_evaluator.py b/scanapi/evaluators/string_evaluator.py index 060f9b65..fa971e71 100644 --- a/scanapi/evaluators/string_evaluator.py +++ b/scanapi/evaluators/string_evaluator.py @@ -1,7 +1,6 @@ import logging import os import re -import sys from scanapi.errors import BadConfigurationError from scanapi.evaluators.code_evaluator import CodeEvaluator @@ -11,7 +10,7 @@ class StringEvaluator: variable_pattern = re.compile( - r"(?P\w*)(?P\${)(?P\w*)(?P})(?P\w*)" + r"(?P\w*)(?P\${)(?P[\w|-]*)(?P})(?P\w*)" ) # ${} @classmethod diff --git a/scanapi/reporter.py b/scanapi/reporter.py index 4c9cf4b5..ca7da632 100644 --- a/scanapi/reporter.py +++ b/scanapi/reporter.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import datetime import logging +from os.path import abspath from scanapi.session import session from scanapi.settings import settings @@ -30,7 +31,7 @@ def write(self, results): doc.write(content) logger.info("\nThe documentation was generated successfully.") - logger.info(f"It is available at {self.output_path}") + logger.info(f"It is available at {abspath(self.output_path)}") @staticmethod def _build_context(results): diff --git a/scanapi/scan.py b/scanapi/scan.py index a9482f28..ec7a7c7f 100644 --- a/scanapi/scan.py +++ b/scanapi/scan.py @@ -1,4 +1,5 @@ import logging + import yaml from scanapi.config_loader import load_config_file diff --git a/scanapi/session.py b/scanapi/session.py index e1b48a1f..e840acfb 100644 --- a/scanapi/session.py +++ b/scanapi/session.py @@ -1,5 +1,4 @@ import sys - from datetime import datetime from scanapi.exit_code import ExitCode diff --git a/scanapi/settings.py b/scanapi/settings.py index cd182b3f..0fbaf330 100644 --- a/scanapi/settings.py +++ b/scanapi/settings.py @@ -1,9 +1,9 @@ import os + import appdirs from scanapi.config_loader import load_config_file - GLOBAL_CONFIG_PATH = os.path.join(appdirs.site_config_dir("scanapi"), "scanapi.conf",) LOCAL_CONFIG_PATH = "./scanapi.conf" diff --git a/scanapi/template_render.py b/scanapi/template_render.py index b4678d07..be095834 100644 --- a/scanapi/template_render.py +++ b/scanapi/template_render.py @@ -1,5 +1,4 @@ import curlify - from jinja2 import Environment, FileSystemLoader, PackageLoader diff --git a/scanapi/templates/report.html b/scanapi/templates/report.html index b3e134da..09b04039 100644 --- a/scanapi/templates/report.html +++ b/scanapi/templates/report.html @@ -43,6 +43,7 @@ details { margin-top: 20px; + position: relative; } details div { @@ -320,18 +321,62 @@ } /* END TESTS SUMMARY */ + /* START RENDERJSON */ - .renderjson { + .json-body{ + padding: 0; + margin: 0; + } + /* END RENDERJSON */ + + /* START SNIPPET CONTENT */ + .snippet__content{ background-color: #eeeeee; white-space: break-spaces; - padding: 10px; + padding: 20px 40px 20px 10px; + font-size: 16px; } - .renderjson a { + .snippet__content a{ text-decoration: none; } - /* END RENDERJSON */ + .copy__snippet{ + margin: 0; + padding: 0; + border: none; + } + + .copy__snippet{ + position: absolute; + top: 42px; + right: 10px; + background-color: #C6C4C4; + border-radius: 2px; + font-size: 18px; + width: 25px; + height: 23px; + cursor: pointer; + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); + } + + .copy__snippet:hover{ + background-color: #D6D8D6; + } + + .copy__snippet:focus{ + outline: none; + } + + .bg_green{ + background-color: green!important; + color: white; + } + /* END SNIPPET CONTENT */
@@ -410,7 +455,8 @@

Request

cURL -

{{request|curlify}}

+
{{request|curlify}}
+
@@ -479,6 +525,7 @@

Response

{% if response.text %} {{ response.text }} {% else %} {{ response.content }} {% endif %}

+ {% endif %} @@ -549,8 +596,54 @@

Tests Summary

if (parsed_json) { all_json_bodies[i].innerText = ''; all_json_bodies[i].appendChild(renderjson(parsed_json)); + all_json_bodies[i].firstElementChild.setAttribute('data-content', raw_json); } } + /* + * Adding class name of snippet__content to all pre elements + */ + var pre_elements = document.querySelectorAll('pre'); + + for (var i = 0; i < pre_elements.length; i++){ + pre_elements[i].classList.add('snippet__content'); + } + /* + * Copying data to clipboard + */ + var copy_snippet_button = document.querySelectorAll('.copy__snippet'); + + var copyToClipBoard = function(value, button) { + if ( value ){ + navigator.clipboard.writeText(value) + .then(function(){ + console.log('Copied!'); + console.log(value) + button.classList.add('bg_green') + + setTimeout(function(){ + button.classList.remove('bg_green') + }, 180); + }) + .catch(function(err){ + console.log('Something went wrong', err); + }) + } + } + + copy_snippet_button.forEach(function(elem){ + elem.addEventListener('click', function(evt){ + var sibling_element = this.previousElementSibling; + var _this_button = this; + if ( sibling_element.className == 'snippet__content' ){ + var content = sibling_element.textContent.trim(); + copyToClipBoard(content, _this_button) + } else { + var child_element = sibling_element.children[0]; + var content = child_element.getAttribute('data-content').trim() + copyToClipBoard(content, _this_button) + } + }) + }) diff --git a/scanapi/tree/endpoint_node.py b/scanapi/tree/endpoint_node.py index b135df29..655fb6fd 100644 --- a/scanapi/tree/endpoint_node.py +++ b/scanapi/tree/endpoint_node.py @@ -1,10 +1,12 @@ -from itertools import chain import logging +from itertools import chain from scanapi.evaluators import SpecEvaluator from scanapi.exit_code import ExitCode from scanapi.session import session +from scanapi.tree.request_node import RequestNode from scanapi.tree.tree_keys import ( + DELAY_KEY, ENDPOINTS_KEY, HEADERS_KEY, NAME_KEY, @@ -14,7 +16,6 @@ ROOT_SCOPE, VARS_KEY, ) -from scanapi.tree.request_node import RequestNode from scanapi.utils import join_urls, validate_keys logger = logging.getLogger(__name__) @@ -29,6 +30,7 @@ class EndpointNode: PARAMS_KEY, PATH_KEY, REQUESTS_KEY, + DELAY_KEY, ) REQUIRED_KEYS = (NAME_KEY,) ROOT_REQUIRED_KEYS = () @@ -74,6 +76,11 @@ def headers(self): def params(self): return self._get_specs(PARAMS_KEY) + @property + def delay(self): + delay = self.spec.get(DELAY_KEY, 0) + return delay or getattr(self.parent, DELAY_KEY, 0) + @property def is_root(self): return not self.parent diff --git a/scanapi/tree/request_node.py b/scanapi/tree/request_node.py index 806f9561..106514e1 100644 --- a/scanapi/tree/request_node.py +++ b/scanapi/tree/request_node.py @@ -1,12 +1,16 @@ import logging +import time + import requests from scanapi.errors import HTTPMethodNotAllowedError from scanapi.evaluators.spec_evaluator import SpecEvaluator +from scanapi.hide_utils import hide_sensitive_info from scanapi.test_status import TestStatus from scanapi.tree.testing_node import TestingNode from scanapi.tree.tree_keys import ( BODY_KEY, + DELAY_KEY, HEADERS_KEY, METHOD_KEY, NAME_KEY, @@ -16,7 +20,6 @@ VARS_KEY, ) from scanapi.utils import join_urls, validate_keys -from scanapi.hide_utils import hide_sensitive_info logger = logging.getLogger(__name__) @@ -32,6 +35,7 @@ class RequestNode: PATH_KEY, TESTS_KEY, VARS_KEY, + DELAY_KEY, ) ALLOWED_HTTP_METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE") REQUIRED_KEYS = (NAME_KEY,) @@ -81,9 +85,14 @@ def params(self): return self.endpoint.vars.evaluate({**endpoint_params, **params}) + @property + def delay(self): + delay = self.spec.get(DELAY_KEY, 0) + return delay or self.endpoint.delay + @property def body(self): - body = self.spec.get(BODY_KEY, {}) + body = self.spec.get(BODY_KEY) return self.endpoint.vars.evaluate(body) @@ -92,6 +101,8 @@ def tests(self): return (TestingNode(spec, self) for spec in self.spec.get("tests", [])) def run(self): + time.sleep(self.delay / 1000) + method = self.http_method url = self.full_url_path logger.info("Making request %s %s", method, url) diff --git a/scanapi/tree/testing_node.py b/scanapi/tree/testing_node.py index 3dbf838c..1dfcda30 100644 --- a/scanapi/tree/testing_node.py +++ b/scanapi/tree/testing_node.py @@ -1,9 +1,10 @@ import logging + import requests -from scanapi.test_status import TestStatus from scanapi.session import session -from scanapi.tree.tree_keys import NAME_KEY, ASSERT_KEY +from scanapi.test_status import TestStatus +from scanapi.tree.tree_keys import ASSERT_KEY, NAME_KEY from scanapi.utils import validate_keys logger = logging.getLogger(__name__) diff --git a/scanapi/tree/tree_keys.py b/scanapi/tree/tree_keys.py index 2fc02018..b62d5b80 100644 --- a/scanapi/tree/tree_keys.py +++ b/scanapi/tree/tree_keys.py @@ -10,3 +10,4 @@ ROOT_SCOPE = "root" TESTS_KEY = "tests" VARS_KEY = "vars" +DELAY_KEY = "delay" diff --git a/tests/unit/evaluators/test_code_evaluator.py b/tests/unit/evaluators/test_code_evaluator.py index d8cdf5d8..7f127cad 100644 --- a/tests/unit/evaluators/test_code_evaluator.py +++ b/tests/unit/evaluators/test_code_evaluator.py @@ -1,4 +1,5 @@ import os + import pytest import requests diff --git a/tests/unit/evaluators/test_spec_evaluator.py b/tests/unit/evaluators/test_spec_evaluator.py index bb8eafd8..5acf39e1 100644 --- a/tests/unit/evaluators/test_spec_evaluator.py +++ b/tests/unit/evaluators/test_spec_evaluator.py @@ -1,4 +1,5 @@ import os + import pytest from scanapi.errors import BadConfigurationError, InvalidPythonCodeError @@ -15,15 +16,25 @@ def mock_string_evaluate(self, mocker): @pytest.fixture def spec_evaluator(self): - endpoint = EndpointNode({"name": "foo", "requests": [{}]}) - return SpecEvaluator(endpoint) + parent = EndpointNode({"name": "bar", "requests": [{}]}) + endpoint = EndpointNode({"name": "foo", "requests": [{}]}, parent) + return SpecEvaluator(endpoint, {"name": "foo"}) class TestEvaluateString: - def test_should_call_evaluate_dict(self, spec_evaluator, mock_string_evaluate): + def test_should_call_evaluate_string( + self, spec_evaluator, mock_string_evaluate + ): string = "foo" spec_evaluator.evaluate(string) assert mock_string_evaluate.called_once_with(string) + def test_should_call_evaluate_assertion_string( + self, spec_evaluator, mock_string_evaluate + ): + string = "foo" + spec_evaluator.evaluate_assertion(string) + assert mock_string_evaluate.called_once_with(string) + class TestEvaluateDict: class TestWhenDictIsEmpty: def test_return_empty_dict(self, spec_evaluator): @@ -67,3 +78,14 @@ def test_return_evaluated_list( mocker.call("bar", spec_evaluator, False), ] ) + + class TestSpecEvaluatorGetKey: + def test_should_return_none(self, spec_evaluator): + key = "some_key" + value = spec_evaluator.get(key) + assert value == None + + def test_should_return_foo(self, spec_evaluator): + key = "name" + value = spec_evaluator.get(key) + assert value == "foo" diff --git a/tests/unit/evaluators/test_string_evaluator.py b/tests/unit/evaluators/test_string_evaluator.py index 68e0140e..fc740e80 100644 --- a/tests/unit/evaluators/test_string_evaluator.py +++ b/tests/unit/evaluators/test_string_evaluator.py @@ -1,4 +1,5 @@ import os + import pytest from scanapi.errors import BadConfigurationError @@ -99,6 +100,7 @@ class TestWhenMatchesThePattern: class TestWhenCodeDoesNotContainThePreSavedCustomVar: test_data = [ ("${user_id}"), + ("${user-id}"), ("something before ${user_id} something after"), ("something before ${user_id}"), ("${user_id} something after"), @@ -114,6 +116,7 @@ class TestWhenCodeContainsThePreSavedCustomVar: test_data = [ ("${user_id}", "10"), ("${apiKey}", "abc123"), + ("${api-token}", "xwo"), ("something before ${user_id}", "something before 10"), ( "something before ${user_id} something after", @@ -124,7 +127,7 @@ class TestWhenCodeContainsThePreSavedCustomVar: @pytest.mark.parametrize("sequence, expected", test_data) def test_should_return_sequence(self, sequence, expected): - vars = {"user_id": "10", "apiKey": "abc123"} + vars = {"user_id": "10", "apiKey": "abc123", "api-token": "xwo"} assert ( StringEvaluator._evaluate_custom_var(sequence, vars) == expected ) @@ -132,6 +135,7 @@ def test_should_return_sequence(self, sequence, expected): class TestReplaceVarWithValue: test_data = [ ("${age}", "${age}", "45", "45"), + ("${USER-AGE}", "${USER-AGE}", "45", "45"), ("I am ${age} years old", "${age}", "60", "I am 60 years old"), ( "url/${{some['python_code']!}}", diff --git a/tests/unit/test_config_loader.py b/tests/unit/test_config_loader.py index 26d70dae..7eb4b2da 100644 --- a/tests/unit/test_config_loader.py +++ b/tests/unit/test_config_loader.py @@ -1,8 +1,8 @@ import pytest import yaml -from scanapi.errors import EmptyConfigFileError from scanapi.config_loader import load_config_file +from scanapi.errors import EmptyConfigFileError class TestLoadConfigFile: diff --git a/tests/unit/test_hide_utils.py b/tests/unit/test_hide_utils.py index a1081096..8efc9791 100644 --- a/tests/unit/test_hide_utils.py +++ b/tests/unit/test_hide_utils.py @@ -1,7 +1,7 @@ import pytest import requests -from scanapi.hide_utils import hide_sensitive_info, _hide, _override_info +from scanapi.hide_utils import _hide, _override_info, hide_sensitive_info @pytest.fixture diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index e651b151..2c11d0da 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1,8 +1,9 @@ -from click.testing import CliRunner import logging import os + import pytest import yaml +from click.testing import CliRunner from scanapi.__main__ import run diff --git a/tests/unit/test_reporter.py b/tests/unit/test_reporter.py index 56b13849..b81db504 100644 --- a/tests/unit/test_reporter.py +++ b/tests/unit/test_reporter.py @@ -1,6 +1,6 @@ import pytest - from freezegun.api import FakeDatetime + from scanapi.reporter import Reporter fake_results = [ diff --git a/tests/unit/test_scan.py b/tests/unit/test_scan.py index 8c591e71..8495575e 100644 --- a/tests/unit/test_scan.py +++ b/tests/unit/test_scan.py @@ -1,14 +1,12 @@ import errno import logging import os + import pytest import requests import yaml -from scanapi.errors import ( - EmptyConfigFileError, - InvalidKeyError, -) +from scanapi.errors import EmptyConfigFileError, InvalidKeyError from scanapi.scan import scan, write_report log = logging.getLogger(__name__) diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 609a4f2d..40846b15 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -1,7 +1,7 @@ -import pytest - from random import randrange +import pytest + from scanapi.session import Session diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 82ac7c9e..68432734 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -1,7 +1,8 @@ -import pytest import os -from scanapi.settings import settings, LOCAL_CONFIG_PATH +import pytest + +from scanapi.settings import LOCAL_CONFIG_PATH, settings @pytest.fixture diff --git a/tests/unit/test_template_render.py b/tests/unit/test_template_render.py index 9bcb6512..ebc58ea0 100644 --- a/tests/unit/test_template_render.py +++ b/tests/unit/test_template_render.py @@ -1,7 +1,6 @@ import pytest -import jinja2 -from scanapi.template_render import render, _loader +from scanapi.template_render import _loader, render class TestTemplateRender: diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 701710a6..6f853154 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -2,11 +2,8 @@ import requests from scanapi.errors import InvalidKeyError, MissingMandatoryKeyError -from scanapi.utils import ( - join_urls, - validate_keys, -) -from scanapi.hide_utils import hide_sensitive_info, _hide, _override_info +from scanapi.hide_utils import _hide, _override_info, hide_sensitive_info +from scanapi.utils import join_urls, validate_keys @pytest.fixture diff --git a/tests/unit/tree/test_endpoint_node.py b/tests/unit/tree/test_endpoint_node.py index 84b16cc3..52bce30b 100644 --- a/tests/unit/tree/test_endpoint_node.py +++ b/tests/unit/tree/test_endpoint_node.py @@ -1,7 +1,7 @@ import pytest -from scanapi.tree import EndpointNode from scanapi.errors import MissingMandatoryKeyError +from scanapi.tree import EndpointNode class TestEndpointNode: @@ -156,6 +156,28 @@ def test_when_parent_has_repeated_keys(self): ) assert node.params == {"abc": "def", "xxx": "www"} + class TestDelay: + def test_when_node_has_no_delay(self): + node = EndpointNode({"name": "node"}) + assert node.delay == 0 + + def test_when_node_has_delay(self): + node = EndpointNode({"name": "node", "delay": 1}) + assert node.delay == 1 + + def test_when_parent_has_delay(self): + node = EndpointNode( + {"name": "node"}, parent=EndpointNode({"name": "parent", "delay": 2}) + ) + assert node.delay == 2 + + def test_when_both_node_and_parent_have_delay(self): + node = EndpointNode( + {"name": "node", "delay": 3}, + parent=EndpointNode({"name": "parent", "delay": 4}), + ) + assert node.delay == 3 + class TestRun: pass # TODO @@ -177,7 +199,7 @@ def test_should_call_validate_keys(self, mock_validate_keys): mock_validate_keys.assert_called_with( keys, - ("endpoints", "headers", "name", "params", "path", "requests"), + ("endpoints", "headers", "name", "params", "path", "requests", "delay"), ("name",), "endpoint", ) diff --git a/tests/unit/tree/test_request_node.py b/tests/unit/tree/test_request_node.py index d56a7426..73d3f95c 100644 --- a/tests/unit/tree/test_request_node.py +++ b/tests/unit/tree/test_request_node.py @@ -204,15 +204,41 @@ def test_calls_evaluate(self, mocker, mock_evaluate): mock_evaluate.assert_has_calls(calls) + class TestDelay: + def test_when_request_has_no_delay(self): + request = RequestNode( + {"name": "foo"}, endpoint=EndpointNode({"name": "bar"}) + ) + assert request.delay == 0 + + def test_when_request_has_delay(self): + request = RequestNode( + {"name": "foo", "delay": 1}, endpoint=EndpointNode({"name": "bar"}) + ) + assert request.delay == 1 + + def test_when_endpoint_has_delay(self): + request = RequestNode( + {"name": "foo"}, endpoint=EndpointNode({"name": "bar", "delay": 2}) + ) + assert request.delay == 2 + + def test_when_both_request_and_endpoint_have_delay(self): + request = RequestNode( + {"name": "foo", "delay": 3}, + endpoint=EndpointNode({"name": "bar", "delay": 4}), + ) + assert request.delay == 3 + class TestBody: def test_when_request_has_no_body(self): request = RequestNode( {"path": "http://foo.com", "name": "foo"}, endpoint=EndpointNode({"name": "foo", "requests": [{}]}), ) - assert request.body == {} + assert request.body is None - def test_when_request_has_no_body(self): + def test_when_request_has_body(self): request = RequestNode( {"body": {"abc": "def"}, "path": "http://foo.com", "name": "foo",}, endpoint=EndpointNode({"name": "foo", "requests": [{}]}), @@ -238,13 +264,19 @@ def mock_request(self, mocker): def mock_run_tests(self, mocker): return mocker.patch("scanapi.tree.request_node.RequestNode._run_tests") - def test_calls_request(self, mock_request): + @pytest.fixture + def mock_time_sleep(self, mocker): + return mocker.patch("scanapi.tree.request_node.time.sleep") + + def test_calls_request(self, mock_request, mock_time_sleep): request = RequestNode( {"path": "http://foo.com", "name": "foo"}, - endpoint=EndpointNode({"name": "foo", "requests": [{}]}), + endpoint=EndpointNode({"name": "foo", "requests": [{}], "delay": 1}), ) result = request.run() + mock_time_sleep.assert_called_once_with(0.001) + mock_request.assert_called_once_with( request.http_method, request.full_url_path, @@ -307,6 +339,7 @@ def test_should_call_validate_keys(self, mock_validate_keys): "path", "tests", "vars", + "delay", ), ("name",), "request", diff --git a/tests/unit/tree/test_testing_node.py b/tests/unit/tree/test_testing_node.py index f55318c6..10ecf906 100644 --- a/tests/unit/tree/test_testing_node.py +++ b/tests/unit/tree/test_testing_node.py @@ -1,4 +1,5 @@ import logging + import pytest from scanapi.errors import MissingMandatoryKeyError