├── .flake8 ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── bin ├── ci ├── console ├── docs ├── format ├── setup └── test ├── mypy.ini ├── poetry.lock ├── pydactory ├── __init__.py ├── errors.py ├── fake.py ├── gen.py ├── params.py ├── pydactory.py └── types.py ├── pyproject.toml └── tests ├── __init__.py ├── console.py ├── support.py └── test_pydactory.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501,W503,E203,E731 3 | select = E,W,F,N 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | python-version: [3.7, 3.8, 3.9] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v1 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | 25 | - name: Install Poetry 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install poetry 29 | 30 | - name: Test 31 | run: bin/ci 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.egg-info/ 3 | /.vscode/ 4 | /.mypy_cache/ 5 | /.coverage 6 | /htmlcov/ 7 | /docs/html/ 8 | /docs/stubs/ 9 | /test.db 10 | /console.db 11 | .pytest_cache/ 12 | dist 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 Richard Howard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `pydactory` 2 | 3 | `pydactory` is a factory library for [`pydantic`](https://github.com/samuelcolvin/pydantic/) models with an API inspired by [`factory_boy`](https://github.com/FactoryBoy/factory_boy). 4 | 5 | ## Installation 6 | 7 | PyPI: https://pypi.org/project/pydactory/ 8 | 9 | `pip install pydactory` 10 | 11 | ## Features 12 | 13 | `pydactory` is... 14 | 15 | **low boilerplate**: provides default values for many common types. You don't need to tell `pydactory` how to build your `name: str` fields 16 | 17 | **familiar**: define your factories like you define your `pydantic` models: in a simple, declarative syntax 18 | 19 | ## Getting started 20 | 21 | ### Declare your `pydantic` models 22 | 23 | ```python 24 | from datetime import datetime 25 | from typing import Optional 26 | 27 | from pydantic import BaseModel, Field 28 | 29 | 30 | class Address(BaseModel): 31 | street1: str 32 | street2: str 33 | city: str 34 | state: str 35 | zip_code: str = Field(max_length=5) 36 | 37 | 38 | class Author(BaseModel): 39 | name: str 40 | address: Address 41 | date_of_birth: datetime 42 | 43 | 44 | class Book(BaseModel): 45 | title: str = Field(alias="Title") 46 | author: Author = Field(alias="Author") 47 | pages: int = Field(alias="PageCount") 48 | publish_date: datetime = Field(alias="PublishDate") 49 | isbn_13: str = Field(alias="ISBN-13") 50 | isbn_10: Optional[str] = Field(alias="ISBN-10") 51 | ``` 52 | 53 | ### Declare your factories 54 | 55 | ```python 56 | from pydactory import Factory 57 | 58 | 59 | class AuthorFactory(Factory[Author]): 60 | name = "Leo Tolstoy" 61 | 62 | 63 | class BookFactory(Factory[Book]): 64 | title = "War and Peace" 65 | author = AuthorFactory 66 | publish_date = datetime.today 67 | ``` 68 | 69 | ### Use the factories to build your models 70 | 71 | ```python 72 | def test_book_factory(): 73 | book: Book = BookFactory.build(title="Anna Karenina") 74 | assert Book( 75 | title="Anna Karenina", 76 | author=Author( 77 | name="Leo Tolstoy", 78 | address=Address( 79 | street1="fake", street2="fake", city="fake", state="fake", zip_code="fake" 80 | ), 81 | date_of_birth=datetime.datetime(2000, 1, 1, 0, 0), 82 | ), 83 | pages=1, 84 | publish_date=datetime.datetime(2021, 3, 26, 14, 15, 22, 613309), 85 | isbn_13="fake", 86 | isbn_10=None, 87 | ) == book 88 | ``` 89 | 90 | ## Roadmap 91 | 92 | `pydactory` is still very much in progress. 93 | -------------------------------------------------------------------------------- /bin/ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | say() { 6 | printf "\e[33m$1\e[0m\n" 7 | } 8 | 9 | say "==>> Installing dependencies..." 10 | poetry install -n 11 | 12 | say "\n==>> Checking formatting..." 13 | poetry run isort --check . 14 | poetry run black --check . 15 | 16 | say "\n==>> Linting..." 17 | poetry run flake8 pydactory 18 | 19 | say "\n==>> Typechecking..." 20 | poetry run mypy pydactory tests --pretty 21 | 22 | say "\n==>> Testing..." 23 | poetry run pytest --cov pydactory --cov-fail-under=90 24 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | poetry run python tests/console.py 6 | -------------------------------------------------------------------------------- /bin/docs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | poetry run sphinx-build -W -b html docs docs/html 6 | -------------------------------------------------------------------------------- /bin/format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | say() { 6 | printf "\e[33m$1\e[0m\n" 7 | } 8 | 9 | say "==>> Formatting..." 10 | poetry run isort . 11 | poetry run black . 12 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | say() { 6 | printf "\e[33m$1\e[0m\n" 7 | } 8 | 9 | say "==>> Installing dependencies..." 10 | poetry install 11 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | say() { 6 | printf "\e[33m$1\e[0m\n" 7 | } 8 | 9 | say "==>> Formatting..." 10 | poetry run isort . 11 | poetry run black . 12 | 13 | say "\n==>> Linting..." 14 | poetry run flake8 pydactory tests 15 | 16 | say "\n==>> Typechecking..." 17 | poetry run mypy pydactory tests --pretty 18 | 19 | say "\n==>> Testing..." 20 | poetry run pytest "$@" --cov pydactory --cov-report html 21 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | no_implicit_optional = True 4 | warn_unreachable = True 5 | warn_unused_ignores = True 6 | warn_redundant_casts = True 7 | allow_redefinition = True 8 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "alabaster" 3 | version = "0.7.12" 4 | description = "A configurable sidebar-enabled Sphinx theme" 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "appdirs" 11 | version = "1.4.4" 12 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 13 | category = "dev" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "appnope" 19 | version = "0.1.0" 20 | description = "Disable App Nap on OS X 10.9" 21 | category = "dev" 22 | optional = false 23 | python-versions = "*" 24 | 25 | [[package]] 26 | name = "atomicwrites" 27 | version = "1.4.0" 28 | description = "Atomic file writes." 29 | category = "dev" 30 | optional = false 31 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 32 | 33 | [[package]] 34 | name = "attrs" 35 | version = "20.3.0" 36 | description = "Classes Without Boilerplate" 37 | category = "dev" 38 | optional = false 39 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 40 | 41 | [package.extras] 42 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 43 | docs = ["furo", "sphinx", "zope.interface"] 44 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 45 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 46 | 47 | [[package]] 48 | name = "babel" 49 | version = "2.9.0" 50 | description = "Internationalization utilities" 51 | category = "dev" 52 | optional = false 53 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 54 | 55 | [package.dependencies] 56 | pytz = ">=2015.7" 57 | 58 | [[package]] 59 | name = "backcall" 60 | version = "0.2.0" 61 | description = "Specifications for callback functions passed in to an API" 62 | category = "dev" 63 | optional = false 64 | python-versions = "*" 65 | 66 | [[package]] 67 | name = "black" 68 | version = "20.8b1" 69 | description = "The uncompromising code formatter." 70 | category = "dev" 71 | optional = false 72 | python-versions = ">=3.6" 73 | 74 | [package.dependencies] 75 | appdirs = "*" 76 | click = ">=7.1.2" 77 | mypy-extensions = ">=0.4.3" 78 | pathspec = ">=0.6,<1" 79 | regex = ">=2020.1.8" 80 | toml = ">=0.10.1" 81 | typed-ast = ">=1.4.0" 82 | typing-extensions = ">=3.7.4" 83 | 84 | [package.extras] 85 | colorama = ["colorama (>=0.4.3)"] 86 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 87 | 88 | [[package]] 89 | name = "certifi" 90 | version = "2020.11.8" 91 | description = "Python package for providing Mozilla's CA Bundle." 92 | category = "dev" 93 | optional = false 94 | python-versions = "*" 95 | 96 | [[package]] 97 | name = "chardet" 98 | version = "3.0.4" 99 | description = "Universal encoding detector for Python 2 and 3" 100 | category = "dev" 101 | optional = false 102 | python-versions = "*" 103 | 104 | [[package]] 105 | name = "click" 106 | version = "7.1.2" 107 | description = "Composable command line interface toolkit" 108 | category = "dev" 109 | optional = false 110 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 111 | 112 | [[package]] 113 | name = "colorama" 114 | version = "0.4.4" 115 | description = "Cross-platform colored terminal text." 116 | category = "dev" 117 | optional = false 118 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 119 | 120 | [[package]] 121 | name = "coverage" 122 | version = "5.3" 123 | description = "Code coverage measurement for Python" 124 | category = "dev" 125 | optional = false 126 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 127 | 128 | [package.extras] 129 | toml = ["toml"] 130 | 131 | [[package]] 132 | name = "decorator" 133 | version = "4.4.2" 134 | description = "Decorators for Humans" 135 | category = "dev" 136 | optional = false 137 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 138 | 139 | [[package]] 140 | name = "docutils" 141 | version = "0.16" 142 | description = "Docutils -- Python Documentation Utilities" 143 | category = "dev" 144 | optional = false 145 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 146 | 147 | [[package]] 148 | name = "faker" 149 | version = "4.15.0" 150 | description = "Faker is a Python package that generates fake data for you." 151 | category = "main" 152 | optional = false 153 | python-versions = ">=3.5" 154 | 155 | [package.dependencies] 156 | python-dateutil = ">=2.4" 157 | text-unidecode = "1.3" 158 | 159 | [[package]] 160 | name = "flake8" 161 | version = "3.8.4" 162 | description = "the modular source code checker: pep8 pyflakes and co" 163 | category = "dev" 164 | optional = false 165 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 166 | 167 | [package.dependencies] 168 | mccabe = ">=0.6.0,<0.7.0" 169 | pycodestyle = ">=2.6.0a1,<2.7.0" 170 | pyflakes = ">=2.2.0,<2.3.0" 171 | 172 | [[package]] 173 | name = "idna" 174 | version = "2.10" 175 | description = "Internationalized Domain Names in Applications (IDNA)" 176 | category = "dev" 177 | optional = false 178 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 179 | 180 | [[package]] 181 | name = "imagesize" 182 | version = "1.2.0" 183 | description = "Getting image size from png/jpeg/jpeg2000/gif file" 184 | category = "dev" 185 | optional = false 186 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 187 | 188 | [[package]] 189 | name = "iniconfig" 190 | version = "1.1.1" 191 | description = "iniconfig: brain-dead simple config-ini parsing" 192 | category = "dev" 193 | optional = false 194 | python-versions = "*" 195 | 196 | [[package]] 197 | name = "ipdb" 198 | version = "0.13.4" 199 | description = "IPython-enabled pdb" 200 | category = "dev" 201 | optional = false 202 | python-versions = ">=2.7" 203 | 204 | [package.dependencies] 205 | ipython = {version = ">=5.1.0", markers = "python_version >= \"3.4\""} 206 | 207 | [[package]] 208 | name = "ipython" 209 | version = "7.19.0" 210 | description = "IPython: Productive Interactive Computing" 211 | category = "dev" 212 | optional = false 213 | python-versions = ">=3.7" 214 | 215 | [package.dependencies] 216 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 217 | backcall = "*" 218 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 219 | decorator = "*" 220 | jedi = ">=0.10" 221 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} 222 | pickleshare = "*" 223 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 224 | pygments = "*" 225 | traitlets = ">=4.2" 226 | 227 | [package.extras] 228 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] 229 | doc = ["Sphinx (>=1.3)"] 230 | kernel = ["ipykernel"] 231 | nbconvert = ["nbconvert"] 232 | nbformat = ["nbformat"] 233 | notebook = ["notebook", "ipywidgets"] 234 | parallel = ["ipyparallel"] 235 | qtconsole = ["qtconsole"] 236 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 237 | 238 | [[package]] 239 | name = "ipython-genutils" 240 | version = "0.2.0" 241 | description = "Vestigial utilities from IPython" 242 | category = "dev" 243 | optional = false 244 | python-versions = "*" 245 | 246 | [[package]] 247 | name = "isort" 248 | version = "5.6.4" 249 | description = "A Python utility / library to sort Python imports." 250 | category = "dev" 251 | optional = false 252 | python-versions = ">=3.6,<4.0" 253 | 254 | [package.extras] 255 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 256 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 257 | colors = ["colorama (>=0.4.3,<0.5.0)"] 258 | 259 | [[package]] 260 | name = "jedi" 261 | version = "0.17.2" 262 | description = "An autocompletion tool for Python that can be used for text editors." 263 | category = "dev" 264 | optional = false 265 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 266 | 267 | [package.dependencies] 268 | parso = ">=0.7.0,<0.8.0" 269 | 270 | [package.extras] 271 | qa = ["flake8 (==3.7.9)"] 272 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] 273 | 274 | [[package]] 275 | name = "jinja2" 276 | version = "2.11.2" 277 | description = "A very fast and expressive template engine." 278 | category = "dev" 279 | optional = false 280 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 281 | 282 | [package.dependencies] 283 | MarkupSafe = ">=0.23" 284 | 285 | [package.extras] 286 | i18n = ["Babel (>=0.8)"] 287 | 288 | [[package]] 289 | name = "markupsafe" 290 | version = "1.1.1" 291 | description = "Safely add untrusted strings to HTML/XML markup." 292 | category = "dev" 293 | optional = false 294 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 295 | 296 | [[package]] 297 | name = "mccabe" 298 | version = "0.6.1" 299 | description = "McCabe checker, plugin for flake8" 300 | category = "dev" 301 | optional = false 302 | python-versions = "*" 303 | 304 | [[package]] 305 | name = "mypy" 306 | version = "0.790" 307 | description = "Optional static typing for Python" 308 | category = "dev" 309 | optional = false 310 | python-versions = ">=3.5" 311 | 312 | [package.dependencies] 313 | mypy-extensions = ">=0.4.3,<0.5.0" 314 | typed-ast = ">=1.4.0,<1.5.0" 315 | typing-extensions = ">=3.7.4" 316 | 317 | [package.extras] 318 | dmypy = ["psutil (>=4.0)"] 319 | 320 | [[package]] 321 | name = "mypy-extensions" 322 | version = "0.4.3" 323 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 324 | category = "dev" 325 | optional = false 326 | python-versions = "*" 327 | 328 | [[package]] 329 | name = "packaging" 330 | version = "20.4" 331 | description = "Core utilities for Python packages" 332 | category = "dev" 333 | optional = false 334 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 335 | 336 | [package.dependencies] 337 | pyparsing = ">=2.0.2" 338 | six = "*" 339 | 340 | [[package]] 341 | name = "parso" 342 | version = "0.7.1" 343 | description = "A Python Parser" 344 | category = "dev" 345 | optional = false 346 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 347 | 348 | [package.extras] 349 | testing = ["docopt", "pytest (>=3.0.7)"] 350 | 351 | [[package]] 352 | name = "pathspec" 353 | version = "0.8.1" 354 | description = "Utility library for gitignore style pattern matching of file paths." 355 | category = "dev" 356 | optional = false 357 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 358 | 359 | [[package]] 360 | name = "pexpect" 361 | version = "4.8.0" 362 | description = "Pexpect allows easy control of interactive console applications." 363 | category = "dev" 364 | optional = false 365 | python-versions = "*" 366 | 367 | [package.dependencies] 368 | ptyprocess = ">=0.5" 369 | 370 | [[package]] 371 | name = "pickleshare" 372 | version = "0.7.5" 373 | description = "Tiny 'shelve'-like database with concurrency support" 374 | category = "dev" 375 | optional = false 376 | python-versions = "*" 377 | 378 | [[package]] 379 | name = "pluggy" 380 | version = "0.13.1" 381 | description = "plugin and hook calling mechanisms for python" 382 | category = "dev" 383 | optional = false 384 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 385 | 386 | [package.extras] 387 | dev = ["pre-commit", "tox"] 388 | 389 | [[package]] 390 | name = "prompt-toolkit" 391 | version = "3.0.8" 392 | description = "Library for building powerful interactive command lines in Python" 393 | category = "dev" 394 | optional = false 395 | python-versions = ">=3.6.1" 396 | 397 | [package.dependencies] 398 | wcwidth = "*" 399 | 400 | [[package]] 401 | name = "ptyprocess" 402 | version = "0.6.0" 403 | description = "Run a subprocess in a pseudo terminal" 404 | category = "dev" 405 | optional = false 406 | python-versions = "*" 407 | 408 | [[package]] 409 | name = "py" 410 | version = "1.9.0" 411 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 412 | category = "dev" 413 | optional = false 414 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 415 | 416 | [[package]] 417 | name = "pycodestyle" 418 | version = "2.6.0" 419 | description = "Python style guide checker" 420 | category = "dev" 421 | optional = false 422 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 423 | 424 | [[package]] 425 | name = "pydantic" 426 | version = "1.7.2" 427 | description = "Data validation and settings management using python 3.6 type hinting" 428 | category = "main" 429 | optional = false 430 | python-versions = ">=3.6" 431 | 432 | [package.extras] 433 | dotenv = ["python-dotenv (>=0.10.4)"] 434 | email = ["email-validator (>=1.0.3)"] 435 | typing_extensions = ["typing-extensions (>=3.7.2)"] 436 | 437 | [[package]] 438 | name = "pyflakes" 439 | version = "2.2.0" 440 | description = "passive checker of Python programs" 441 | category = "dev" 442 | optional = false 443 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 444 | 445 | [[package]] 446 | name = "pygments" 447 | version = "2.7.2" 448 | description = "Pygments is a syntax highlighting package written in Python." 449 | category = "dev" 450 | optional = false 451 | python-versions = ">=3.5" 452 | 453 | [[package]] 454 | name = "pyhamcrest" 455 | version = "2.0.2" 456 | description = "Hamcrest framework for matcher objects" 457 | category = "dev" 458 | optional = false 459 | python-versions = ">=3.5" 460 | 461 | [[package]] 462 | name = "pyparsing" 463 | version = "2.4.7" 464 | description = "Python parsing module" 465 | category = "dev" 466 | optional = false 467 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 468 | 469 | [[package]] 470 | name = "pytest" 471 | version = "6.1.2" 472 | description = "pytest: simple powerful testing with Python" 473 | category = "dev" 474 | optional = false 475 | python-versions = ">=3.5" 476 | 477 | [package.dependencies] 478 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 479 | attrs = ">=17.4.0" 480 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 481 | iniconfig = "*" 482 | packaging = "*" 483 | pluggy = ">=0.12,<1.0" 484 | py = ">=1.8.2" 485 | toml = "*" 486 | 487 | [package.extras] 488 | checkqa_mypy = ["mypy (==0.780)"] 489 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 490 | 491 | [[package]] 492 | name = "pytest-cov" 493 | version = "2.10.1" 494 | description = "Pytest plugin for measuring coverage." 495 | category = "dev" 496 | optional = false 497 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 498 | 499 | [package.dependencies] 500 | coverage = ">=4.4" 501 | pytest = ">=4.6" 502 | 503 | [package.extras] 504 | testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] 505 | 506 | [[package]] 507 | name = "pytest-sugar" 508 | version = "0.9.4" 509 | description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." 510 | category = "dev" 511 | optional = false 512 | python-versions = "*" 513 | 514 | [package.dependencies] 515 | packaging = ">=14.1" 516 | pytest = ">=2.9" 517 | termcolor = ">=1.1.0" 518 | 519 | [[package]] 520 | name = "python-dateutil" 521 | version = "2.8.1" 522 | description = "Extensions to the standard Python datetime module" 523 | category = "main" 524 | optional = false 525 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 526 | 527 | [package.dependencies] 528 | six = ">=1.5" 529 | 530 | [[package]] 531 | name = "pytz" 532 | version = "2020.4" 533 | description = "World timezone definitions, modern and historical" 534 | category = "dev" 535 | optional = false 536 | python-versions = "*" 537 | 538 | [[package]] 539 | name = "regex" 540 | version = "2020.11.13" 541 | description = "Alternative regular expression module, to replace re." 542 | category = "dev" 543 | optional = false 544 | python-versions = "*" 545 | 546 | [[package]] 547 | name = "requests" 548 | version = "2.25.0" 549 | description = "Python HTTP for Humans." 550 | category = "dev" 551 | optional = false 552 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 553 | 554 | [package.dependencies] 555 | certifi = ">=2017.4.17" 556 | chardet = ">=3.0.2,<4" 557 | idna = ">=2.5,<3" 558 | urllib3 = ">=1.21.1,<1.27" 559 | 560 | [package.extras] 561 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 562 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 563 | 564 | [[package]] 565 | name = "six" 566 | version = "1.15.0" 567 | description = "Python 2 and 3 compatibility utilities" 568 | category = "main" 569 | optional = false 570 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 571 | 572 | [[package]] 573 | name = "snowballstemmer" 574 | version = "2.0.0" 575 | description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." 576 | category = "dev" 577 | optional = false 578 | python-versions = "*" 579 | 580 | [[package]] 581 | name = "sphinx" 582 | version = "3.3.1" 583 | description = "Python documentation generator" 584 | category = "dev" 585 | optional = false 586 | python-versions = ">=3.5" 587 | 588 | [package.dependencies] 589 | alabaster = ">=0.7,<0.8" 590 | babel = ">=1.3" 591 | colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} 592 | docutils = ">=0.12" 593 | imagesize = "*" 594 | Jinja2 = ">=2.3" 595 | packaging = "*" 596 | Pygments = ">=2.0" 597 | requests = ">=2.5.0" 598 | snowballstemmer = ">=1.1" 599 | sphinxcontrib-applehelp = "*" 600 | sphinxcontrib-devhelp = "*" 601 | sphinxcontrib-htmlhelp = "*" 602 | sphinxcontrib-jsmath = "*" 603 | sphinxcontrib-qthelp = "*" 604 | sphinxcontrib-serializinghtml = "*" 605 | 606 | [package.extras] 607 | docs = ["sphinxcontrib-websupport"] 608 | lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.790)", "docutils-stubs"] 609 | test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"] 610 | 611 | [[package]] 612 | name = "sphinxcontrib-applehelp" 613 | version = "1.0.2" 614 | description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" 615 | category = "dev" 616 | optional = false 617 | python-versions = ">=3.5" 618 | 619 | [package.extras] 620 | lint = ["flake8", "mypy", "docutils-stubs"] 621 | test = ["pytest"] 622 | 623 | [[package]] 624 | name = "sphinxcontrib-devhelp" 625 | version = "1.0.2" 626 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." 627 | category = "dev" 628 | optional = false 629 | python-versions = ">=3.5" 630 | 631 | [package.extras] 632 | lint = ["flake8", "mypy", "docutils-stubs"] 633 | test = ["pytest"] 634 | 635 | [[package]] 636 | name = "sphinxcontrib-htmlhelp" 637 | version = "1.0.3" 638 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" 639 | category = "dev" 640 | optional = false 641 | python-versions = ">=3.5" 642 | 643 | [package.extras] 644 | lint = ["flake8", "mypy", "docutils-stubs"] 645 | test = ["pytest", "html5lib"] 646 | 647 | [[package]] 648 | name = "sphinxcontrib-jsmath" 649 | version = "1.0.1" 650 | description = "A sphinx extension which renders display math in HTML via JavaScript" 651 | category = "dev" 652 | optional = false 653 | python-versions = ">=3.5" 654 | 655 | [package.extras] 656 | test = ["pytest", "flake8", "mypy"] 657 | 658 | [[package]] 659 | name = "sphinxcontrib-qthelp" 660 | version = "1.0.3" 661 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." 662 | category = "dev" 663 | optional = false 664 | python-versions = ">=3.5" 665 | 666 | [package.extras] 667 | lint = ["flake8", "mypy", "docutils-stubs"] 668 | test = ["pytest"] 669 | 670 | [[package]] 671 | name = "sphinxcontrib-serializinghtml" 672 | version = "1.1.4" 673 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." 674 | category = "dev" 675 | optional = false 676 | python-versions = ">=3.5" 677 | 678 | [package.extras] 679 | lint = ["flake8", "mypy", "docutils-stubs"] 680 | test = ["pytest"] 681 | 682 | [[package]] 683 | name = "termcolor" 684 | version = "1.1.0" 685 | description = "ANSII Color formatting for output in terminal." 686 | category = "dev" 687 | optional = false 688 | python-versions = "*" 689 | 690 | [[package]] 691 | name = "text-unidecode" 692 | version = "1.3" 693 | description = "The most basic Text::Unidecode port" 694 | category = "main" 695 | optional = false 696 | python-versions = "*" 697 | 698 | [[package]] 699 | name = "toml" 700 | version = "0.10.2" 701 | description = "Python Library for Tom's Obvious, Minimal Language" 702 | category = "dev" 703 | optional = false 704 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 705 | 706 | [[package]] 707 | name = "traitlets" 708 | version = "5.0.5" 709 | description = "Traitlets Python configuration system" 710 | category = "dev" 711 | optional = false 712 | python-versions = ">=3.7" 713 | 714 | [package.dependencies] 715 | ipython-genutils = "*" 716 | 717 | [package.extras] 718 | test = ["pytest"] 719 | 720 | [[package]] 721 | name = "typed-ast" 722 | version = "1.4.1" 723 | description = "a fork of Python 2 and 3 ast modules with type comment support" 724 | category = "dev" 725 | optional = false 726 | python-versions = "*" 727 | 728 | [[package]] 729 | name = "typing-extensions" 730 | version = "3.7.4.3" 731 | description = "Backported and Experimental Type Hints for Python 3.5+" 732 | category = "dev" 733 | optional = false 734 | python-versions = "*" 735 | 736 | [[package]] 737 | name = "urllib3" 738 | version = "1.26.2" 739 | description = "HTTP library with thread-safe connection pooling, file post, and more." 740 | category = "dev" 741 | optional = false 742 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 743 | 744 | [package.extras] 745 | brotli = ["brotlipy (>=0.6.0)"] 746 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 747 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 748 | 749 | [[package]] 750 | name = "wcwidth" 751 | version = "0.2.5" 752 | description = "Measures the displayed width of unicode strings in a terminal" 753 | category = "dev" 754 | optional = false 755 | python-versions = "*" 756 | 757 | [metadata] 758 | lock-version = "1.1" 759 | python-versions = "^3.8" 760 | content-hash = "52c525d47b9b7c7587355f0591ea470a1486186b1df9bca37449b48b67227702" 761 | 762 | [metadata.files] 763 | alabaster = [ 764 | {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, 765 | {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, 766 | ] 767 | appdirs = [ 768 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 769 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 770 | ] 771 | appnope = [ 772 | {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, 773 | {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, 774 | ] 775 | atomicwrites = [ 776 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 777 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 778 | ] 779 | attrs = [ 780 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 781 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 782 | ] 783 | babel = [ 784 | {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, 785 | {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, 786 | ] 787 | backcall = [ 788 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 789 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 790 | ] 791 | black = [ 792 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 793 | ] 794 | certifi = [ 795 | {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, 796 | {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, 797 | ] 798 | chardet = [ 799 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 800 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 801 | ] 802 | click = [ 803 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 804 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 805 | ] 806 | colorama = [ 807 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 808 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 809 | ] 810 | coverage = [ 811 | {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, 812 | {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, 813 | {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, 814 | {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, 815 | {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, 816 | {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, 817 | {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, 818 | {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, 819 | {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, 820 | {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, 821 | {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, 822 | {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, 823 | {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, 824 | {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, 825 | {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, 826 | {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, 827 | {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, 828 | {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, 829 | {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, 830 | {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, 831 | {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, 832 | {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, 833 | {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, 834 | {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, 835 | {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, 836 | {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, 837 | {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, 838 | {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, 839 | {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, 840 | {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, 841 | {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, 842 | {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, 843 | {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, 844 | {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, 845 | ] 846 | decorator = [ 847 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 848 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 849 | ] 850 | docutils = [ 851 | {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, 852 | {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, 853 | ] 854 | faker = [ 855 | {file = "Faker-4.15.0-py3-none-any.whl", hash = "sha256:2bf1078afc3db2dbab40ed4a353c21179e9703e57e9aa460f3d16ebade21d782"}, 856 | {file = "Faker-4.15.0.tar.gz", hash = "sha256:71eae67c12497da9e64b259306e3f145b6bbda9cc0a70de9f1b8860c3c9fca86"}, 857 | ] 858 | flake8 = [ 859 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 860 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 861 | ] 862 | idna = [ 863 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 864 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 865 | ] 866 | imagesize = [ 867 | {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, 868 | {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, 869 | ] 870 | iniconfig = [ 871 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 872 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 873 | ] 874 | ipdb = [ 875 | {file = "ipdb-0.13.4.tar.gz", hash = "sha256:c85398b5fb82f82399fc38c44fe3532c0dde1754abee727d8f5cfcc74547b334"}, 876 | ] 877 | ipython = [ 878 | {file = "ipython-7.19.0-py3-none-any.whl", hash = "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f"}, 879 | {file = "ipython-7.19.0.tar.gz", hash = "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"}, 880 | ] 881 | ipython-genutils = [ 882 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 883 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 884 | ] 885 | isort = [ 886 | {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, 887 | {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, 888 | ] 889 | jedi = [ 890 | {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, 891 | {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, 892 | ] 893 | jinja2 = [ 894 | {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, 895 | {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, 896 | ] 897 | markupsafe = [ 898 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 899 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 900 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 901 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 902 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 903 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 904 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 905 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 906 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 907 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 908 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 909 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 910 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 911 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 912 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 913 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 914 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 915 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 916 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 917 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 918 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 919 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 920 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 921 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 922 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 923 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 924 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 925 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 926 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 927 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 928 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 929 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 930 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 931 | ] 932 | mccabe = [ 933 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 934 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 935 | ] 936 | mypy = [ 937 | {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, 938 | {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, 939 | {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, 940 | {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, 941 | {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, 942 | {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, 943 | {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, 944 | {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, 945 | {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, 946 | {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, 947 | {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, 948 | {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, 949 | {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, 950 | {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, 951 | ] 952 | mypy-extensions = [ 953 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 954 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 955 | ] 956 | packaging = [ 957 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 958 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 959 | ] 960 | parso = [ 961 | {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, 962 | {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, 963 | ] 964 | pathspec = [ 965 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 966 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 967 | ] 968 | pexpect = [ 969 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 970 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 971 | ] 972 | pickleshare = [ 973 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 974 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 975 | ] 976 | pluggy = [ 977 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 978 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 979 | ] 980 | prompt-toolkit = [ 981 | {file = "prompt_toolkit-3.0.8-py3-none-any.whl", hash = "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"}, 982 | {file = "prompt_toolkit-3.0.8.tar.gz", hash = "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c"}, 983 | ] 984 | ptyprocess = [ 985 | {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, 986 | {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, 987 | ] 988 | py = [ 989 | {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, 990 | {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, 991 | ] 992 | pycodestyle = [ 993 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 994 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 995 | ] 996 | pydantic = [ 997 | {file = "pydantic-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dfaa6ed1d509b5aef4142084206584280bb6e9014f01df931ec6febdad5b200a"}, 998 | {file = "pydantic-1.7.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2182ba2a9290964b278bcc07a8d24207de709125d520efec9ad6fa6f92ee058d"}, 999 | {file = "pydantic-1.7.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:0fe8b45d31ae53d74a6aa0bf801587bd49970070eac6a6326f9fa2a302703b8a"}, 1000 | {file = "pydantic-1.7.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:01f0291f4951580f320f7ae3f2ecaf0044cdebcc9b45c5f882a7e84453362420"}, 1001 | {file = "pydantic-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4ba6b903e1b7bd3eb5df0e78d7364b7e831ed8b4cd781ebc3c4f1077fbcb72a4"}, 1002 | {file = "pydantic-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b11fc9530bf0698c8014b2bdb3bbc50243e82a7fa2577c8cfba660bcc819e768"}, 1003 | {file = "pydantic-1.7.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a3c274c49930dc047a75ecc865e435f3df89715c775db75ddb0186804d9b04d0"}, 1004 | {file = "pydantic-1.7.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:c68b5edf4da53c98bb1ccb556ae8f655575cb2e676aef066c12b08c724a3f1a1"}, 1005 | {file = "pydantic-1.7.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:95d4410c4e429480c736bba0db6cce5aaa311304aea685ebcf9ee47571bfd7c8"}, 1006 | {file = "pydantic-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a2fc7bf77ed4a7a961d7684afe177ff59971828141e608f142e4af858e07dddc"}, 1007 | {file = "pydantic-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9572c0db13c8658b4a4cb705dcaae6983aeb9842248b36761b3fbc9010b740f"}, 1008 | {file = "pydantic-1.7.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f83f679e727742b0c465e7ef992d6da4a7e5268b8edd8fdaf5303276374bef52"}, 1009 | {file = "pydantic-1.7.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:e5fece30e80087d9b7986104e2ac150647ec1658c4789c89893b03b100ca3164"}, 1010 | {file = "pydantic-1.7.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce2d452961352ba229fe1e0b925b41c0c37128f08dddb788d0fd73fd87ea0f66"}, 1011 | {file = "pydantic-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:fc21a37ff3f545de80b166e1735c4172b41b017948a3fb2d5e2f03c219eac50a"}, 1012 | {file = "pydantic-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c9760d1556ec59ff745f88269a8f357e2b7afc75c556b3a87b8dda5bc62da8ba"}, 1013 | {file = "pydantic-1.7.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c1673633ad1eea78b1c5c420a47cd48717d2ef214c8230d96ca2591e9e00958"}, 1014 | {file = "pydantic-1.7.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:388c0c26c574ff49bad7d0fd6ed82fbccd86a0473fa3900397d3354c533d6ebb"}, 1015 | {file = "pydantic-1.7.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ab1d5e4d8de00575957e1c982b951bffaedd3204ddd24694e3baca3332e53a23"}, 1016 | {file = "pydantic-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:f045cf7afb3352a03bc6cb993578a34560ac24c5d004fa33c76efec6ada1361a"}, 1017 | {file = "pydantic-1.7.2-py3-none-any.whl", hash = "sha256:6665f7ab7fbbf4d3c1040925ff4d42d7549a8c15fe041164adfe4fc2134d4cce"}, 1018 | {file = "pydantic-1.7.2.tar.gz", hash = "sha256:c8200aecbd1fb914e1bd061d71a4d1d79ecb553165296af0c14989b89e90d09b"}, 1019 | ] 1020 | pyflakes = [ 1021 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 1022 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 1023 | ] 1024 | pygments = [ 1025 | {file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"}, 1026 | {file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"}, 1027 | ] 1028 | pyhamcrest = [ 1029 | {file = "PyHamcrest-2.0.2-py3-none-any.whl", hash = "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"}, 1030 | {file = "PyHamcrest-2.0.2.tar.gz", hash = "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316"}, 1031 | ] 1032 | pyparsing = [ 1033 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 1034 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 1035 | ] 1036 | pytest = [ 1037 | {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, 1038 | {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, 1039 | ] 1040 | pytest-cov = [ 1041 | {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, 1042 | {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, 1043 | ] 1044 | pytest-sugar = [ 1045 | {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"}, 1046 | ] 1047 | python-dateutil = [ 1048 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 1049 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 1050 | ] 1051 | pytz = [ 1052 | {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, 1053 | {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, 1054 | ] 1055 | regex = [ 1056 | {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, 1057 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, 1058 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, 1059 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, 1060 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, 1061 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, 1062 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, 1063 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, 1064 | {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, 1065 | {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, 1066 | {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, 1067 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, 1068 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, 1069 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, 1070 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, 1071 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, 1072 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, 1073 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, 1074 | {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, 1075 | {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, 1076 | {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, 1077 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, 1078 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, 1079 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, 1080 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, 1081 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, 1082 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, 1083 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, 1084 | {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, 1085 | {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, 1086 | {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, 1087 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, 1088 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, 1089 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, 1090 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, 1091 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, 1092 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, 1093 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, 1094 | {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, 1095 | {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, 1096 | {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, 1097 | ] 1098 | requests = [ 1099 | {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, 1100 | {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, 1101 | ] 1102 | six = [ 1103 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 1104 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 1105 | ] 1106 | snowballstemmer = [ 1107 | {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, 1108 | {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, 1109 | ] 1110 | sphinx = [ 1111 | {file = "Sphinx-3.3.1-py3-none-any.whl", hash = "sha256:d4e59ad4ea55efbb3c05cde3bfc83bfc14f0c95aa95c3d75346fcce186a47960"}, 1112 | {file = "Sphinx-3.3.1.tar.gz", hash = "sha256:1e8d592225447104d1172be415bc2972bd1357e3e12fdc76edf2261105db4300"}, 1113 | ] 1114 | sphinxcontrib-applehelp = [ 1115 | {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, 1116 | {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, 1117 | ] 1118 | sphinxcontrib-devhelp = [ 1119 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, 1120 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, 1121 | ] 1122 | sphinxcontrib-htmlhelp = [ 1123 | {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, 1124 | {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, 1125 | ] 1126 | sphinxcontrib-jsmath = [ 1127 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, 1128 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, 1129 | ] 1130 | sphinxcontrib-qthelp = [ 1131 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, 1132 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, 1133 | ] 1134 | sphinxcontrib-serializinghtml = [ 1135 | {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, 1136 | {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, 1137 | ] 1138 | termcolor = [ 1139 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 1140 | ] 1141 | text-unidecode = [ 1142 | {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, 1143 | {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, 1144 | ] 1145 | toml = [ 1146 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1147 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1148 | ] 1149 | traitlets = [ 1150 | {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, 1151 | {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, 1152 | ] 1153 | typed-ast = [ 1154 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 1155 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 1156 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 1157 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 1158 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 1159 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 1160 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 1161 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 1162 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 1163 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 1164 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 1165 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 1166 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 1167 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 1168 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 1169 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 1170 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 1171 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 1172 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 1173 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 1174 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 1175 | ] 1176 | typing-extensions = [ 1177 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 1178 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 1179 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 1180 | ] 1181 | urllib3 = [ 1182 | {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, 1183 | {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, 1184 | ] 1185 | wcwidth = [ 1186 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 1187 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 1188 | ] 1189 | -------------------------------------------------------------------------------- /pydactory/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import PydactoryError 2 | from .pydactory import Factory, build_model, build_model_batch 3 | 4 | __version__ = "0.2.0" 5 | 6 | __all__ = ["Factory", "PydactoryError", "build_model", "build_model_batch"] 7 | -------------------------------------------------------------------------------- /pydactory/errors.py: -------------------------------------------------------------------------------- 1 | class PydactoryError(Exception): 2 | pass 3 | 4 | 5 | class NoDefaultGeneratorError(Exception): 6 | def __init__(self, key=None, type_=None): 7 | self.key = key 8 | self.type = type_ 9 | self.message = f"Default of type {type_} could not be generated for key {key}." 10 | super().__init__(self.message) 11 | -------------------------------------------------------------------------------- /pydactory/fake.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from faker import Faker 4 | 5 | 6 | class FakeGen: 7 | """ 8 | A wrapper around Faker to integrate it with Pydactory. 9 | """ 10 | 11 | def __init__(self): 12 | self.fake = Faker() 13 | 14 | def __getattr__(self, name: str) -> Any: 15 | """ 16 | Search for a Faker method matching `name` and wrap it in a 17 | function (thunk) to delay its evaluation until generation time. 18 | """ 19 | try: 20 | fake_func = getattr(self.fake, name) 21 | 22 | def fake_func_wrapper(*args, **kwargs): 23 | return lambda _field: fake_func(*args, **kwargs) 24 | 25 | return fake_func_wrapper 26 | except AttributeError: 27 | raise AttributeError(f"Not a valid fake: {name}") 28 | 29 | def __call__(self, provider: str) -> Any: 30 | return getattr(self.fake, provider) 31 | -------------------------------------------------------------------------------- /pydactory/gen.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from decimal import Decimal 3 | from enum import Enum 4 | from inspect import isclass 5 | from typing import Any, Callable, Dict, Type 6 | 7 | from pydactory import errors 8 | 9 | GENS: Dict[Type, Callable[[Type], Any]] = { 10 | str: lambda _f: "fake", 11 | int: lambda _f: 1, 12 | list: lambda _f: [], 13 | bool: lambda _f: False, 14 | Enum: lambda f: list(f._member_map_.values())[0], 15 | datetime: lambda f: datetime(2000, 1, 1), 16 | Decimal: lambda _: Decimal("1.00"), 17 | } 18 | 19 | 20 | def try_gen_default(type_: Type) -> Any: 21 | for t, fn in GENS.items(): 22 | if type_ == t: 23 | return fn(type_) 24 | 25 | for t, fn in GENS.items(): 26 | if isinstance(type_, t): 27 | return fn(type_) 28 | 29 | if getattr(type_, "__origin__", None) == tuple: 30 | return tuple(try_gen_default(t) for t in type_.__args__) 31 | 32 | if getattr(type_, "__origin__", None) == list: 33 | return [] 34 | 35 | if isclass(type_): 36 | for t, fn in GENS.items(): 37 | if issubclass(type_, t): 38 | return fn(type_) 39 | 40 | raise errors.NoDefaultGeneratorError() 41 | 42 | 43 | # def build_model(model: Type[BaseModel], factory: Any, overrides: Params) -> BaseModel: 44 | # return model(**build_fields(model, factory, overrides)) 45 | -------------------------------------------------------------------------------- /pydactory/params.py: -------------------------------------------------------------------------------- 1 | from inspect import isclass, ismethod 2 | from typing import Any, Dict, Optional, Type 3 | 4 | from pydantic import BaseModel 5 | from pydantic.fields import ModelField 6 | 7 | from pydactory import errors 8 | from pydactory.gen import try_gen_default 9 | from pydactory.types import FactoryField, Model, Params 10 | 11 | 12 | class Unspecified: 13 | ... 14 | 15 | 16 | def kwargs_to_aliases(model: Type[Model], kwargs: Dict[str, Any]) -> Dict[str, Any]: 17 | def to_alias(k: str) -> Optional[str]: 18 | try: 19 | return model.__fields__[k].alias 20 | except KeyError: 21 | return None 22 | 23 | return {alias: v for k, v in kwargs.items() if (alias := to_alias(k)) is not None} 24 | 25 | 26 | def build_model(model: Type[Model], overrides: Params) -> Model: 27 | return model(**kwargs_to_aliases(model, params(model, overrides))) 28 | 29 | 30 | def params(model: Type[BaseModel], overrides: Params) -> Params: 31 | return { 32 | key: value 33 | for (key, field) in model.__fields__.items() 34 | if (value := param(key, field, overrides)) is not Unspecified 35 | } 36 | 37 | 38 | def param(key: str, field: ModelField, overrides: Params) -> Any: 39 | if field.name in overrides: 40 | override_val = overrides[field.name] 41 | 42 | if ismethod(getattr(override_val, "build", None)): 43 | return override_val.build() 44 | elif isclass(override_val) and issubclass(override_val, BaseModel): 45 | return build_model(override_val, overrides) 46 | return eval_param(override_val) 47 | 48 | if not field.required or (field.default is not None): 49 | return Unspecified 50 | 51 | is_generic = field.outer_type_ != field.type_ 52 | is_model = isclass(field.type_) and issubclass(field.type_, BaseModel) 53 | 54 | if is_model and not is_generic: 55 | return build_model(field.type_, {}) 56 | 57 | try: 58 | return try_gen_default(field.outer_type_) 59 | except errors.NoDefaultGeneratorError: 60 | raise errors.NoDefaultGeneratorError(key=key, type_=field.outer_type_) 61 | 62 | 63 | def eval_param(v: FactoryField): 64 | return v() if callable(v) else v 65 | -------------------------------------------------------------------------------- /pydactory/pydactory.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Generic, List, Type, get_args 2 | 3 | from pydantic import BaseModel 4 | 5 | from pydactory import errors 6 | from pydactory.fake import FakeGen 7 | from pydactory.params import build_model as _build_model 8 | from pydactory.params import kwargs_to_aliases, params 9 | from pydactory.types import Model 10 | 11 | 12 | class Factory(Generic[Model]): 13 | Fake = FakeGen() 14 | 15 | @classmethod 16 | def build(cls, **overrides) -> Model: 17 | """ 18 | Build a valid model instance. 19 | """ 20 | return cls._model()(**cls.params(**overrides)) 21 | 22 | @classmethod 23 | def build_batch(cls, count: int, **overrides) -> List[Model]: 24 | return [cls.build(**overrides) for _ in range(count)] 25 | 26 | @classmethod 27 | def params(cls, alias=True, **overrides) -> Dict[str, Any]: 28 | try: 29 | params_ = params(cls._model(), {**cls._field_overrides(), **overrides}) 30 | except errors.NoDefaultGeneratorError as e: 31 | raise errors.PydactoryError( 32 | f"Factory {cls} must define a value for param {e.key} of type {e.type}" 33 | ) 34 | 35 | return kwargs_to_aliases(cls._model(), params_) if alias else params_ 36 | 37 | @classmethod 38 | def _model(cls) -> Type[Model]: 39 | model_cls: Type[Model] = get_args(cls.__orig_bases__[0])[0] # type: ignore 40 | 41 | try: 42 | assert isinstance(model_cls, type(BaseModel)) 43 | return model_cls 44 | except AssertionError: 45 | raise errors.PydactoryError( 46 | f"Type argument required for {cls.__name__}. Must be subclass of pydantic.BaseModel." 47 | ) 48 | 49 | @classmethod 50 | def _field_overrides(cls) -> Dict[str, Any]: 51 | return { 52 | key: getattr(cls, key) 53 | for key, override in cls._model().__fields__.items() 54 | if hasattr(cls, key) 55 | } 56 | 57 | 58 | def build_model(model: Type[Model], **overrides) -> Model: 59 | return _build_model(model, overrides) 60 | 61 | 62 | def build_model_batch(model: Type[Model], count: int, **overrides) -> List[Model]: 63 | return [build_model(model, **overrides) for _ in range(count)] 64 | -------------------------------------------------------------------------------- /pydactory/types.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Dict, TypeVar, Union 2 | 3 | from pydantic import BaseModel 4 | 5 | Params = Dict[str, Any] 6 | FieldGenerator = Callable[[], Any] 7 | FactoryField = Union[FieldGenerator, Any] 8 | 9 | Model = TypeVar("Model", bound=BaseModel) 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pydactory" 3 | version = "0.2.0" 4 | description = "A factory library for pydantic models." 5 | authors = ["Richard Howard "] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/rthoward/pydactory" 9 | keywords = ["factory", "fixture", "pydantic"] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | pydantic = "^1.7.2" 14 | faker = "^4.15.0" 15 | 16 | [tool.poetry.dev-dependencies] 17 | pytest = "^6.1.2" 18 | ipython = "^7.19.0" 19 | ipdb = "^0.13.4" 20 | isort = "^5.6.4" 21 | mypy = "^0.790" 22 | sphinx = "^3.3.1" 23 | black = "^20.8b1" 24 | flake8 = "^3.8.4" 25 | pytest-cov = "^2.10.1" 26 | pytest-sugar = "^0.9.4" 27 | PyHamcrest = "^2.0.2" 28 | 29 | [tool.isort] 30 | multi_line_output = 3 31 | include_trailing_comma = true 32 | force_grid_wrap = 0 33 | use_parentheses = true 34 | line_length = 88 35 | 36 | [build-system] 37 | requires = ["poetry>=0.12"] 38 | build-backend = "poetry.masonry.api" 39 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthoward/pydactory/c8494b2cddceb598b63f38880a36d2de347ce935/tests/__init__.py -------------------------------------------------------------------------------- /tests/console.py: -------------------------------------------------------------------------------- 1 | from IPython import start_ipython 2 | 3 | context: dict = {} 4 | 5 | if __name__ == "__main__": 6 | start_ipython(argv=[], user_ns=context) 7 | -------------------------------------------------------------------------------- /tests/support.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from decimal import Decimal 3 | from enum import Enum 4 | from typing import List, Optional, Tuple 5 | 6 | from pydantic import BaseModel, Field 7 | 8 | from pydactory import Factory 9 | 10 | 11 | class Language(Enum): 12 | ENGLISH = "en" 13 | FRENCH = "fr" 14 | SPANISH = "es" 15 | GERMAN = "de" 16 | RUSSIAN = "ru" 17 | 18 | 19 | class Review(BaseModel): 20 | rating: int = Field(alias="Rating") 21 | comment: Optional[str] = Field(alias="Comment") 22 | 23 | 24 | class Address(BaseModel): 25 | street1: str 26 | street2: str 27 | city: str 28 | state: str 29 | zip: str = Field(max_length=5) 30 | 31 | 32 | class Author(BaseModel): 33 | name: str 34 | address: Address 35 | likes_cake: bool = Field(default=True) 36 | likes_chocolate_cake: Optional[bool] 37 | preferred_language: Language 38 | dob: datetime 39 | 40 | 41 | class Book(BaseModel): 42 | title: str = Field(alias="Title") 43 | author: Author = Field(alias="Author") 44 | pages: int = Field(alias="PageCount") 45 | isbn_10: Optional[str] = Field(alias="ISBN-10") 46 | isbn_13: str = Field(alias="ISBN-13") 47 | dimensions: Tuple[Decimal, Decimal, Decimal] = Field(alias="Dimensions") 48 | publish_date: datetime = Field(alias="PublishDate") 49 | language: Language = Field(alias="Language") 50 | 51 | reviews: List[Review] = Field(alias="Reviews") 52 | 53 | 54 | class AuthorFactory(Factory[Author]): 55 | ... 56 | 57 | 58 | class BookFactory(Factory[Book]): 59 | title = "War and Peace" 60 | author = AuthorFactory 61 | pages = Factory.Fake.pyint(max_value=1000) 62 | isbn_13 = "978-1400079988" 63 | dimensions = ["1.0", "2.0", "3.0"] 64 | language = Language.RUSSIAN 65 | publish_date = datetime(1869, 1, 1) 66 | reviews = [Review.construct(rating=1, comment="too long")] 67 | -------------------------------------------------------------------------------- /tests/test_pydactory.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Tuple 3 | 4 | import pytest 5 | from hamcrest import assert_that, has_properties 6 | from pydantic import BaseModel, Field 7 | 8 | from pydactory import Factory, PydactoryError, build_model, build_model_batch 9 | from tests.support import Author, AuthorFactory, Language 10 | 11 | 12 | def test_build_simple_factory(): 13 | class Rect(BaseModel): 14 | height: int 15 | width: int 16 | 17 | class RectFactory(Factory[Rect]): 18 | height = 3 19 | width = 4 20 | 21 | rect = RectFactory.build() 22 | assert_that(rect, has_properties(height=3, width=4)) 23 | 24 | 25 | def test_build_overrides(): 26 | class Rect(BaseModel): 27 | height: int 28 | width: int 29 | 30 | class RectFactory(Factory[Rect]): 31 | height = 3 32 | width = 4 33 | 34 | rect = RectFactory.build(height=2) 35 | assert_that(rect, has_properties(height=2, width=4)) 36 | 37 | 38 | def test_build_model_with_aliases(): 39 | class Rect(BaseModel): 40 | height: int = Field(alias="Height") 41 | width: int = Field(alias="Width") 42 | 43 | class RectFactory(Factory[Rect]): 44 | height = 3 45 | width = 4 46 | 47 | rect = RectFactory.build() 48 | assert_that(rect, has_properties(height=3, width=4)) 49 | 50 | 51 | def test_build_model_with_aliases_and_override(): 52 | class Rect(BaseModel): 53 | height: int = Field(alias="Height") 54 | width: int = Field(alias="Width") 55 | 56 | class RectFactory(Factory[Rect]): 57 | height = 3 58 | width = 4 59 | 60 | rect = RectFactory.build(height=2) 61 | assert_that(rect, has_properties(height=2, width=4)) 62 | 63 | 64 | def test_build_with_callable_param(): 65 | class User(BaseModel): 66 | id: int 67 | 68 | class UserFactory(Factory[User]): 69 | id = lambda: 123 70 | 71 | user = UserFactory.build() 72 | assert user.id == 123 73 | 74 | 75 | @pytest.mark.parametrize( 76 | "type_,expected", 77 | [ 78 | (int, 1), 79 | (str, "fake"), 80 | (bool, False), 81 | (Language, Language.ENGLISH), 82 | (datetime, datetime(2000, 1, 1)), 83 | (Tuple[int, str, bool], (1, "fake", False)), 84 | (List[int], []), 85 | ], 86 | ) 87 | def test_build_default_values(type_, expected): 88 | class Thing(BaseModel): 89 | value: type_ 90 | 91 | class ThingFactory(Factory[Thing]): 92 | id = lambda: 123 93 | 94 | user = ThingFactory.build() 95 | assert user.value == expected 96 | 97 | 98 | def test_build_default_values_unknown_throws_exception(): 99 | class Foo: 100 | pass 101 | 102 | class Bar(BaseModel): 103 | foo: Foo 104 | 105 | class Config: 106 | arbitrary_types_allowed = True 107 | 108 | class BarFactory(Factory[Bar]): 109 | pass 110 | 111 | with pytest.raises(PydactoryError) as e: 112 | BarFactory.build() 113 | 114 | assert isinstance(e.value, PydactoryError) 115 | 116 | 117 | def test_build_with_pydantic_default(): 118 | class Foo(BaseModel): 119 | x: List[str] = Field(default=["a", "b", "c"]) 120 | 121 | class FooFactory(Factory[Foo]): 122 | pass 123 | 124 | assert FooFactory.build().x == ["a", "b", "c"] 125 | 126 | 127 | def test_build_with_falsey_pydantic_default(): 128 | class Foo(BaseModel): 129 | x: List[str] = Field(default=[]) 130 | 131 | class FooFactory(Factory[Foo]): 132 | pass 133 | 134 | assert FooFactory.build().x == [] 135 | 136 | 137 | def test_build_nested_factory(): 138 | class Foo(BaseModel): 139 | pass 140 | 141 | class Bar(BaseModel): 142 | foo: Foo 143 | 144 | class FooFactory(Factory[Foo]): 145 | pass 146 | 147 | class BarFactory(Factory[Bar]): 148 | foo = FooFactory 149 | 150 | bar = BarFactory.build() 151 | assert isinstance(bar.foo, Foo) 152 | 153 | 154 | def test_build_nested_model(): 155 | class Foo(BaseModel): 156 | x: int 157 | 158 | class Bar(BaseModel): 159 | foo: Foo 160 | 161 | class BarFactory(Factory[Bar]): 162 | foo = Foo 163 | 164 | bar = BarFactory.build() 165 | assert isinstance(bar.foo, Foo) 166 | assert bar.foo.x == 1 167 | 168 | 169 | def test_build_nested_model_list(): 170 | class Foo(BaseModel): 171 | x: int 172 | 173 | class Bar(BaseModel): 174 | foos: List[Foo] 175 | 176 | class BarFactory(Factory[Bar]): 177 | ... 178 | 179 | assert BarFactory.build().foos == [] 180 | 181 | 182 | @pytest.mark.skip 183 | def test_build_nested_model_nested_overrides(): 184 | class Foo(BaseModel): 185 | x: int 186 | 187 | class Bar(BaseModel): 188 | foo: Foo 189 | 190 | class BarFactory(Factory[Bar]): 191 | foo = Foo 192 | 193 | bar = BarFactory.build(foo__x=256) 194 | assert isinstance(bar.foo, Foo) 195 | assert bar.foo.x == 256 196 | 197 | 198 | def test_factory_mixins(): 199 | class Order(BaseModel): 200 | id: int 201 | amount: int 202 | 203 | class Identifiable: 204 | id = 1 205 | 206 | class OrderFactory(Factory[Order], Identifiable): 207 | amount = 2 208 | 209 | order = OrderFactory.build() 210 | assert_that(order, has_properties(id=1, amount=2)) 211 | 212 | 213 | def test_params(): 214 | class Rect(BaseModel): 215 | height: int 216 | width: int 217 | 218 | class RectFactory(Factory[Rect]): 219 | height = 3 220 | width = 4 221 | 222 | params = RectFactory.params(alias=False) 223 | assert params["height"] == 3 224 | assert params["width"] == 4 225 | 226 | 227 | def test_params_alias(): 228 | class Rect(BaseModel): 229 | height: int = Field(alias="Height") 230 | width: int = Field(alias="Width") 231 | 232 | class RectFactory(Factory[Rect]): 233 | height = 3 234 | width = 4 235 | 236 | params = RectFactory.params(alias=True) 237 | assert params["Height"] == 3 238 | assert params["Width"] == 4 239 | 240 | 241 | def test_build_model(): 242 | class Rect(BaseModel): 243 | height: int 244 | width: int 245 | 246 | rect = build_model(Rect) 247 | 248 | assert hasattr(rect, "height") 249 | assert hasattr(rect, "width") 250 | 251 | 252 | def test_build_model_with_overrides(): 253 | class Rect(BaseModel): 254 | height: int 255 | width: int 256 | 257 | rect = build_model(Rect, height=100) 258 | 259 | assert rect.height == 100 260 | assert hasattr(rect, "width") 261 | 262 | 263 | def test_faker_integration(): 264 | class AuthorFactory(Factory[Author]): 265 | name = Factory.Fake("name") 266 | 267 | first_author = AuthorFactory.build() 268 | second_author = AuthorFactory.build() 269 | 270 | assert first_author.name and first_author.name != second_author.name 271 | 272 | 273 | def test_faker_integration_with_unknown_provider_throws_at_declaration_time(): 274 | with pytest.raises(AttributeError): 275 | 276 | class _(Factory[Author]): 277 | name = Factory.Fake("foo") 278 | 279 | 280 | def test_build_batch(): 281 | authors = AuthorFactory.build_batch(5, name="John Steinbeck") 282 | 283 | assert len(authors) == 5 284 | assert all(author.name == "John Steinbeck" for author in authors) 285 | 286 | 287 | def test_build_model_batch(): 288 | authors = build_model_batch(Author, 5, name="John Steinbeck") 289 | 290 | assert len(authors) == 5 291 | assert all(author.name == "John Steinbeck" for author in authors) 292 | --------------------------------------------------------------------------------