pax_global_header00006660000000000000000000000064145476224310014522gustar00rootroot0000000000000052 comment=8d099039f7a26cf987535f91b4f579d77afd43ba python-django-test-migrations-1.3.0/000077500000000000000000000000001454762243100174335ustar00rootroot00000000000000python-django-test-migrations-1.3.0/.editorconfig000066400000000000000000000005111454762243100221050ustar00rootroot00000000000000# Check http://editorconfig.org for more information # This is the main config file for this project: root = true [*] charset = utf-8 trim_trailing_whitespace = true end_of_line = lf indent_style = space insert_final_newline = true indent_size = 2 [*.py] indent_size = 4 [*.pyi] indent_size = 4 [Makefile] indent_style = tab python-django-test-migrations-1.3.0/.github/000077500000000000000000000000001454762243100207735ustar00rootroot00000000000000python-django-test-migrations-1.3.0/.github/dependabot.yml000066400000000000000000000004311454762243100236210ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily time: "02:00" open-pull-requests-limit: 10 - package-ecosystem: github-actions directory: "/" schedule: interval: daily time: "02:00" open-pull-requests-limit: 10 python-django-test-migrations-1.3.0/.github/workflows/000077500000000000000000000000001454762243100230305ustar00rootroot00000000000000python-django-test-migrations-1.3.0/.github/workflows/test.yml000066400000000000000000000106341454762243100245360ustar00rootroot00000000000000--- name: test 'on': push: branches: - master pull_request: workflow_dispatch: jobs: safety: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install poetry run: | curl -sSL \ "https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py" | python # Adding `poetry` to `$PATH`: echo "$HOME/.poetry/bin" >> $GITHUB_PATH - name: Install dependencies run: | poetry config virtualenvs.in-project true poetry run pip install -U pip poetry install - name: "Run safety" run: make safety build: runs-on: ubuntu-latest continue-on-error: ${{ matrix.continue-on-error }} env: DJANGO_DATABASE_ENGINE: "${{ matrix.env.DJANGO_DATABASE_ENGINE || 'django.db.backends.sqlite3' }}" DJANGO_DATABASE_USER: django DJANGO_DATABASE_PASSWORD: passwd123 DJANGO_DATABASE_NAME: db DJANGO_DATABASE_HOST: 127.0.0.1 DJANGO_DATABASE_PORT: "${{ matrix.env.DJANGO_DATABASE_PORT }}" DOCKERIZE_VERSION: v0.6.1 strategy: matrix: python-version: ['3.8', '3.9', '3.10', '3.11'] django-version: - 'Django~=3.2.0' - 'Django~=4.1.0' - 'Django~=4.2.0' docker-compose-services: [''] additional-dependencies: [''] continue-on-error: [false] include: - python-version: '3.11' django-version: 'https://github.com/django/django/archive/main.zip' continue-on-error: true - python-version: '3.10' django-version: 'Django~=4.1.0' docker-compose-services: postgresql-db additional-dependencies: psycopg2 env: DJANGO_DATABASE_ENGINE: 'django.db.backends.postgresql' DJANGO_DATABASE_PORT: 5432 continue-on-error: false - python-version: '3.10' django-version: 'Django~=4.1.0' docker-compose-services: mysql-db additional-dependencies: mysqlclient env: DJANGO_DATABASE_ENGINE: 'django.db.backends.mysql' DJANGO_DATABASE_PORT: 3306 continue-on-error: false steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install poetry run: | curl -sSL \ "https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py" | python # Adding `poetry` to `$PATH`: echo "$HOME/.poetry/bin" >> $GITHUB_PATH - name: Install dependencies run: | poetry config virtualenvs.in-project true poetry run pip install -U pip poetry install poetry run pip install \ --upgrade \ "${{ matrix.django-version }}" \ ${{ matrix.additional-dependencies }} - name: Pull and build docker-compose services if: ${{ matrix.docker-compose-services }} run: | docker-compose pull ${{ matrix.docker-compose-services }} docker-compose up --detach ${{ matrix.docker-compose-services }} - name: Wait for docker-compose services if: ${{ matrix.docker-compose-services }} run: | wget \ https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz \ && tar -C . -xzvf dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz \ && rm dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz \ && chmod +x dockerize ./dockerize \ -wait "tcp://localhost:${{ matrix.env.DJANGO_DATABASE_PORT }}" \ -wait-retry-interval "1s" \ -timeout "30s" - name: "Run checks for python ${{ matrix.python-version }} and django ${{ matrix.django-version }}" run: make test - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage.xml - name: Stop docker-compose services if: ${{ always() && matrix.docker-compose-services }} run: docker-compose down || true python-django-test-migrations-1.3.0/.gitignore000066400000000000000000000055351454762243100214330ustar00rootroot00000000000000#### joe made this: http://goel.io/joe #### python #### # Byte-compiled / optimized / DLL files .pytest_cache __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg pip-wheel-metadata/ # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ #### macos #### # General *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk #### windows #### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk #### linux #### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* #### jetbrains #### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: .idea/ ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### Custom ### ex.py *.sqlite3 python-django-test-migrations-1.3.0/CHANGELOG.md000066400000000000000000000041261454762243100212470ustar00rootroot00000000000000# Version history We follow Semantic Versions since the `0.1.0` release. ## Version 1.3.0 ### Features - Adds Python 3.11 support - Drops Python 3.7 support - Adds Django 4.1 support - Adds Django 4.2 support - Drops Django 2.2 support ## Version 1.2.0 ### Features - Adds Python 3.10 - Adds Django 4.0 support - Updates `typing_extensions` to `>=3.6,<5` ## Version 1.1.0 ### Features - Adds Django 3.1 support (#123, #154) - Adds markers/tags to migration tests (#138) - Adds database configuration checks (#91) ### Bugfixes - Fixes tables dropping on MySQL by disabling foreign keys checks (#149) - Fixes migrate signals muting when running migrations tests (#133) ### Misc - Runs tests against PostgreSQL and MySQL database engines (#129) ## Version 1.0.0 ### Breaking Changes - Rename following `Migrator` methods (#83): + `before` to `apply_initial_migration` + `after` to `apply_tested_migration` - Improves databases setup and teardown for migrations tests (#76) Currently `Migrator.reset` uses `migrate` management command and all logic related to migrations tests setup is moved to `Migrator.apply_tested_migration`. ### Bugfixes - Fixes `pre_migrate` and `post_migrate` signals muting (#87) - Adds missing `typing_extension` dependency (#86) ### Misc - Refactor tests (#79) - Return `django` installed from `master` branch to testing matrix (#77) ## Version 0.3.0 ### Features - Drops `django@2.1` support - Adds `'*'` alias for ignoring all migrations in an app with `DTM_IGNORED_MIGRATIONS` ### Bugfixes - Fixes how `pre_migrate` and `post_migrate` signals are muted ### Misc - Updates `wemake-python-styleguide` - Moves from `travis` to Github Actions ## Version 0.2.0 ### Features - Adds `autoname` check to forbid `*_auto_*` named migrations - Adds `django@3.0` support - Adds `python3.8` support ### Bugfixes - Fixes that migtaions were failing with `pre_migrate` and `post_migrate` signals - Fixes that tests were failing when `pytest --nomigration` was executed, now they are skipped ### Misc - Updates to `poetry@1.0` ## Version 0.1.0 - Initial release python-django-test-migrations-1.3.0/LICENSE000066400000000000000000000020601454762243100204360ustar00rootroot00000000000000MIT License Copyright (c) 2019 wemake.services 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. python-django-test-migrations-1.3.0/Makefile000066400000000000000000000010011454762243100210630ustar00rootroot00000000000000SHELL:=/usr/bin/env bash .PHONY: lint lint: poetry run mypy django_test_migrations poetry run flake8 . .PHONY: unit unit: # We need one more test run to make sure that `--nomigrations` work: poetry run pytest -p no:cov -o addopts="" --nomigrations # Real `pytest` execution: poetry run pytest .PHONY: package package: poetry check poetry run pip check .PHONY: safety safety: # Is not run by default, is a separate command. poetry run safety check --full-report .PHONY: test test: lint unit package python-django-test-migrations-1.3.0/README.md000066400000000000000000000277441454762243100207300ustar00rootroot00000000000000# django-test-migrations [![wemake.services](https://img.shields.io/badge/%20-wemake.services-green.svg?label=%20&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAAAFzUkdCAK7OHOkAAAAbUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2F5TvxDIAAAAIdFJOUwAjRA8xXANAL%2Bv0SAAAADNJREFUGNNjYCAIOJjRBdBFWMkVQeGzcHAwksJnAPPZGOGAASzPzAEHEGVsLExQwE7YswCb7AFZSF3bbAAAAABJRU5ErkJggg%3D%3D)](https://wemake-services.github.io) [![Build status](https://github.com/wemake-services/django-test-migrations/workflows/test/badge.svg?branch=master&event=push)](https://github.com/wemake-services/django-test-migrations/actions?query=workflow%3Atest) [![codecov](https://codecov.io/gh/wemake-services/django-test-migrations/branch/master/graph/badge.svg)](https://codecov.io/gh/wemake-services/django-test-migrations) [![Python Version](https://img.shields.io/pypi/pyversions/django-test-migrations.svg)](https://pypi.org/project/django-test-migrations/) ![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-test-migrations) [![wemake-python-styleguide](https://img.shields.io/badge/style-wemake-000000.svg)](https://github.com/wemake-services/wemake-python-styleguide) ## Features - Allows to test `django` schema and data migrations - Allows to test both forward and rollback migrations - Allows to test the migrations order - Allows to test migration names - Allows to test database configuration - Fully typed with annotations and checked with `mypy`, [PEP561 compatible](https://www.python.org/dev/peps/pep-0561/) - Easy to start: has lots of docs, tests, and tutorials Read the [announcing post](https://sobolevn.me/2019/10/testing-django-migrations). See real-world [usage example](https://github.com/wemake-services/wemake-django-template). ## Installation ```bash pip install django-test-migrations ``` We support several `django` versions: - `2.2` - `3.2` - `4.0` - `4.1` - `4.2` Other versions most likely will work too, but they are not officially supported. ## Testing Django migrations Testing migrations is not a frequent thing in `django` land. But, sometimes it is totally required. When? When we do complex schema or data changes and what to be sure that existing data won't be corrupted. We might also want to be sure that all migrations can be safely rolled back. And as a final touch, we want to be sure that migrations are in the correct order and have correct dependencies. ### Testing forward migrations To test all migrations we have a [`Migrator`](https://github.com/wemake-services/django-test-migrations/blob/master/django_test_migrations/migrator.py) class. It has three methods to work with: - `.apply_initial_migration()` which takes app and migration names to generate a state before the actual migration happens. It creates the `before state` by applying all migrations up to and including the ones passed as an argument. - `.apply_tested_migration()` which takes app and migration names to perform the actual migration - `.reset()` to clean everything up after we are done with testing So, here's an example: ```python from django_test_migrations.migrator import Migrator migrator = Migrator(database='default') # Initial migration, currently our model has only a single string field: # Note: # We are testing migration `0002_someitem_is_clean`, so we are specifying # the name of the previous migration (`0001_initial`) in the # .apply_initial_migration() method in order to prepare a state of the database # before applying the migration we are going to test. # old_state = migrator.apply_initial_migration(('main_app', '0001_initial')) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') # Let's create a model with just a single field specified: SomeItem.objects.create(string_field='a') assert len(SomeItem._meta.get_fields()) == 2 # id + string_field # Now this migration will add `is_clean` field to the model: new_state = migrator.apply_tested_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = new_state.apps.get_model('main_app', 'SomeItem') # We can now test how our migration worked, new field is there: assert SomeItem.objects.filter(is_clean=True).count() == 0 assert len(SomeItem._meta.get_fields()) == 3 # id + string_field + is_clean # Cleanup: migrator.reset() ``` That was an example of a forward migration. ### Backward migration The thing is that you can also test backward migrations. Nothing really changes except migration names that you pass and your logic: ```python migrator = Migrator() # Currently our model has two field, but we need a rollback: old_state = migrator.apply_initial_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') # Create some data to illustrate your cases: # ... # Now this migration will drop `is_clean` field: new_state = migrator.apply_tested_migration(('main_app', '0001_initial')) # Assert the results: # ... # Cleanup: migrator.reset() ``` ### Testing migrations ordering Sometimes we also want to be sure that our migrations are in the correct order and that all our `dependencies = [...]` are correct. To achieve that we have [`plan.py`](https://github.com/wemake-services/django-test-migrations/blob/master/django_test_migrations/plan.py) module. That's how it can be used: ```python from django_test_migrations.plan import all_migrations, nodes_to_tuples main_migrations = all_migrations('default', ['main_app', 'other_app']) assert nodes_to_tuples(main_migrations) == [ ('main_app', '0001_initial'), ('main_app', '0002_someitem_is_clean'), ('other_app', '0001_initial'), ('main_app', '0003_update_is_clean'), ('main_app', '0004_auto_20191119_2125'), ('other_app', '0002_auto_20191120_2230'), ] ``` This way you can be sure that migrations and apps that depend on each other will be executed in the correct order. ### `factory_boy` integration If you use factories to create models, you can replace their respective `.build()` or `.create()` calls with methods of `factory` and pass the model name and factory class as arguments: ```python import factory old_state = migrator.apply_initial_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') # instead of # item = SomeItemFactory.create() # use this: factory.create(SomeItem, FACTORY_CLASS=SomeItemFactory) # ... ``` ## Test framework integrations 🐍 We support several test frameworks as first-class citizens. That's a testing tool after all! Note that the Django `post_migrate` signal's receiver list is cleared at the start of tests and restored afterwards. If you need to test your own `post_migrate` signals then attach/remove them during a test. ### pytest We ship `django-test-migrations` with a `pytest` plugin that provides two convenient fixtures: - `migrator_factory` that gives you an opportunity to create `Migrator` classes for any database - `migrator` instance for the `'default'` database That's how it can be used: ```python import pytest @pytest.mark.django_db() def test_pytest_plugin_initial(migrator): """Ensures that the initial migration works.""" old_state = migrator.apply_initial_migration(('main_app', None)) with pytest.raises(LookupError): # Model does not yet exist: old_state.apps.get_model('main_app', 'SomeItem') new_state = migrator.apply_tested_migration(('main_app', '0001_initial')) # After the initial migration is done, we can use the model state: SomeItem = new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.filter(string_field='').count() == 0 ``` ### unittest We also ship an integration with the built-in `unittest` framework. Here's how it can be used: ```python from django_test_migrations.contrib.unittest_case import MigratorTestCase class TestDirectMigration(MigratorTestCase): """This class is used to test direct migrations.""" migrate_from = ('main_app', '0002_someitem_is_clean') migrate_to = ('main_app', '0003_update_is_clean') def prepare(self): """Prepare some data before the migration.""" SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem') SomeItem.objects.create(string_field='a') SomeItem.objects.create(string_field='a b') def test_migration_main0003(self): """Run the test itself.""" SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.count() == 2 assert SomeItem.objects.filter(is_clean=True).count() == 1 ``` ### Choosing only migrations tests In CI systems it is important to get instant feedback. Running tests that apply database migration can slow down tests execution, so it is often a good idea to run standard, fast, regular unit tests without migrations in parallel with slower migrations tests. #### pytest `django_test_migrations` adds `migration_test` marker to each test using `migrator_factory` or `migrator` fixture. To run only migrations test, use `-m` option: ```bash pytest -m migration_test # Runs only migration tests pytest -m "not migration_test" # Runs all except migration tests ``` #### unittest `django_test_migrations` adds `migration_test` [tag](https://docs.djangoproject.com/en/3.0/topics/testing/tools/#tagging-tests) to every `MigratorTestCase` subclass. To run only migrations tests, use `--tag` option: ```bash python mange.py test --tag=migration_test # Runs only migration tests python mange.py test --exclude-tag=migration_test # Runs all except migration tests ``` ## Django Checks `django_test_migrations` comes with 2 groups of Django's checks for: + detecting migrations scripts automatically generated names + validating some subset of database settings ### Testing migration names `django` generates migration names for you when you run `makemigrations`. These names are bad ([read more](https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/) about why it is bad)! Just look at this: `0004_auto_20191119_2125.py` What does this migration do? What changes does it have? One can also pass `--name` attribute when creating migrations, but it is easy to forget. We offer an automated solution: `django` check that produces an error for each badly named migration. Add our check into your `INSTALLED_APPS`: ```python INSTALLED_APPS = [ # ... # Our custom check: 'django_test_migrations.contrib.django_checks.AutoNames', ] ``` Then in your CI run: ```bash python manage.py check --deploy ``` This way you will be safe from wrong names in your migrations. Do you have a migrations that cannot be renamed? Add them to the ignore list: ```python # settings.py DTM_IGNORED_MIGRATIONS = { ('main_app', '0004_auto_20191119_2125'), ('dependency_app', '0001_auto_20201110_2100'), } ``` Then we won't complain about them. Or you can completely ignore entire app: ```python # settings.py DTM_IGNORED_MIGRATIONS = { ('dependency_app', '*'), ('another_dependency_app', '*'), } ``` ### Database configuration Add our check to `INSTALLED_APPS`: ```python INSTALLED_APPS = [ # ... # Our custom check: 'django_test_migrations.contrib.django_checks.DatabaseConfiguration', ] ``` Then just run `check` management command in your CI like listed in section above. ## Related projects You might also like: - [django-migration-linter](https://github.com/3YOURMIND/django-migration-linter) - Detect backward incompatible migrations for your django project. - [wemake-django-template](https://github.com/wemake-services/wemake-django-template/) - Bleeding edge django template focused on code quality and security with both `django-test-migrations` and `django-migration-linter` on board. ## Credits This project is based on work of other awesome people: - [@asfaltboy](https://gist.github.com/asfaltboy/b3e6f9b5d95af8ba2cc46f2ba6eae5e2) - [@blueyed](https://gist.github.com/blueyed/4fb0a807104551f103e6) - [@fernandogrd](https://gist.github.com/blueyed/4fb0a807104551f103e6#gistcomment-1546191) - [@adamchainz](https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/) ## License MIT. python-django-test-migrations-1.3.0/django_test_app/000077500000000000000000000000001454762243100225745ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/django_test_app/000077500000000000000000000000001454762243100257355ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/django_test_app/__init__.py000066400000000000000000000000301454762243100300370ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-django-test-migrations-1.3.0/django_test_app/django_test_app/settings.py000066400000000000000000000064521454762243100301560ustar00rootroot00000000000000""" Django settings for django_test_app project. Generated by 'django-admin startproject' using Django 2.2.7. For more information on this file, see https://docs.djangoproject.com/en/2.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname( os.path.dirname(os.path.abspath(__file__)), ) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '_dpvr*#hjgv)6v=potf%*+$na7_ck(*+^g08lw0^44zoo88)wb' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # Our custom checks: 'django_test_migrations.contrib.django_checks.AutoNames', 'django_test_migrations.contrib.django_checks.DatabaseConfiguration', # Custom: 'main_app', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'django_test_app.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'django_test_app.wsgi.application' # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': os.environ.get( 'DJANGO_DATABASE_ENGINE', default='django.db.backends.sqlite3', ), 'USER': os.environ.get('DJANGO_DATABASE_USER', default=''), 'PASSWORD': os.environ.get('DJANGO_DATABASE_PASSWORD', default=''), 'NAME': os.environ.get( 'DJANGO_DATABASE_NAME', default=os.path.join(BASE_DIR, 'db.sqlite3'), ), 'PORT': os.environ.get('DJANGO_DATABASE_PORT', default=''), 'HOST': os.environ.get('DJANGO_DATABASE_HOST', default=''), }, } # Password validation # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [] # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = '/static/' python-django-test-migrations-1.3.0/django_test_app/django_test_app/urls.py000066400000000000000000000012151454762243100272730ustar00rootroot00000000000000""" django_test_app URL Configuration. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.2/topics/http/urls/ Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ urlpatterns = [] python-django-test-migrations-1.3.0/django_test_app/django_test_app/wsgi.py000066400000000000000000000006271454762243100272650ustar00rootroot00000000000000""" WSGI config for django_test_app project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test_app.settings') application = get_wsgi_application() python-django-test-migrations-1.3.0/django_test_app/main_app/000077500000000000000000000000001454762243100243605ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/main_app/__init__.py000066400000000000000000000000301454762243100264620ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-django-test-migrations-1.3.0/django_test_app/main_app/apps.py000066400000000000000000000002051454762243100256720ustar00rootroot00000000000000from django.apps import AppConfig class MainAppConfig(AppConfig): """Configuration for ``main_app``.""" name = 'main_app' python-django-test-migrations-1.3.0/django_test_app/main_app/logic/000077500000000000000000000000001454762243100254555ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/main_app/logic/__init__.py000066400000000000000000000000301454762243100275570ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-django-test-migrations-1.3.0/django_test_app/main_app/logic/pure/000077500000000000000000000000001454762243100264305ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/main_app/logic/pure/__init__.py000066400000000000000000000000301454762243100305320ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-django-test-migrations-1.3.0/django_test_app/main_app/logic/pure/migrations.py000066400000000000000000000004201454762243100311520ustar00rootroot00000000000000import typing if typing.TYPE_CHECKING: from main_app.models import SomeItem # noqa: WPS433 def is_clean_item(instance: 'SomeItem') -> bool: """Pure function that decides whether or not whitespace is in the model.""" return ' ' not in instance.string_field python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/000077500000000000000000000000001454762243100265345ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/0001_initial.py000066400000000000000000000014331454762243100312000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 2.2.7 on 2019-11-19 20:00 from django.db import migrations, models class Migration(migrations.Migration): """Initial migration.""" initial = True dependencies = [] operations = [ migrations.CreateModel( name='SomeItem', fields=[ ( 'id', models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name='ID', ), ), ( 'string_field', models.CharField(max_length=50), # noqa: WPS432 ), ], ), ] python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/0002_someitem_is_clean.py000066400000000000000000000007231454762243100332300ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 2.2.7 on 2019-11-19 21:24 from django.db import migrations, models class Migration(migrations.Migration): """Migration to add ``is_clean`` field to ``SomeItem``.""" dependencies = [ ('main_app', '0001_initial'), ] operations = [ migrations.AddField( model_name='someitem', name='is_clean', field=models.BooleanField(default=True), ), ] python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/0003_update_is_clean.py000066400000000000000000000023141454762243100326670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 2.2.7 on 2019-11-19 21:25 from django.db import migrations from main_app.logic.pure.migrations import is_clean_item def _set_clean_flag(apps, schema_editor): """ Performs the data migration. We can't import the ``SomeItem`` model directly as it may be a newer version than this migration expects. We use the historical version. We are using ``.all()`` because we don't have a lot of ``SomeItem`` instances. In real-life you should not do that. """ SomeItem = apps.get_model('main_app', 'SomeItem') for instance in SomeItem.objects.all(): instance.is_clean = is_clean_item(instance) instance.save(update_fields=['is_clean']) def _remove_clean_flags(apps, schema_editor): """ This is just a noop example of a rollback function. It is not used in our simple case, but it should be implemented for more complex scenarios. """ class Migration(migrations.Migration): """Performs the logical data migration for ``SomeItem``.""" dependencies = [ ('main_app', '0002_someitem_is_clean'), ] operations = [ migrations.RunPython(_set_clean_flag, _remove_clean_flags), ] python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/0004_auto_20191119_2125.py000066400000000000000000000011151454762243100321570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 2.2.7 on 2019-11-19 21:25 """ This migration is named incorrectly. We use it as a test for wrong autonames. Please, do not rename it! """ from django.db import migrations, models class Migration(migrations.Migration): """Removes the default value from ``is_clean`` from ``SomeItem``.""" dependencies = [ ('main_app', '0003_update_is_clean'), ] operations = [ migrations.AlterField( model_name='someitem', name='is_clean', field=models.BooleanField(), ), ] python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/0005_auto_20200329_1118.py000066400000000000000000000006031454762243100321540ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 2.2.10 on 2020-03-29 11:18 """ This migration is named incorrectly. We use it as a test for wrong autonames. Please, do not rename it! """ from django.db import migrations class Migration(migrations.Migration): """Dummy migration.""" dependencies = [ ('main_app', '0004_auto_20191119_2125'), ] operations = [] python-django-test-migrations-1.3.0/django_test_app/main_app/migrations/__init__.py000066400000000000000000000000001454762243100306330ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_app/main_app/models.py000066400000000000000000000004031454762243100262120ustar00rootroot00000000000000from django.db import models _SomeItemStringFieldLength = 50 class SomeItem(models.Model): """We use this model for testing migrations.""" string_field = models.CharField(max_length=_SomeItemStringFieldLength) is_clean = models.BooleanField() python-django-test-migrations-1.3.0/django_test_app/main_app/views.py000066400000000000000000000000621454762243100260650ustar00rootroot00000000000000from django.shortcuts import render # noqa: F401 python-django-test-migrations-1.3.0/django_test_app/manage.py000077500000000000000000000004741454762243100244060ustar00rootroot00000000000000#!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys from django.core.management import execute_from_command_line if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test_app.settings') execute_from_command_line(sys.argv) python-django-test-migrations-1.3.0/django_test_migrations/000077500000000000000000000000001454762243100241705ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/__init__.py000066400000000000000000000000301454762243100262720ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-django-test-migrations-1.3.0/django_test_migrations/checks/000077500000000000000000000000001454762243100254305ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/checks/__init__.py000066400000000000000000000000001454762243100275270ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/checks/autonames.py000066400000000000000000000052011454762243100277740ustar00rootroot00000000000000from fnmatch import fnmatch from typing import FrozenSet, List, Tuple from django.conf import settings from django.core.checks import CheckMessage, Warning from typing_extensions import Final _IgnoreAppSpec = FrozenSet[str] _IgnoreMigrationSpec = FrozenSet[Tuple[str, str]] #: We use this type hint to represent ignore rules for migrations. _IgnoreSpec = Tuple[_IgnoreAppSpec, _IgnoreMigrationSpec] #: We use this value as a unique identifier of this check. CHECK_NAME: Final = 'django_test_migrations.checks.autonames' #: Settings name for this check to ignore some migrations. _SETTINGS_NAME: Final = 'DTM_IGNORED_MIGRATIONS' # Special key to ignore all migrations inside an app _IGNORE_APP_MIGRATIONS_SPECIAL_KEY: Final = '*' def _is_ignored( app_label: str, migration_name: str, ignored: _IgnoreSpec, ) -> bool: ignored_apps, ignored_migrations = ignored return ( app_label in ignored_apps or (app_label, migration_name) in ignored_migrations ) def _build_ignores() -> _IgnoreSpec: ignored_migrations: _IgnoreMigrationSpec = getattr( settings, _SETTINGS_NAME, frozenset(), ) ignored_apps: _IgnoreAppSpec = frozenset( app_label for app_label, migration_name in ignored_migrations if migration_name == _IGNORE_APP_MIGRATIONS_SPECIAL_KEY ) return ignored_apps, ignored_migrations def check_migration_names(*args, **kwargs) -> List[CheckMessage]: """ Finds automatic names in available migrations. We use nested import here, because some versions of django fails otherwise. They do raise: ``django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.`` """ from django.db.migrations.loader import MigrationLoader # noqa: WPS433 loader = MigrationLoader(None, ignore_no_migrations=True) loader.load_disk() messages = [] ignores = _build_ignores() for app_label, migration_name in loader.disk_migrations.keys(): if _is_ignored(app_label, migration_name, ignores): continue if fnmatch(migration_name, '????_auto_*'): messages.append( Warning( 'Migration {0}.{1} has an automatic name.'.format( app_label, migration_name, ), hint=( 'Rename the migration to describe its contents, ' + "or if it's from a third party app, add to " + _SETTINGS_NAME ), id='{0}.W001'.format(CHECK_NAME), ), ) return messages CHECKS: Final = (check_migration_names,) python-django-test-migrations-1.3.0/django_test_migrations/checks/database_configuration.py000066400000000000000000000005241454762243100324760ustar00rootroot00000000000000from typing_extensions import Final from django_test_migrations.db.checks.statement_timeout import ( check_statement_timeout_setting, ) #: We use this value as a unique identifier of databases related check. CHECK_NAME: Final = 'django_test_migrations.checks.database_configuration' CHECKS: Final = (check_statement_timeout_setting,) python-django-test-migrations-1.3.0/django_test_migrations/constants.py000066400000000000000000000002341454762243100265550ustar00rootroot00000000000000from typing_extensions import Final #: marker/tag indicating that marked test is a Django's migration test MIGRATION_TEST_MARKER: Final = 'migration_test' python-django-test-migrations-1.3.0/django_test_migrations/contrib/000077500000000000000000000000001454762243100256305ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/contrib/__init__.py000066400000000000000000000000301454762243100277320ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-django-test-migrations-1.3.0/django_test_migrations/contrib/django_checks.py000066400000000000000000000041761454762243100307740ustar00rootroot00000000000000from django.apps import AppConfig from django.core import checks from typing_extensions import final from django_test_migrations.checks import autonames, database_configuration @final class AutoNames(AppConfig): """ Class to install this check into ``INSTALLED_APPS`` in ``django``. If you have migrations that cannot be renamed, use ``DTM_IGNORED_MIGRATIONS`` setting in ``django.conf`` to ignore ones you have to deal with: .. code:: python # settings.py DTM_IGNORED_MIGRATIONS = { ('main_app', '0004_auto_20191119_2125'), ('dependency_app', '0001_auto_20201110_2100'), } To run checks use: .. code:: bash python manage.py check --deploy --fail-level WARNING It will return exit code ``1`` if any violations are found. This can be easily added into your CI. See: https://docs.djangoproject.com/en/3.0/ref/applications/ https://twitter.com/AdamChainz/status/1231895529686208512 """ #: Part of Django API. name = autonames.CHECK_NAME def ready(self): """That's how we register our check when apps are ready.""" for check in autonames.CHECKS: checks.register(check, checks.Tags.compatibility) @final class DatabaseConfiguration(AppConfig): """Class to install this check into ``INSTALLED_APPS`` in ``django``. Database configuration checks are made with aim to help/guide developers set the most appropriate values for some database settings according to best practices. Currently supported database settings: * statement timeout (timeout queries that execution take too long): * `postgresql` via `statement_timeout` - https://bit.ly/2ZFjaRM * `mysql` via `max_execution_time` - https://bit.ly/399TBvk See: https://github.com/wemake-services/wemake-django-template/issues/1064 """ #: Part of Django API. name = database_configuration.CHECK_NAME def ready(self): """Register database configuration checks.""" for check in database_configuration.CHECKS: checks.register(check, checks.Tags.database) python-django-test-migrations-1.3.0/django_test_migrations/contrib/pytest_plugin.py000066400000000000000000000056551454762243100311230ustar00rootroot00000000000000from typing import Optional import pytest from django.db import DEFAULT_DB_ALIAS from django_test_migrations.constants import MIGRATION_TEST_MARKER def pytest_load_initial_conftests(early_config): """Register pytest's markers.""" early_config.addinivalue_line( 'markers', "{0}: mark the test as a Django's migration test.".format( MIGRATION_TEST_MARKER, ), ) def pytest_collection_modifyitems(session, items): # noqa: WPS110 """Mark all tests using ``migrator_factory`` fixture with proper marks. Add ``MIGRATION_TEST_MARKER`` marker to all items using ``migrator_factory`` fixture. """ for pytest_item in items: if 'migrator_factory' in getattr(pytest_item, 'fixturenames', []): pytest_item.add_marker(MIGRATION_TEST_MARKER) @pytest.fixture() def migrator_factory(request, transactional_db, django_db_use_migrations): """ Pytest fixture to create migrators inside the pytest tests. How? Here's an example. .. code:: python @pytest.mark.django_db() def test_migration(migrator_factory): migrator = migrator_factory('custom_db_alias') old_state = migrator.apply_initial_migration(('main_app', None)) new_state = migrator.apply_tested_migration( ('main_app', '0001_initial'), ) assert isinstance(old_state, ProjectState) assert isinstance(new_state, ProjectState) Why do we import :class:`Migrator` inside the fixture function? Otherwise, coverage won't work correctly during our internal tests. Why? Because modules in Python are singletons. Once imported, they will be stored in memory and reused. That's why we cannot import ``Migrator`` on a module level. Because it won't be caught be coverage later on. """ from django_test_migrations.migrator import Migrator # noqa: WPS433 if not django_db_use_migrations: pytest.skip('--nomigrations was specified') def factory(database_name: Optional[str] = None) -> Migrator: migrator = Migrator(database_name) request.addfinalizer(migrator.reset) # noqa: PT021 return migrator return factory @pytest.fixture() def migrator(migrator_factory): # noqa: WPS442 """ Useful alias for ``'default'`` database in ``django``. That's a predefined instance of a ``migrator_factory``. How to use it? Here's an example. .. code:: python @pytest.mark.django_db() def test_migration(migrator): old_state = migrator.apply_initial_migration(('main_app', None)) new_state = migrator.apply_tested_migration( ('main_app', '0001_initial'), ) assert isinstance(old_state, ProjectState) assert isinstance(new_state, ProjectState) Just one step easier than ``migrator_factory`` fixture. """ return migrator_factory(DEFAULT_DB_ALIAS) python-django-test-migrations-1.3.0/django_test_migrations/contrib/unittest_case.py000066400000000000000000000041721454762243100310600ustar00rootroot00000000000000from typing import ClassVar, Optional from django.db.migrations.state import ProjectState from django.db.models.signals import post_migrate, pre_migrate from django.test import TransactionTestCase, tag from django_test_migrations.constants import MIGRATION_TEST_MARKER from django_test_migrations.migrator import Migrator from django_test_migrations.types import MigrationSpec @tag(MIGRATION_TEST_MARKER) class MigratorTestCase(TransactionTestCase): """Used when using raw ``unitest`` library for test.""" database_name: ClassVar[Optional[str]] = None old_state: ProjectState new_state: ProjectState #: Part of the end-user API. Used to tell what migrations we are using. migrate_from: ClassVar[MigrationSpec] migrate_to: ClassVar[MigrationSpec] def setUp(self) -> None: """ Regular ``unittest`` styled setup case. What it does? - It starts with defining the initial migration state - Then it allows to run custom method to prepare some data before the migration will happen - Then it applies the migration and saves all states """ super().setUp() self._migrator = Migrator(self.database_name) self.old_state = self._migrator.apply_initial_migration( self.migrate_from, ) self.prepare() self.new_state = self._migrator.apply_tested_migration(self.migrate_to) def prepare(self) -> None: """ Part of the end-user API. Used to prepare some data before the migration process. """ def tearDown(self) -> None: """Used to clean mess up after each test.""" pre_migrate.receivers = self._pre_migrate_receivers post_migrate.receivers = self._post_migrate_receivers self._migrator.reset() super().tearDown() def _pre_setup(self): self._pre_migrate_receivers, pre_migrate.receivers = ( # noqa: WPS414 pre_migrate.receivers, [], ) self._post_migrate_receivers, post_migrate.receivers = ( # noqa: WPS414 post_migrate.receivers, [], ) super()._pre_setup() python-django-test-migrations-1.3.0/django_test_migrations/db/000077500000000000000000000000001454762243100245555ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/__init__.py000066400000000000000000000000001454762243100266540ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/000077500000000000000000000000001454762243100263275ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/__init__.py000066400000000000000000000005021454762243100304350ustar00rootroot00000000000000# register all ``BaseDatabaseConfiguration`` subclasses from django_test_migrations.db.backends.mysql.configuration import ( DatabaseConfiguration as MySQLDatabaseConfiguration, ) from django_test_migrations.db.backends.postgresql.configuration import ( DatabaseConfiguration as PostgreSQLDatabaseConfiguration, ) python-django-test-migrations-1.3.0/django_test_migrations/db/backends/base/000077500000000000000000000000001454762243100272415ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/base/__init__.py000066400000000000000000000000001454762243100313400ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/base/configuration.py000066400000000000000000000023171454762243100324650ustar00rootroot00000000000000import abc import inspect from typing import ClassVar from django_test_migrations.db.backends.exceptions import ( DatabaseConfigurationSettingNotFound, ) from django_test_migrations.db.backends.registry import ( database_configuration_registry, ) from django_test_migrations.types import AnyConnection, DatabaseSettingValue class BaseDatabaseConfiguration(abc.ABC): """Interact with database's settings.""" vendor: ClassVar[str] statement_timeout: ClassVar[str] @classmethod def __init_subclass__(cls, **kwargs) -> None: """Register ``BaseDatabaseConfiguration`` subclass of db ``vendor``.""" if not inspect.isabstract(cls): database_configuration_registry.setdefault(cls.vendor, cls) def __init__(self, connection: AnyConnection) -> None: """Bind database ``connection`` used to retrieve settings values.""" self.connection = connection @abc.abstractmethod def get_setting_value(self, name: str) -> DatabaseSettingValue: """Retrieve value of ``vendor`` database's ``name`` setting. Raises: DatabaseConfigurationSettingNotFound """ raise DatabaseConfigurationSettingNotFound(self.vendor, name) python-django-test-migrations-1.3.0/django_test_migrations/db/backends/exceptions.py000066400000000000000000000020631454762243100310630ustar00rootroot00000000000000from typing import ClassVar class BaseDatabaseConfigurationException(Exception): """Base exception for errors related to database configuration.""" class DatabaseConfigurationNotFound(BaseDatabaseConfigurationException): """``BaseDatabaseConfiguration`` subclass when given db vendor not found.""" message_template: ClassVar[str] = ( '``BaseDatabaseConfiguration`` subclass for "{0}" vendor not found' ) def __init__(self, vendor: str) -> None: """Format and set message from args and ``message_template``.""" super().__init__(self.message_template.format(vendor)) class DatabaseConfigurationSettingNotFound(BaseDatabaseConfigurationException): """Database configurations setting not found.""" message_template: ClassVar[str] = ( 'Database vendor "{0}" does not support setting "{1}"' ) def __init__(self, vendor: str, setting_name: str) -> None: """Format and set message from args and ``message_template``.""" super().__init__(self.message_template.format(vendor, setting_name)) python-django-test-migrations-1.3.0/django_test_migrations/db/backends/mysql/000077500000000000000000000000001454762243100274745ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/mysql/__init__.py000066400000000000000000000000001454762243100315730ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/mysql/configuration.py000066400000000000000000000015431454762243100327200ustar00rootroot00000000000000from typing_extensions import final from django_test_migrations.db.backends.base.configuration import ( BaseDatabaseConfiguration, ) from django_test_migrations.types import DatabaseSettingValue @final class DatabaseConfiguration(BaseDatabaseConfiguration): """Interact with MySQL database configuration.""" vendor = 'mysql' statement_timeout = 'MAX_EXECUTION_TIME' def get_setting_value(self, name: str) -> DatabaseSettingValue: """Retrieve value of MySQL database's setting with ``name``.""" with self.connection.cursor() as cursor: cursor.execute( 'SELECT @@{0};'.format(self.connection.ops.quote_name(name)), ) setting_value = cursor.fetchone() if not setting_value: return super().get_setting_value(name) return setting_value[0] python-django-test-migrations-1.3.0/django_test_migrations/db/backends/postgresql/000077500000000000000000000000001454762243100305325ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/postgresql/__init__.py000066400000000000000000000000001454762243100326310ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/backends/postgresql/configuration.py000066400000000000000000000017371454762243100337630ustar00rootroot00000000000000from typing_extensions import final from django_test_migrations.db.backends.base.configuration import ( BaseDatabaseConfiguration, ) from django_test_migrations.types import DatabaseSettingValue @final class DatabaseConfiguration(BaseDatabaseConfiguration): """Interact with PostgreSQL database configuration.""" vendor = 'postgresql' statement_timeout = 'statement_timeout' def get_setting_value(self, name: str) -> DatabaseSettingValue: """Retrieve value of PostgreSQL database's setting with ``name``.""" with self.connection.cursor() as cursor: cursor.execute( ( 'SELECT setting FROM pg_settings ' + # noqa: S608 'WHERE name = %s;' # noqa: WPS323 ), (name,), ) setting_value = cursor.fetchone() if not setting_value: return super().get_setting_value(name) return setting_value[0] python-django-test-migrations-1.3.0/django_test_migrations/db/backends/registry.py000066400000000000000000000022241454762243100305510ustar00rootroot00000000000000from typing import MutableMapping, Type from typing_extensions import TYPE_CHECKING from django_test_migrations.db.backends.exceptions import ( DatabaseConfigurationNotFound, ) from django_test_migrations.types import AnyConnection if TYPE_CHECKING: from django_test_migrations.db.backends.base.configuration import ( BaseDatabaseConfiguration, ) _DatabaseConfigurationMapping = MutableMapping[ str, Type['BaseDatabaseConfiguration'], ] database_configuration_registry: _DatabaseConfigurationMapping = {} def get_database_configuration( connection: AnyConnection, ) -> 'BaseDatabaseConfiguration': """Return proper ``BaseDatabaseConfiguration`` subclass instance. Raises: DatabaseConfigurationNotFound when vendor extracted from ``connection`` doesn't support interaction with database configuration/settings """ vendor = getattr(connection, 'vendor', '') try: database_configuration_class = database_configuration_registry[vendor] except KeyError as exc: raise DatabaseConfigurationNotFound(vendor) from exc return database_configuration_class(connection) python-django-test-migrations-1.3.0/django_test_migrations/db/checks/000077500000000000000000000000001454762243100260155ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/checks/__init__.py000066400000000000000000000000001454762243100301140ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/db/checks/statement_timeout.py000066400000000000000000000066251454762243100321520ustar00rootroot00000000000000import datetime from typing import List from django.core.checks import CheckMessage, Warning from django.db import connections from typing_extensions import Final from django_test_migrations.db.backends import exceptions, registry from django_test_migrations.db.backends.base.configuration import ( BaseDatabaseConfiguration, ) from django_test_migrations.logic.datetime import timedelta_to_milliseconds from django_test_migrations.types import AnyConnection #: We use this value as a unique identifier of databases related check. CHECK_NAME: Final = 'django_test_migrations.checks.database_configuration' STATEMENT_TIMEOUT_MINUTES_UPPER_LIMIT: Final = 30 def check_statement_timeout_setting(*args, **kwargs) -> List[CheckMessage]: """Check if statements' timeout settings is properly configured.""" messages: List[CheckMessage] = [] for connection in connections.all(): _check_statement_timeout_setting(connection, messages) return messages def _check_statement_timeout_setting( connection: AnyConnection, messages: List[CheckMessage], ) -> None: try: database_configuration = registry.get_database_configuration( connection, ) except exceptions.DatabaseConfigurationNotFound: return try: setting_value = int(database_configuration.get_setting_value( database_configuration.statement_timeout, )) except exceptions.DatabaseConfigurationSettingNotFound: return _ensure_statement_timeout_is_set( database_configuration, setting_value, messages, ) _ensure_statement_timeout_not_too_high( database_configuration, setting_value, messages, ) def _ensure_statement_timeout_is_set( database_configuration: BaseDatabaseConfiguration, setting_value: int, messages: List[CheckMessage], ) -> None: if not setting_value: messages.append( Warning( '{0}: statement timeout "{1}" setting is not set.'.format( database_configuration.connection.alias, database_configuration.statement_timeout, ), hint=( 'Set "{0}" database setting to some reasonable value.' ).format(database_configuration.statement_timeout), id='{0}.W001'.format(CHECK_NAME), ), ) def _ensure_statement_timeout_not_too_high( database_configuration: BaseDatabaseConfiguration, setting_value: int, messages: List[CheckMessage], ) -> None: upper_limit = timedelta_to_milliseconds( datetime.timedelta(minutes=STATEMENT_TIMEOUT_MINUTES_UPPER_LIMIT), ) if setting_value > upper_limit: messages.append( Warning( ( '{0}: statement timeout "{1}" setting value - "{2} ms" ' + 'might be too high.' ).format( database_configuration.connection.alias, database_configuration.statement_timeout, setting_value, ), hint=( 'Set "{0}" database setting to some ' + 'reasonable value, but remember it should not be ' + 'too high.' ).format(database_configuration.statement_timeout), id='{0}.W002'.format(CHECK_NAME), ), ) python-django-test-migrations-1.3.0/django_test_migrations/exceptions.py000066400000000000000000000007541454762243100267310ustar00rootroot00000000000000from django_test_migrations.types import MigrationTarget class MigrationNotInPlan(Exception): """``MigrationTarget`` not found in migrations plan.""" def __init__(self, migration_target: MigrationTarget) -> None: # noqa: D107 self.migration_target = migration_target def __str__(self) -> str: """String representation of exception's instance.""" return 'Migration {0} not found in migrations plan'.format( self.migration_target, ) python-django-test-migrations-1.3.0/django_test_migrations/logic/000077500000000000000000000000001454762243100252655ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/logic/__init__.py000066400000000000000000000000001454762243100273640ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/logic/datetime.py000066400000000000000000000003001454762243100274240ustar00rootroot00000000000000import datetime def timedelta_to_milliseconds(timedelta: datetime.timedelta) -> int: """Convert ``timedelta`` object to milliseconds.""" return int(timedelta.total_seconds() * 1000) python-django-test-migrations-1.3.0/django_test_migrations/logic/migrations.py000066400000000000000000000005421454762243100300140ustar00rootroot00000000000000from typing import List from django_test_migrations.types import MigrationSpec, MigrationTarget def normalize(migration_target: MigrationSpec) -> List[MigrationTarget]: """Normalize ``migration_target`` to expected format.""" if not isinstance(migration_target, list): migration_target = [migration_target] return migration_target python-django-test-migrations-1.3.0/django_test_migrations/migrator.py000066400000000000000000000057371454762243100264020ustar00rootroot00000000000000from typing import Optional from django.core.management import call_command from django.core.management.color import no_style from django.db import DEFAULT_DB_ALIAS, connections from django.db.migrations.executor import MigrationExecutor from django.db.migrations.state import ProjectState from django_test_migrations import sql from django_test_migrations.logic.migrations import normalize from django_test_migrations.plan import truncate_plan from django_test_migrations.signals import mute_migrate_signals from django_test_migrations.types import MigrationPlan, MigrationSpec class Migrator(object): """ Class to manage your migrations and app state. It is designed to be used inside the tests to ensure that migrations are working as intended: both data and schema migrations. This class can be but probably should not be used directly. Because we have utility test framework integrations for ``unitest`` and ``pytest``. Use them for better experience. """ def __init__( self, database: Optional[str] = None, ) -> None: """That's where we initialize all required internals.""" if database is None: database = DEFAULT_DB_ALIAS self._database: str = database self._executor = MigrationExecutor(connections[self._database]) def apply_initial_migration(self, targets: MigrationSpec) -> ProjectState: """Reverse back to the original migration.""" targets = normalize(targets) style = no_style() # start from clean database state sql.drop_models_tables(self._database, style) sql.flush_django_migrations_table(self._database, style) # prepare as broad plan as possible based on full plan self._executor.loader.build_graph() # reload full_plan = self._executor.migration_plan( self._executor.loader.graph.leaf_nodes(), clean_start=True, ) plan = truncate_plan(targets, full_plan) # apply all migrations from generated plan on clean database # (only forward, so any unexpected migration won't be applied) # to restore database state before tested migration return self._migrate(targets, plan=plan) def apply_tested_migration(self, targets: MigrationSpec) -> ProjectState: """Apply the next migration.""" self._executor.loader.build_graph() # reload return self._migrate(normalize(targets)) def reset(self) -> None: """ Reset the state to the most recent one. Notably, signals are not muted here to avoid https://github.com/wemake-services/django-test-migrations/issues/128 """ call_command('migrate', verbosity=0, database=self._database) def _migrate( self, migration_targets: MigrationSpec, plan: Optional[MigrationPlan] = None, ) -> ProjectState: with mute_migrate_signals(): return self._executor.migrate(migration_targets, plan=plan) python-django-test-migrations-1.3.0/django_test_migrations/plan.py000066400000000000000000000107651454762243100255050ustar00rootroot00000000000000from typing import List, Optional, Set, Tuple from django.apps import apps from django.db import DEFAULT_DB_ALIAS, connections from django.db.migrations import Migration from django.db.migrations.graph import Node from django.db.migrations.loader import MigrationLoader from django_test_migrations.exceptions import MigrationNotInPlan from django_test_migrations.types import MigrationPlan, MigrationTarget def all_migrations( database: str = DEFAULT_DB_ALIAS, app_names: Optional[List[str]] = None, ) -> List[Node]: """ Returns the sorted list of migrations nodes. The order is the same as when migrations are applied. When you might need this function? When you are testing the migration order. For example, imagine that you have a direct dependency: ``main_app.0002_migration`` and ``other_app.0001_initial`` where ``other_app.0001_initial`` relies on the model or field introduced in ``main_app.0002_migration``. You can use ``dependencies`` field to ensure that everything works correctly. But, sometimes migrations are squashed, sometimes they are renamed, refactored, and moved. It would be better to have a test that will ensure that ``other_app.0001_initial`` comes after ``main_app.0002_migration``. And everything works as expected. """ loader = MigrationLoader(connections[database]) if app_names: _validate_app_names(app_names) targets = [ key for key in loader.graph.leaf_nodes() if key[0] in app_names ] else: targets = loader.graph.leaf_nodes() return _generate_plan(targets, loader) def nodes_to_tuples(nodes: List[Node]) -> List[Tuple[str, str]]: """Utility function to transform nodes to tuples.""" return [ (node[0], node[1]) for node in nodes ] def _validate_app_names(app_names: List[str]) -> None: """ Validates the provided app names. Raises ```LookupError`` when incorrect app names are provided. """ for app_name in app_names: apps.get_app_config(app_name) def _generate_plan( targets: List[Node], loader: MigrationLoader, ) -> List[Node]: plan = [] seen: Set[Node] = set() # Generate the plan for target in targets: for migration in loader.graph.forwards_plan(target): if migration not in seen: node = loader.graph.node_map[migration] plan.append(node) seen.add(migration) return plan def truncate_plan( targets: List[MigrationTarget], plan: MigrationPlan, ) -> MigrationPlan: """Truncate migrations ``plan`` up to ``targets``. This method is used mainly to truncate full/clean migrations plan to get as broad plan as possible. By "broad" plan we mean plan with all targets migrations included as well as all older migrations not related with targets. "Broad" plan is needed mostly to make ``Migrator`` API developers' friendly, just to not force developers to include migrations targets for all other models they want to use in test (e.g. to setup some model instances) in ``migrate_from``. Such plan will also produce database state really similar to state from our production environment just before new migrations are applied. Migrations plan for targets generated by Django's ``MigrationExecutor.migration_plan`` is minimum plan needed to apply targets migrations, it includes only migrations targets with all its dependencies, so it doesn't fit to our approach, that's why following function is needed. """ if not targets or not plan: return plan target_max_index = max(_get_index(target, plan) for target in targets) return plan[:(target_max_index + 1)] def _get_index(target: MigrationTarget, plan: MigrationPlan) -> int: try: index = next( index for index, (migration, _) in enumerate(plan) # noqa: WPS405, WPS414 if _filter_predicate(target, migration) ) except StopIteration: raise MigrationNotInPlan(target) return index - (target[1] is None) def _filter_predicate(target: MigrationTarget, migration: Migration) -> bool: # when ``None`` passed as migration name then initial migration from # target's app will be chosen and handled properly in ``_get_index`` # so in final all target app migrations will be excluded from plan index = 2 - (target[1] is None) return (migration.app_label, migration.name)[:index] == target[:index] python-django-test-migrations-1.3.0/django_test_migrations/py.typed000066400000000000000000000000001454762243100256550ustar00rootroot00000000000000python-django-test-migrations-1.3.0/django_test_migrations/signals.py000066400000000000000000000010241454762243100261770ustar00rootroot00000000000000from contextlib import contextmanager from django.db.models.signals import post_migrate, pre_migrate @contextmanager def mute_migrate_signals(): """Context manager to mute migration-related signals.""" pre_migrate_receivers = pre_migrate.receivers post_migrate_receivers = post_migrate.receivers pre_migrate.receivers = [] post_migrate.receivers = [] yield pre_migrate_receivers, post_migrate_receivers pre_migrate.receivers = pre_migrate_receivers post_migrate.receivers = post_migrate_receivers python-django-test-migrations-1.3.0/django_test_migrations/sql.py000066400000000000000000000032041454762243100253400ustar00rootroot00000000000000from typing import Optional from django.core.management.color import Style, no_style from django.db import connections from typing_extensions import Final DJANGO_MIGRATIONS_TABLE_NAME: Final = 'django_migrations' def drop_models_tables( database_name: str, style: Optional[Style] = None, ) -> None: """Drop all installed Django's models tables.""" style = style or no_style() connection = connections[database_name] tables = connection.introspection.django_table_names( only_existing=True, include_views=False, ) sql_drop_tables = [ connection.SchemaEditorClass.sql_delete_table % { 'table': style.SQL_FIELD(connection.ops.quote_name(table)), } for table in tables ] if sql_drop_tables: if connection.vendor == 'mysql': sql_drop_tables = [ 'SET FOREIGN_KEY_CHECKS = 0;', *sql_drop_tables, 'SET FOREIGN_KEY_CHECKS = 1;', ] connection.ops.execute_sql_flush(sql_drop_tables) def flush_django_migrations_table( database_name: str, style: Optional[Style] = None, ) -> None: """Flush `django_migrations` table. `django_migrations` is not "regular" Django model, so its not returned by ``ConnectionRouter.get_migratable_models`` which is used e.g. to implement sequences reset. """ connection = connections[database_name] connection.ops.execute_sql_flush( connection.ops.sql_flush( style or no_style(), [DJANGO_MIGRATIONS_TABLE_NAME], allow_cascade=False, reset_sequences=True, ), ) python-django-test-migrations-1.3.0/django_test_migrations/types.py000066400000000000000000000011551454762243100257100ustar00rootroot00000000000000from typing import List, Optional, Tuple, Union from django.db.backends.base.base import BaseDatabaseWrapper from django.db.migrations import Migration from django.utils.connection import ConnectionProxy # Migration target: (app_name, migration_name) # Regular or rollback migration: 0001 -> 0002, or 0002 -> 0001 # Rollback migration to initial state: 0001 -> None MigrationTarget = Tuple[str, Optional[str]] MigrationSpec = Union[MigrationTarget, List[MigrationTarget]] MigrationPlan = List[Tuple[Migration, bool]] AnyConnection = Union[ConnectionProxy, BaseDatabaseWrapper] DatabaseSettingValue = Union[str, int] python-django-test-migrations-1.3.0/docker-compose.yml000066400000000000000000000013471454762243100230750ustar00rootroot00000000000000--- version: '3.8' services: postgresql-db: image: postgres:13 ports: - "5432:5432" environment: - POSTGRES_USER=django - POSTGRES_PASSWORD=passwd123 - POSTGRES_DB=db mysql-db: image: mysql:8 ports: - "3306:3306" environment: - MYSQL_USER=django - MYSQL_PASSWORD=passwd123 # NOTE: MySQL container entrypoint gives user `${MYSQL_USER}` access # only to `${MYSQL_DATABASE}` database, so we are setting # `${MYSQL_DATABASE}` to Django default test database's name to avoid # overriding `ENTRYPOINT` or `CMD`. - MYSQL_DATABASE=test_db - MYSQL_ROOT_PASSWORD=superpasswd123 command: --default-authentication-plugin=mysql_native_password python-django-test-migrations-1.3.0/poetry.lock000066400000000000000000002761611454762243100216440ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "asgiref" version = "3.6.0" description = "ASGI specs, helper code, and adapters" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, ] [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "astor" version = "0.8.1" description = "Read/rewrite/write Python ASTs" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, ] [[package]] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "autorepr" version = "0.3.0" description = "Makes civilized __repr__, __str__, and __unicode__ methods" category = "dev" optional = false python-versions = "*" files = [ {file = "autorepr-0.3.0-py2-none-any.whl", hash = "sha256:c34567e4073630feb52d9c788fc198085e9e9de4817e3b93b7c4c534fc689f11"}, {file = "autorepr-0.3.0-py2.py3-none-any.whl", hash = "sha256:1d9010d14fb325d3961e3aa73692685563f97d6ba4a2f0f735329fb37422599c"}, {file = "autorepr-0.3.0.tar.gz", hash = "sha256:ef770b84793d5433e6bb893054973b8c7ce6b487274f9c3f734f678cae11e85e"}, ] [[package]] name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] [package.extras] tzdata = ["tzdata"] [[package]] name = "bandit" version = "1.7.5" description = "Security oriented static analyser for python code." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, ] [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=5.3.1" rich = "*" stevedore = ">=1.20.0" [package.extras] test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] [[package]] name = "cattrs" version = "22.2.0" description = "Composable complex class support for attrs and dataclasses." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "cattrs-22.2.0-py3-none-any.whl", hash = "sha256:bc12b1f0d000b9f9bee83335887d532a1d3e99a833d1bf0882151c97d3e68c21"}, {file = "cattrs-22.2.0.tar.gz", hash = "sha256:f0eed5642399423cf656e7b66ce92cdc5b963ecafd041d1b24d136fdde7acf6d"}, ] [package.dependencies] attrs = ">=20" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} [[package]] name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] [[package]] name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, ] [[package]] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "configupdater" version = "3.1.1" description = "Parser like ConfigParser but for updating configuration files" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "ConfigUpdater-3.1.1-py2.py3-none-any.whl", hash = "sha256:805986dbeba317886c7a8d348b2e34986dc9e3128cd3761ecc35decbd372b286"}, {file = "ConfigUpdater-3.1.1.tar.gz", hash = "sha256:46f0c74d73efa723776764b43c9739f68052495dd3d734319c1d0eb58511f15b"}, ] [package.extras] testing = ["flake8", "pytest", "pytest-cov", "pytest-virtualenv", "pytest-xdist", "sphinx"] [[package]] name = "coverage" version = "7.2.4" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "coverage-7.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e5eedde6e6e241ec3816f05767cc77e7456bf5ec6b373fb29917f0990e2078f"}, {file = "coverage-7.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c6c6e3b8fb6411a2035da78d86516bfcfd450571d167304911814407697fb7a"}, {file = "coverage-7.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7668a621afc52db29f6867e0e9c72a1eec9f02c94a7c36599119d557cf6e471"}, {file = "coverage-7.2.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfb53bef4b2739ff747ebbd76d6ac5384371fd3c7a8af08899074eba034d483"}, {file = "coverage-7.2.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5c4f2e44a2ae15fa6883898e756552db5105ca4bd918634cbd5b7c00e19e8a1"}, {file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:700bc9fb1074e0c67c09fe96a803de66663830420781df8dc9fb90d7421d4ccb"}, {file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ac4861241e693e21b280f07844ae0e0707665e1dfcbf9466b793584984ae45c4"}, {file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3d6f3c5b6738a494f17c73b4aa3aa899865cc33a74aa85e3b5695943b79ad3ce"}, {file = "coverage-7.2.4-cp310-cp310-win32.whl", hash = "sha256:437da7d2fcc35bf45e04b7e9cfecb7c459ec6f6dc17a8558ed52e8d666c2d9ab"}, {file = "coverage-7.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:1d3893f285fd76f56651f04d1efd3bdce251c32992a64c51e5d6ec3ba9e3f9c9"}, {file = "coverage-7.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a17bf32e9e3333d78606ac1073dd20655dc0752d5b923fa76afd3bc91674ab4"}, {file = "coverage-7.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f7ffdb3af2a01ce91577f84fc0faa056029fe457f3183007cffe7b11ea78b23c"}, {file = "coverage-7.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89e63b38c7b888e00fd42ce458f838dccb66de06baea2da71801b0fc9070bfa0"}, {file = "coverage-7.2.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4522dd9aeb9cc2c4c54ce23933beb37a4e106ec2ba94f69138c159024c8a906a"}, {file = "coverage-7.2.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29c7d88468f01a75231797173b52dc66d20a8d91b8bb75c88fc5861268578f52"}, {file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bc47015fc0455753e8aba1f38b81b731aaf7f004a0c390b404e0fcf1d6c1d72f"}, {file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c122d120c11a236558c339a59b4b60947b38ac9e3ad30a0e0e02540b37bf536"}, {file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:50fda3d33b705b9c01e3b772cfa7d14de8aec2ec2870e4320992c26d057fde12"}, {file = "coverage-7.2.4-cp311-cp311-win32.whl", hash = "sha256:ab08af91cf4d847a6e15d7d5eeae5fead1487caf16ff3a2056dbe64d058fd246"}, {file = "coverage-7.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:876e4ef3eff00b50787867c5bae84857a9af4c369a9d5b266cd9b19f61e48ef7"}, {file = "coverage-7.2.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3fc9cde48de956bfbacea026936fbd4974ff1dc2f83397c6f1968f0142c9d50b"}, {file = "coverage-7.2.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12bc9127c8aca2f7c25c9acca53da3db6799b2999b40f28c2546237b7ea28459"}, {file = "coverage-7.2.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2857894c22833d3da6e113623a9b7440159b2295280b4e0d954cadbfa724b85a"}, {file = "coverage-7.2.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4db4e6c115d869cd5397d3d21fd99e4c7053205c33a4ae725c90d19dcd178af"}, {file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f37ae1804596f13d811e0247ffc8219f5261b3565bdf45fcbb4fc091b8e9ff35"}, {file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdee9a77fd0ce000781680b6a1f4b721c567f66f2f73a49be1843ff439d634f3"}, {file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b65a6a5484b7f2970393d6250553c05b2ede069e0e18abe907fdc7f3528252e"}, {file = "coverage-7.2.4-cp37-cp37m-win32.whl", hash = "sha256:1a3e8697cb40f28e5bcfb6f4bda7852d96dbb6f6fd7cc306aba4ae690c9905ab"}, {file = "coverage-7.2.4-cp37-cp37m-win_amd64.whl", hash = "sha256:4078939c4b7053e14e87c65aa68dbed7867e326e450f94038bfe1a1b22078ff9"}, {file = "coverage-7.2.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:603a2b172126e3b08c11ca34200143089a088cd0297d4cfc4922d2c1c3a892f9"}, {file = "coverage-7.2.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72751d117ceaad3b1ea3bcb9e85f5409bbe9fb8a40086e17333b994dbccc0718"}, {file = "coverage-7.2.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f19ba9301e6fb0b94ba71fda9a1b02d11f0aab7f8e2455122a4e2921b6703c2f"}, {file = "coverage-7.2.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d784177a7fb9d0f58d24d3e60638c8b729c3693963bf67fa919120f750db237"}, {file = "coverage-7.2.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d2a9180beff1922b09bd7389e23454928e108449e646c26da5c62e29b0bf4e3"}, {file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:39747afc854a7ee14e5e132da7db179d6281faf97dc51e6d7806651811c47538"}, {file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60feb703abc8d78e9427d873bcf924c9e30cf540a21971ef5a17154da763b60f"}, {file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2becddfcbf3d994a8f4f9dd2b6015cae3a3eff50dedc6e4a17c3cccbe8f93d4"}, {file = "coverage-7.2.4-cp38-cp38-win32.whl", hash = "sha256:56a674ad18d6b04008283ca03c012be913bf89d91c0803c54c24600b300d9e51"}, {file = "coverage-7.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:ab08e03add2cf5793e66ac1bbbb24acfa90c125476f5724f5d44c56eeec1d635"}, {file = "coverage-7.2.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92b565c51732ea2e7e541709ccce76391b39f4254260e5922e08e00971e88e33"}, {file = "coverage-7.2.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8769a67e8816c7e94d5bf446fc0501641fde78fdff362feb28c2c64d45d0e9b1"}, {file = "coverage-7.2.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d74d6fbd5a98a5629e8467b719b0abea9ca01a6b13555d125c84f8bf4ea23d"}, {file = "coverage-7.2.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9f770c6052d9b5c9b0e824fd8c003fe33276473b65b4f10ece9565ceb62438e"}, {file = "coverage-7.2.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3023ce23e41a6f006c09f7e6d62b6c069c36bdc9f7de16a5ef823acc02e6c63"}, {file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fabd1f4d12dfd6b4f309208c2f31b116dc5900e0b42dbafe4ee1bc7c998ffbb0"}, {file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e41a7f44e73b37c6f0132ecfdc1c8b67722f42a3d9b979e6ebc150c8e80cf13a"}, {file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:864e36947289be05abd83267c4bade35e772526d3e9653444a9dc891faf0d698"}, {file = "coverage-7.2.4-cp39-cp39-win32.whl", hash = "sha256:ea534200efbf600e60130c48552f99f351cae2906898a9cd924c1c7f2fb02853"}, {file = "coverage-7.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:00f8fd8a5fe1ffc3aef78ea2dbf553e5c0f4664324e878995e38d41f037eb2b3"}, {file = "coverage-7.2.4-pp37.pp38.pp39-none-any.whl", hash = "sha256:856bcb837e96adede31018a0854ce7711a5d6174db1a84e629134970676c54fa"}, {file = "coverage-7.2.4.tar.gz", hash = "sha256:7283f78d07a201ac7d9dc2ac2e4faaea99c4d302f243ee5b4e359f3e170dc008"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, ] [[package]] name = "dictdiffer" version = "0.9.0" description = "Dictdiffer is a library that helps you to diff and patch dictionaries." category = "dev" optional = false python-versions = "*" files = [ {file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"}, {file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"}, ] [package.extras] all = ["Sphinx (>=3)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "tox (>=3.7.0)"] docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"] numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"] tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "tox (>=3.7.0)"] [[package]] name = "django" version = "4.2" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "Django-4.2-py3-none-any.whl", hash = "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78"}, {file = "Django-4.2.tar.gz", hash = "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"}, ] [package.dependencies] asgiref = ">=3.6.0,<4" "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] [[package]] name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, ] [[package]] name = "dparse" version = "0.6.2" description = "A parser for Python dependency files" category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "dparse-0.6.2-py3-none-any.whl", hash = "sha256:8097076f1dd26c377f30d4745e6ec18fef42f3bf493933b842ac5bafad8c345f"}, {file = "dparse-0.6.2.tar.gz", hash = "sha256:d45255bda21f998bc7ddf2afd5e62505ba6134756ba2d42a84c56b0826614dfe"}, ] [package.dependencies] packaging = "*" toml = "*" [package.extras] conda = ["pyyaml"] pipenv = ["pipenv"] [[package]] name = "dpath" version = "2.1.5" description = "Filesystem-like pathing and searching for dictionaries" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "dpath-2.1.5-py3-none-any.whl", hash = "sha256:559edcbfc806ca2f9ad9e63566f22e5d41c000e4215bbce9dbf1ca4c859f5e0b"}, {file = "dpath-2.1.5.tar.gz", hash = "sha256:ccd964db839baad4aa820612b4b8731b09f40a245d401b723156ce4ef45b22b7"}, ] [[package]] name = "eradicate" version = "2.2.0" description = "Removes commented-out code." category = "dev" optional = false python-versions = "*" files = [ {file = "eradicate-2.2.0-py3-none-any.whl", hash = "sha256:751813c315a48ce7e3d0483410991015342d380a956e86e0265c61bfb875bcbc"}, {file = "eradicate-2.2.0.tar.gz", hash = "sha256:c329a05def6a4b558dab58bb1b694f5209706b7c99ba174d226dfdb69a5ba0da"}, ] [[package]] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "flake8" version = "4.0.1" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] [package.dependencies] mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "flake8-bandit" version = "3.0.0" description = "Automated security testing with bandit and flake8." category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "flake8_bandit-3.0.0-py2.py3-none-any.whl", hash = "sha256:61b617f4f7cdaa0e2b1e6bf7b68afb2b619a227bb3e3ae00dd36c213bd17900a"}, {file = "flake8_bandit-3.0.0.tar.gz", hash = "sha256:54d19427e6a8d50322a7b02e1841c0a7c22d856975f3459803320e0e18e2d6a1"}, ] [package.dependencies] bandit = ">=1.7.3" flake8 = "*" flake8-polyfill = "*" pycodestyle = "*" [[package]] name = "flake8-broken-line" version = "0.5.0" description = "Flake8 plugin to forbid backslashes for line breaks" category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ {file = "flake8-broken-line-0.5.0.tar.gz", hash = "sha256:7c98de9dd1385b71e888709c7f2aee3f0514107ecb5875bc95d0c03392191c97"}, {file = "flake8_broken_line-0.5.0-py3-none-any.whl", hash = "sha256:daafb19b67eead0410ce7ba155d51a15b9d020ebe7630d87de9c2b93cedb6703"}, ] [package.dependencies] flake8 = ">=3.5,<6" [[package]] name = "flake8-bugbear" version = "22.12.6" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "flake8-bugbear-22.12.6.tar.gz", hash = "sha256:4cdb2c06e229971104443ae293e75e64c6107798229202fbe4f4091427a30ac0"}, {file = "flake8_bugbear-22.12.6-py3-none-any.whl", hash = "sha256:b69a510634f8a9c298dfda2b18a8036455e6b19ecac4fe582e4d7a0abfa50a30"}, ] [package.dependencies] attrs = ">=19.2.0" flake8 = ">=3.0.0" [package.extras] dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "tox"] [[package]] name = "flake8-commas" version = "2.1.0" description = "Flake8 lint for trailing commas." category = "dev" optional = false python-versions = "*" files = [ {file = "flake8-commas-2.1.0.tar.gz", hash = "sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263"}, {file = "flake8_commas-2.1.0-py2.py3-none-any.whl", hash = "sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"}, ] [package.dependencies] flake8 = ">=2" [[package]] name = "flake8-comprehensions" version = "3.12.0" description = "A flake8 plugin to help you write better list/set/dict comprehensions." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "flake8_comprehensions-3.12.0-py3-none-any.whl", hash = "sha256:013234637ec7dfcb7cd2900578fb53c512f81db909cefe371c019232695c362d"}, {file = "flake8_comprehensions-3.12.0.tar.gz", hash = "sha256:419ef1a6e8de929203791a5e8ff5e3906caeba13eb3290eebdbf88a9078d502e"}, ] [package.dependencies] flake8 = ">=3.0,<3.2.0 || >3.2.0" [[package]] name = "flake8-debugger" version = "4.1.2" description = "ipdb/pdb statement checker plugin for flake8" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "flake8-debugger-4.1.2.tar.gz", hash = "sha256:52b002560941e36d9bf806fca2523dc7fb8560a295d5f1a6e15ac2ded7a73840"}, {file = "flake8_debugger-4.1.2-py3-none-any.whl", hash = "sha256:0a5e55aeddcc81da631ad9c8c366e7318998f83ff00985a49e6b3ecf61e571bf"}, ] [package.dependencies] flake8 = ">=3.0" pycodestyle = "*" [[package]] name = "flake8-docstrings" version = "1.7.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, ] [package.dependencies] flake8 = ">=3" pydocstyle = ">=2.1" [[package]] name = "flake8-eradicate" version = "1.4.0" description = "Flake8 plugin to find commented out code" category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ {file = "flake8-eradicate-1.4.0.tar.gz", hash = "sha256:3088cfd6717d1c9c6c3ac45ef2e5f5b6c7267f7504d5a74b781500e95cb9c7e1"}, {file = "flake8_eradicate-1.4.0-py3-none-any.whl", hash = "sha256:e3bbd0871be358e908053c1ab728903c114f062ba596b4d40c852fd18f473d56"}, ] [package.dependencies] attrs = "*" eradicate = ">=2.0,<3.0" flake8 = ">=3.5,<6" [[package]] name = "flake8-isort" version = "4.2.0" description = "flake8 plugin that integrates isort ." category = "dev" optional = false python-versions = "*" files = [ {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, ] [package.dependencies] flake8 = ">=3.2.1,<6" isort = ">=4.3.5,<6" [package.extras] test = ["pytest-cov"] [[package]] name = "flake8-plugin-utils" version = "1.3.2" description = "The package provides base classes and utils for flake8 plugin writing" category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ {file = "flake8-plugin-utils-1.3.2.tar.gz", hash = "sha256:20fa2a8ca2decac50116edb42e6af0a1253ef639ad79941249b840531889c65a"}, {file = "flake8_plugin_utils-1.3.2-py3-none-any.whl", hash = "sha256:1fe43e3e9acf3a7c0f6b88f5338cad37044d2f156c43cb6b080b5f9da8a76f06"}, ] [[package]] name = "flake8-polyfill" version = "1.0.2" description = "Polyfill package for Flake8 plugins" category = "dev" optional = false python-versions = "*" files = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] [package.dependencies] flake8 = "*" [[package]] name = "flake8-pytest-style" version = "1.7.2" description = "A flake8 plugin checking common style issues or inconsistencies with pytest-based tests." category = "dev" optional = false python-versions = ">=3.7.2,<4.0.0" files = [ {file = "flake8_pytest_style-1.7.2-py3-none-any.whl", hash = "sha256:f5d2aa3219163a052dd92226589d45fab8ea027a3269922f0c4029f548ea5cd1"}, {file = "flake8_pytest_style-1.7.2.tar.gz", hash = "sha256:b924197c99b951315949920b0e5547f34900b1844348432e67a44ab191582109"}, ] [package.dependencies] flake8-plugin-utils = ">=1.3.2,<2.0.0" [[package]] name = "flake8-quotes" version = "3.3.2" description = "Flake8 lint for quotes." category = "dev" optional = false python-versions = "*" files = [ {file = "flake8-quotes-3.3.2.tar.gz", hash = "sha256:6e26892b632dacba517bf27219c459a8396dcfac0f5e8204904c5a4ba9b480e1"}, ] [package.dependencies] flake8 = "*" [[package]] name = "flake8-rst-docstrings" version = "0.2.7" description = "Python docstring reStructuredText (RST) validator" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "flake8-rst-docstrings-0.2.7.tar.gz", hash = "sha256:2740067ab9237559dd45a3434d8c987792c7b259ca563621a3b95efe201f5382"}, {file = "flake8_rst_docstrings-0.2.7-py3-none-any.whl", hash = "sha256:5d56075dce360bcc9c6775bfe7cb431aa395de600ca7e8d40580a28d50b2a803"}, ] [package.dependencies] flake8 = ">=3.0.0" pygments = "*" restructuredtext-lint = "*" [[package]] name = "flake8-string-format" version = "0.3.0" description = "string format checker, plugin for flake8" category = "dev" optional = false python-versions = "*" files = [ {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"}, {file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"}, ] [package.dependencies] flake8 = "*" [[package]] name = "flatten-dict" version = "0.4.2" description = "A flexible utility for flattening and unflattening dict-like objects in Python." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "flatten-dict-0.4.2.tar.gz", hash = "sha256:506a96b6e6f805b81ae46a0f9f31290beb5fa79ded9d80dbe1b7fa236ab43076"}, {file = "flatten_dict-0.4.2-py2.py3-none-any.whl", hash = "sha256:7e245b20c4c718981212210eec4284a330c9f713e632e98765560e05421e48ad"}, ] [package.dependencies] six = ">=1.12,<2.0" [[package]] name = "furl" version = "2.1.3" description = "URL manipulation made simple." category = "dev" optional = false python-versions = "*" files = [ {file = "furl-2.1.3-py2.py3-none-any.whl", hash = "sha256:9ab425062c4217f9802508e45feb4a83e54324273ac4b202f1850363309666c0"}, {file = "furl-2.1.3.tar.gz", hash = "sha256:5a6188fe2666c484a12159c18be97a1977a71d632ef5bb867ef15f54af39cc4e"}, ] [package.dependencies] orderedmultidict = ">=1.0.1" six = ">=1.8.0" [[package]] name = "gitdb" version = "4.0.10" description = "Git Object Database" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.31" description = "GitPython is a Python library used to interact with Git repositories" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "GitPython-3.1.31-py3-none-any.whl", hash = "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"}, {file = "GitPython-3.1.31.tar.gz", hash = "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [[package]] name = "identify" version = "2.5.23" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "identify-2.5.23-py2.py3-none-any.whl", hash = "sha256:17d9351c028a781456965e781ed2a435755cac655df1ebd930f7186b54399312"}, {file = "identify-2.5.23.tar.gz", hash = "sha256:50b01b9d5f73c6b53e5fa2caf9f543d3e657a9d0bbdeb203ebb8d45960ba7433"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] [[package]] name = "importlib-metadata" version = "6.6.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" version = "5.12.0" description = "Read resources from Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false python-versions = ">=3.8.0" files = [ {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, ] [package.extras] colors = ["colorama (>=0.4.3)"] pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] [[package]] name = "loguru" version = "0.7.0" description = "Python logging made (stupidly) simple" category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, ] [package.dependencies] colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] [[package]] name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "marshmallow" version = "3.19.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "marshmallow-3.19.0-py3-none-any.whl", hash = "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"}, {file = "marshmallow-3.19.0.tar.gz", hash = "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.3.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] name = "marshmallow-polyfield" version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] [package.dependencies] marshmallow = ">=3.0.0b10" [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = "*" files = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "more-itertools" version = "9.1.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, ] [[package]] name = "mypy" version = "1.2.0" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "mypy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d"}, {file = "mypy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"}, {file = "mypy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e"}, {file = "mypy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a"}, {file = "mypy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128"}, {file = "mypy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41"}, {file = "mypy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb"}, {file = "mypy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937"}, {file = "mypy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9"}, {file = "mypy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602"}, {file = "mypy-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140"}, {file = "mypy-1.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336"}, {file = "mypy-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e"}, {file = "mypy-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119"}, {file = "mypy-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a"}, {file = "mypy-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950"}, {file = "mypy-1.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6"}, {file = "mypy-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5"}, {file = "mypy-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8"}, {file = "mypy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed"}, {file = "mypy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f"}, {file = "mypy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521"}, {file = "mypy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238"}, {file = "mypy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48"}, {file = "mypy-1.2.0-py3-none-any.whl", hash = "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394"}, {file = "mypy-1.2.0.tar.gz", hash = "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "nitpick" version = "0.33.1" description = "Enforce the same settings across multiple language-independent projects" category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ {file = "nitpick-0.33.1-py3-none-any.whl", hash = "sha256:bc0fe670f77111b6205cc16d23ef541f087dd317e8b3c941f83f7913643d06cb"}, {file = "nitpick-0.33.1.tar.gz", hash = "sha256:859f7e1ab119d9d9405106f42872f17214f3f0030b76ea50d19fff3411ffc46e"}, ] [package.dependencies] attrs = ">=20.1.0" autorepr = "*" click = "*" ConfigUpdater = "*" dictdiffer = "*" dpath = "*" flake8 = ">=3.0.0" flatten-dict = "*" furl = "*" identify = "*" importlib-resources = {version = "*", markers = "python_version >= \"3.7\" and python_version < \"3.9\""} jmespath = "*" loguru = "*" marshmallow = ">=3.0.0b10" marshmallow-polyfield = ">=5.10,<6.0" more-itertools = "*" pluggy = "*" python-slugify = "*" requests = "*" requests-cache = ">=1.0.0" "ruamel.yaml" = "*" sortedcontainers = "*" StrEnum = "*" toml = "*" tomlkit = ">=0.8.0" [package.extras] doc = ["sphinx", "sphinx-gitref", "sphinx_rtd_theme", "sphobjinv"] lint = ["pylint"] test = ["freezegun", "pytest", "pytest-cov", "pytest-datadir", "pytest-socket", "pytest-testmon", "pytest-watch", "responses", "testfixtures"] [[package]] name = "orderedmultidict" version = "1.0.1" description = "Ordered Multivalue Dictionary" category = "dev" optional = false python-versions = "*" files = [ {file = "orderedmultidict-1.0.1-py2.py3-none-any.whl", hash = "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3"}, {file = "orderedmultidict-1.0.1.tar.gz", hash = "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad"}, ] [package.dependencies] six = ">=1.8.0" [[package]] name = "packaging" version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" category = "dev" optional = false python-versions = ">=2.6" files = [ {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] [[package]] name = "pep8-naming" version = "0.13.2" description = "Check PEP-8 naming conventions, plugin for flake8" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "pep8-naming-0.13.2.tar.gz", hash = "sha256:93eef62f525fd12a6f8c98f4dcc17fa70baae2f37fa1f73bec00e3e44392fa48"}, {file = "pep8_naming-0.13.2-py3-none-any.whl", hash = "sha256:59e29e55c478db69cffbe14ab24b5bd2cd615c0413edf790d47d3fb7ba9a4e23"}, ] [package.dependencies] flake8 = ">=3.9.1" [[package]] name = "platformdirs" version = "3.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, ] [package.extras] docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pycodestyle" version = "2.8.0" description = "Python style guide checker" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] [[package]] name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, ] [package.dependencies] snowballstemmer = ">=2.2.0" [package.extras] toml = ["tomli (>=1.2.3)"] [[package]] name = "pyflakes" version = "2.4.0" description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] [[package]] name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] plugins = ["importlib-metadata"] [[package]] name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "dev" optional = false python-versions = ">=3.6.8" files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" version = "7.3.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" version = "4.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-django" version = "4.5.2" description = "A Django plugin for pytest." category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, ] [package.dependencies] pytest = ">=5.4.0" [package.extras] docs = ["sphinx", "sphinx-rtd-theme"] testing = ["Django", "django-configurations (>=2.0)"] [[package]] name = "pytest-mock" version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, ] [package.dependencies] pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-randomly" version = "3.12.0" description = "Pytest plugin to randomly order tests and control random.seed." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, ] [package.dependencies] importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} pytest = "*" [[package]] name = "python-slugify" version = "8.0.1" description = "A Python slugify application that also handles Unicode" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, ] [package.dependencies] text-unidecode = ">=1.3" [package.extras] unidecode = ["Unidecode (>=1.1.1)"] [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] [[package]] name = "requests" version = "2.29.0" description = "Python HTTP for Humans." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"}, {file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-cache" version = "1.0.1" description = "A persistent cache for python requests" category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ {file = "requests_cache-1.0.1-py3-none-any.whl", hash = "sha256:55c5765c26fd98a38c633d6e3931a507b7708cdd07c0afb48773d0718ac15969"}, {file = "requests_cache-1.0.1.tar.gz", hash = "sha256:d42e6c2f11de54e6a134c9a00c5ca2a3c8edde3c3f2bdfd942586fafa8990e14"}, ] [package.dependencies] attrs = ">=21.2" cattrs = ">=22.2" platformdirs = ">=2.5" requests = ">=2.22" url-normalize = ">=1.4" urllib3 = ">=1.25.5" [package.extras] all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=5.4)", "redis (>=3)", "ujson (>=5.4)"] bson = ["bson (>=0.5)"] docs = ["furo (>=2022.12.7,<2023.0.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.6)"] dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] json = ["ujson (>=5.4)"] mongodb = ["pymongo (>=3)"] redis = ["redis (>=3)"] security = ["itsdangerous (>=2.0)"] yaml = ["pyyaml (>=5.4)"] [[package]] name = "restructuredtext-lint" version = "1.4.0" description = "reStructuredText linter" category = "dev" optional = false python-versions = "*" files = [ {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, ] [package.dependencies] docutils = ">=0.11,<1.0" [[package]] name = "rich" version = "13.3.5" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "dev" optional = false python-versions = ">=3.7.0" files = [ {file = "rich-13.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"}, {file = "rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"}, ] [package.dependencies] markdown-it-py = ">=2.2.0,<3.0.0" pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruamel-yaml" version = "0.17.21" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" category = "dev" optional = false python-versions = ">=3" files = [ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} [package.extras] docs = ["ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_12_0_arm64.whl", hash = "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win32.whl", hash = "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e"}, {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93"}, {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win32.whl", hash = "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac"}, {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win32.whl", hash = "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8"}, {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5"}, {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] [[package]] name = "safety" version = "2.3.5" description = "Checks installed dependencies for known vulnerabilities and licenses." category = "dev" optional = false python-versions = "*" files = [ {file = "safety-2.3.5-py3-none-any.whl", hash = "sha256:2227fcac1b22b53c1615af78872b48348661691450aa25d6704a5504dbd1f7e2"}, {file = "safety-2.3.5.tar.gz", hash = "sha256:a60c11f8952f412cbb165d70cb1f673a3b43a2ba9a93ce11f97e6a4de834aa3a"}, ] [package.dependencies] Click = ">=8.0.2" dparse = ">=0.6.2" packaging = ">=21.0,<22.0" requests = "*" "ruamel.yaml" = ">=0.17.21" setuptools = ">=19.3" [package.extras] github = ["jinja2 (>=3.1.0)", "pygithub (>=1.43.3)"] gitlab = ["python-gitlab (>=1.3.0)"] [[package]] name = "setuptools" version = "67.7.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "dev" optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" category = "dev" optional = false python-versions = "*" files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] [[package]] name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, ] [package.extras] dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] [[package]] name = "stevedore" version = "5.0.0" description = "Manage dynamic plugins for Python applications" category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "stevedore-5.0.0-py3-none-any.whl", hash = "sha256:bd5a71ff5e5e5f5ea983880e4a1dd1bb47f8feebbb3d95b592398e2f02194771"}, {file = "stevedore-5.0.0.tar.gz", hash = "sha256:2c428d2338976279e8eb2196f7a94910960d9f7ba2f41f3988511e95ca447021"}, ] [package.dependencies] pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "strenum" version = "0.4.10" description = "An Enum that inherits from str." category = "dev" optional = false python-versions = "*" files = [ {file = "StrEnum-0.4.10-py3-none-any.whl", hash = "sha256:aebf04bba8e5af435937c452d69a86798b6f8d5ca5f20ba18561dbfad571ccdd"}, {file = "StrEnum-0.4.10.tar.gz", hash = "sha256:898cc0ebb5054ee07400341ac1d75fdfee489d76d6df3fbc1c2eaf95971e3916"}, ] [package.extras] docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"] release = ["twine"] test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] [[package]] name = "text-unidecode" version = "1.3" description = "The most basic Text::Unidecode port" category = "dev" optional = false python-versions = "*" files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tomlkit" version = "0.11.8" description = "Style preserving TOML library" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] [[package]] name = "typing-extensions" version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" category = "dev" optional = false python-versions = ">=2" files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] [[package]] name = "url-normalize" version = "1.4.3" description = "URL normalization for Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, ] [package.dependencies] six = "*" [[package]] name = "urllib3" version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "wemake-python-styleguide" version = "0.17.0" description = "The strictest and most opinionated python linter ever" category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ {file = "wemake-python-styleguide-0.17.0.tar.gz", hash = "sha256:c8869fac392019c2bb3eae4287399245d10d2726b23f1b3c68d1564909c3a71a"}, {file = "wemake_python_styleguide-0.17.0-py3-none-any.whl", hash = "sha256:d10b953bbe4fba83a34f4c224a0e1849ede89e486eacfc760690e6c87a28eaae"}, ] [package.dependencies] astor = ">=0.8,<0.9" attrs = "*" darglint = ">=1.2,<2.0" flake8 = ">=3.7,<5" flake8-bandit = ">=2.1,<4" flake8-broken-line = ">=0.5,<0.6" flake8-bugbear = ">=22.9,<23.0" flake8-commas = ">=2.0,<3.0" flake8-comprehensions = ">=3.1,<4.0" flake8-debugger = ">=4.0,<5.0" flake8-docstrings = ">=1.3,<2.0" flake8-eradicate = ">=1.0,<2.0" flake8-isort = ">=4.0,<5.0" flake8-quotes = ">=3.0,<4.0" flake8-rst-docstrings = ">=0.2,<0.3" flake8-string-format = ">=0.3,<0.4" pep8-naming = ">=0.13,<0.14" pygments = ">=2.4,<3.0" typing_extensions = ">=4.0,<5.0" [[package]] name = "win32-setctime" version = "1.1.0" description = "A small Python utility to set file creation time on Windows" category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] [package.extras] dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" python-versions = "^3.8" content-hash = "1c52ef35ac361a69ca5c609cd77ddec3ecee861ad143c28af2b685838ae68edd" python-django-test-migrations-1.3.0/pyproject.toml000066400000000000000000000030311454762243100223440ustar00rootroot00000000000000[tool.poetry] name = "django-test-migrations" version = "1.3.0" description = "Test django schema and data migrations, including ordering" license = "MIT" authors = [ "sobolevn " ] readme = "README.md" repository = "https://github.com/wemake-services/django-test-migrations" keywords = [ "django", "django-tests", "django-migrations", "django-orm", "migrations", "orm", "sql", "tests", "test", "pytest", "pytest-plugin" ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Framework :: Django", "Framework :: Django :: 3.2", "Framework :: Django :: 4.0", "Framework :: Django :: 4.1", ] [tool.poetry.plugins.pytest11] django_test_migrations = "django_test_migrations.contrib.pytest_plugin" [tool.poetry.dependencies] python = "^3.8" typing_extensions = ">=3.6,<5" [tool.poetry.dev-dependencies] django = "^4.2" mypy = "^1.2" wemake-python-styleguide = "^0.17" flake8-pytest-style = "^1.7" nitpick = "^0.33" safety = "^2.3" pytest = "^7.3" pytest-cov = "^4.0" pytest-randomly = "^3.12" pytest-django = "^4.5" pytest-mock = "^3.10" [build-system] requires = ["poetry-core>=1.5.0"] build-backend = "poetry.core.masonry.api" [tool.nitpick] style = "https://raw.githubusercontent.com/wemake-services/wemake-python-styleguide/master/styles/nitpick-style-wemake.toml" python-django-test-migrations-1.3.0/setup.cfg000066400000000000000000000054441454762243100212630ustar00rootroot00000000000000# All configuration for plugins and other utils is defined here. # Read more about `setup.cfg`: # https://docs.python.org/3/distutils/configfile.html [flake8] format = wemake show-source = True doctests = False statistics = False # darglint configuration: # https://github.com/terrencepreilly/darglint strictness = long docstring-style = numpy # Plugins: max-complexity = 6 max-line-length = 80 exclude = # Trash and cache: .git __pycache__ .venv .eggs *.egg temp ignore = D100, D104, D401, W504, X100, N818, RST303, RST304, DAR103, DAR203 per-file-ignores = django_test_migrations/db/backends/registry.py: W0611, F401, WPS433 django_test_migrations/db/backends/__init__.py: F401, WPS412 django_test_app/main_app/migrations/*.py: N806, WPS102, WPS114 django_test_app/django_test_app/settings.py: S105, WPS226, WPS407 tests/test_*.py: N806, S101, S404, S603, S607, WPS118, WPS226, WPS432, WPS442 [isort] # isort configuration: # https://pycqa.github.io/isort/docs/configuration/options.html profile = wemake # Useful for our test app: known_first_party = main_app [tool:pytest] # Django options: # https://pytest-django.readthedocs.io/en/latest/ DJANGO_SETTINGS_MODULE = django_test_app.settings # PYTHONPATH configuration: pythonpath = django_test_app # py.test options: norecursedirs = *.egg .eggs dist build docs .tox .git __pycache__ # Strict `@xfail` by default: xfail_strict = true # You will need to measure your tests speed with `-n auto` and without it, # so you can see whether it gives you any performance gain, or just gives # you an overhead. See `docs/template/development-process.rst`. addopts = --strict-markers --strict-config --doctest-modules --cov=django_test_migrations --cov-report=term-missing:skip-covered --cov-report=html --cov-report=xml --cov-branch --cov-fail-under=100 [coverage:run] # Why do we exclude this file from coverage? # Because coverage is not calculated correctly for pytest plugins. # And we completely test it anyway. omit = django_test_migrations/constants.py django_test_migrations/contrib/pytest_plugin.py django_test_migrations/types.py [coverage:report] skip_covered = True show_missing = True sort = Cover exclude_lines = pragma: no cover # type hinting related code if TYPE_CHECKING: [mypy] # mypy configurations: http://bit.ly/2zEl9WI allow_redefinition = False check_untyped_defs = True disallow_any_explicit = True disallow_any_generics = True disallow_untyped_calls = True ignore_errors = False ignore_missing_imports = True implicit_reexport = False strict_optional = True strict_equality = True local_partial_types = True no_implicit_optional = True warn_no_return = True warn_unused_ignores = True warn_redundant_casts = True warn_unused_configs = True warn_unreachable = True python-django-test-migrations-1.3.0/tests/000077500000000000000000000000001454762243100205755ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/conftest.py000066400000000000000000000006401454762243100227740ustar00rootroot00000000000000import pytest @pytest.fixture(scope='session') def django_db_use_migrations( request, django_db_use_migrations, # noqa: WPS442 ): """ Helper fixture to skip tests when ``--nomigrations`` were specified. Copied from https://github.com/pytest-dev/pytest-django """ if not django_db_use_migrations: pytest.skip('--nomigrations was specified') return django_db_use_migrations python-django-test-migrations-1.3.0/tests/test_checks/000077500000000000000000000000001454762243100230745ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_checks/test_autonames.py000066400000000000000000000027621454762243100265100ustar00rootroot00000000000000import pytest from django.core.checks import WARNING from django_test_migrations.checks.autonames import ( CHECK_NAME, check_migration_names, ) @pytest.mark.django_db() def test_autonames(): """Here we check that bad migrations do produce warnings.""" warnings = check_migration_names() warnings_msgs = {warning.msg for warning in warnings} assert len(warnings) == 2 assert [warnings[0].level, warnings[1].level] == [WARNING, WARNING] assert all( [ warnings[0].id.startswith(CHECK_NAME), warnings[1].id.startswith(CHECK_NAME), ], ) assert warnings_msgs == { 'Migration main_app.0004_auto_20191119_2125 has an automatic name.', 'Migration main_app.0005_auto_20200329_1118 has an automatic name.', } @pytest.mark.django_db() def test_autonames_with_ignore(settings): """Here we check that some migrations can be ignored.""" # patch settings to ignore two bad migrations settings.DTM_IGNORED_MIGRATIONS = { ('main_app', '0004_auto_20191119_2125'), ('main_app', '0005_auto_20200329_1118'), } warnings = check_migration_names() assert not warnings @pytest.mark.django_db() def test_autonames_with_ignore_all_app_migrations(settings): """Here we check that all migrations ignored inside app.""" # patch settings to ignore all migrations in the app settings.DTM_IGNORED_MIGRATIONS = {('main_app', '*')} warnings = check_migration_names() assert not warnings python-django-test-migrations-1.3.0/tests/test_contrib/000077500000000000000000000000001454762243100232745ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_django_checks/000077500000000000000000000000001454762243100271155ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_django_checks/test_autonames_check.py000066400000000000000000000006501454762243100336600ustar00rootroot00000000000000import subprocess def test_managepy_check(): """Checks that checks do fail.""" proc = subprocess.Popen( [ 'python', 'django_test_app/manage.py', 'check', '--fail-level', 'WARNING', ], stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8', ) proc.communicate() assert proc.returncode == 1 python-django-test-migrations-1.3.0/tests/test_contrib/test_pytest_plugin/000077500000000000000000000000001454762243100272415ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_pytest_plugin/__init__.py000066400000000000000000000000001454762243100313400ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_pytest_plugin/test_plugin.py000066400000000000000000000033521454762243100321530ustar00rootroot00000000000000import re import subprocess from django_test_migrations.constants import MIGRATION_TEST_MARKER def test_call_pytest_setup_plan(): """Checks that module is registered and visible in the meta data.""" output_text = subprocess.check_output( [ 'pytest', '--setup-plan', # We need this part because otherwise check fails with `1` code: '--cov-fail-under', '0', ], stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8', ) assert 'migrator' in output_text assert 'migrator_factory' in output_text def test_pytest_registers_marker(): """Ensure ``MIGRATION_TEST_MARKER`` marker is registered.""" output_text = subprocess.check_output( ['pytest', '--markers'], stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8', ) assert MIGRATION_TEST_MARKER in output_text def test_pytest_markers(): """Ensure ``MIGRATION_TEST_MARKER`` markers are properly added.""" output_text = subprocess.check_output( [ 'pytest', '--collect-only', # Collect only tests marked with ``MIGRATION_TEST_MARKER`` marker '-m', MIGRATION_TEST_MARKER, # We need this part because otherwise check fails with `1` code: '--cov-fail-under', '0', ], stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8', ) search_result = re.search( r'(?P\d+)\s+selected', output_text, ) assert search_result assert int(search_result.group('selected_number') or 0) > 0 assert 'test_pytest_plugin' in output_text test_pytest_plugin_direct.py000066400000000000000000000054711454762243100350420ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_pytest_plugin""" This module covers simple direct migrations. We test both schema and data-migrations here. """ import pytest from django.core.exceptions import FieldError from django.db.utils import IntegrityError @pytest.mark.django_db() def test_pytest_plugin_initial(migrator): """Ensures that the initial migration works.""" old_state = migrator.apply_initial_migration(('main_app', None)) with pytest.raises(LookupError): # Models does not yet exist: old_state.apps.get_model('main_app', 'SomeItem') new_state = migrator.apply_tested_migration(('main_app', '0001_initial')) # After the initial migration is done, we can use the model state: SomeItem = new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.filter(string_field='').count() == 0 @pytest.mark.django_db() def test_pytest_plugin0001(migrator): """Ensures that the first migration works.""" old_state = migrator.apply_initial_migration(('main_app', '0001_initial')) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') with pytest.raises(FieldError): SomeItem.objects.filter(is_clean=True) new_state = migrator.apply_tested_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.filter(is_clean=True).count() == 0 @pytest.mark.django_db() def test_pytest_plugin0002(migrator): """Ensures that the second migration works.""" old_state = migrator.apply_initial_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') SomeItem.objects.create(string_field='a') SomeItem.objects.create(string_field='a b') assert SomeItem.objects.count() == 2 assert SomeItem.objects.filter(is_clean=True).count() == 2 new_state = migrator.apply_tested_migration( ('main_app', '0003_update_is_clean'), ) SomeItem = new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.count() == 2 assert SomeItem.objects.filter(is_clean=True).count() == 1 @pytest.mark.django_db() def test_pytest_plugin0003(migrator): """Ensures that the third migration works.""" old_state = migrator.apply_initial_migration( ('main_app', '0003_update_is_clean'), ) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') SomeItem.objects.create(string_field='a') # default is still there assert SomeItem.objects.count() == 1 assert SomeItem.objects.filter(is_clean=True).count() == 1 new_state = migrator.apply_tested_migration( ('main_app', '0004_auto_20191119_2125'), ) SomeItem = new_state.apps.get_model('main_app', 'SomeItem') with pytest.raises(IntegrityError): SomeItem.objects.create(string_field='b') # no default anymore test_pytest_plugin_reverse.py000066400000000000000000000030471454762243100352400ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_pytest_plugin""" This module covers tests for migration rollbacks. It might be useful when something goes wrong and you need to switch back to the previous state. """ import pytest from django.core.exceptions import FieldError @pytest.mark.django_db() def test_pytest_plugin0001(migrator): """Ensures that the first migration works.""" old_state = migrator.apply_initial_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.filter(is_clean=True).count() == 0 new_state = migrator.apply_tested_migration(('main_app', '0001_initial')) SomeItem = new_state.apps.get_model('main_app', 'SomeItem') with pytest.raises(FieldError): SomeItem.objects.filter(is_clean=True) @pytest.mark.django_db() def test_pytest_plugin0002(migrator): """Ensures that the second migration works.""" old_state = migrator.apply_initial_migration( ('main_app', '0003_update_is_clean'), ) SomeItem = old_state.apps.get_model('main_app', 'SomeItem') SomeItem.objects.create(string_field='a', is_clean=True) SomeItem.objects.create(string_field='a b', is_clean=False) assert SomeItem.objects.count() == 2 assert SomeItem.objects.filter(is_clean=True).count() == 1 new_state = migrator.apply_tested_migration( ('main_app', '0002_someitem_is_clean'), ) SomeItem = new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.count() == 2 assert SomeItem.objects.filter(is_clean=True).count() == 1 python-django-test-migrations-1.3.0/tests/test_contrib/test_pytest_plugin/test_signals.py000066400000000000000000000072311454762243100323150ustar00rootroot00000000000000import django import pytest from django.apps import apps from django.core.management import call_command from django.db import DEFAULT_DB_ALIAS from django.db.models.signals import post_migrate, pre_migrate from typing_extensions import Final from django_test_migrations.signals import mute_migrate_signals # value for ``dispatch_uid`` is needed to disconnect signal receiver # registered for testing purposes to which we do not have any reference # outside of test function DISPATCH_UID: Final = 'test_migrate_signals' # Dummy signal receiver function def _my_callback(sender, **kwargs): """Mock that does nothing.""" @pytest.fixture() def _disconnect_receivers(): """Disconnect testing receiver of ``pre_migrate`` or ``post_migrate``.""" yield main_app_config = apps.get_app_config('main_app') pre_migrate.disconnect(sender=main_app_config, dispatch_uid=DISPATCH_UID) post_migrate.disconnect(sender=main_app_config, dispatch_uid=DISPATCH_UID) @pytest.mark.parametrize('signal', [pre_migrate, post_migrate]) def test_migrate_signal_muted(signal): """Ensure the context manager does indeed silences the signals.""" signal.connect(_my_callback) assert signal.receivers with mute_migrate_signals(): assert not signal.receivers assert signal.receivers signal.disconnect(_my_callback) @pytest.mark.skipif( django.VERSION >= (4, 0), reason='requires `Django<4.0`', ) @pytest.mark.parametrize('signal', [pre_migrate, post_migrate]) @pytest.mark.usefixtures('migrator', '_disconnect_receivers') def test_signal_receiver_registered_in_test(mocker, signal): """Ensure migration signal receivers registered in tests are called.""" signal_receiver_mock = mocker.MagicMock() main_app_config = apps.get_app_config('main_app') signal.connect( signal_receiver_mock, sender=main_app_config, dispatch_uid=DISPATCH_UID, ) verbosity = 0 interactive = False # call `migrate` management command to trigger ``pre_migrate`` and # ``post_migrate`` signals call_command('migrate', verbosity=verbosity, interactive=interactive) signal_receiver_mock.assert_called_once_with( sender=main_app_config, app_config=main_app_config, apps=mocker.ANY, # we don't have any reference to this object using=DEFAULT_DB_ALIAS, verbosity=verbosity, interactive=interactive, plan=mocker.ANY, # not important for this test signal=signal, ) @pytest.mark.skipif( django.VERSION < (4, 0), reason='requires `Django>=4.0`', ) @pytest.mark.parametrize('signal', [pre_migrate, post_migrate]) @pytest.mark.usefixtures('migrator', '_disconnect_receivers') def test_signal_receiver_registered_in_test_django40(mocker, signal): """Ensure migration signal receivers registered in tests are called.""" signal_receiver_mock = mocker.MagicMock() main_app_config = apps.get_app_config('main_app') signal.connect( signal_receiver_mock, sender=main_app_config, dispatch_uid=DISPATCH_UID, ) verbosity = 0 interactive = False # call `migrate` management command to trigger ``pre_migrate`` and # ``post_migrate`` signals call_command('migrate', verbosity=verbosity, interactive=interactive) signal_receiver_mock.assert_called_once_with( sender=main_app_config, app_config=main_app_config, apps=mocker.ANY, # we don't have any reference to this object using=DEFAULT_DB_ALIAS, verbosity=verbosity, interactive=interactive, signal=signal, # following kwargs are not important for this test stdout=mocker.ANY, plan=mocker.ANY, ) python-django-test-migrations-1.3.0/tests/test_contrib/test_unittest_case/000077500000000000000000000000001454762243100272055ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_unittest_case/__init__.py000066400000000000000000000000001454762243100313040ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_contrib/test_unittest_case/test_signals.py000066400000000000000000000110641454762243100322600ustar00rootroot00000000000000from unittest import mock import django import pytest from django.apps import apps from django.core.management import call_command from django.db import DEFAULT_DB_ALIAS from django.db.models.signals import post_migrate, pre_migrate from django_test_migrations.contrib.unittest_case import MigratorTestCase class TestSignalMuting(MigratorTestCase): """Test that the `post_migrate` signal has been muted.""" migrate_from = ('main_app', '0002_someitem_is_clean') migrate_to = ('main_app', '0001_initial') def test_pre_migrate_muted(self): """Ensure ``pre_migrate`` signal has been muted.""" assert not pre_migrate.receivers def test_post_migrate_muted(self): """Ensure ``post_migrate`` signal has been muted.""" assert not post_migrate.receivers class TestSignalConnectInTest(MigratorTestCase): """Ensure test ``pre_migrate`` or ``post_migrate`` receiver are called. Ensure that ``pre_migrate`` or ``post_migrate`` signals receivers connected directly in tests are called. """ migrate_from = ('main_app', '0001_initial') migrate_to = ('main_app', '0002_someitem_is_clean') def tearDown(self): """Disconnect ``pre_migrate`` and ``post_migrate`` testing receivers.""" pre_migrate.disconnect( self.pre_migrate_receiver_mock, sender=self.main_app_config, ) post_migrate.disconnect( self.post_migrate_receiver_mock, sender=self.main_app_config, ) def prepare(self): """Connect testing ``pre_migrate`` and ``post_migrate`` receivers.""" self.pre_migrate_receiver_mock = mock.MagicMock() self.post_migrate_receiver_mock = mock.MagicMock() # ``old_apps`` is not real ``ProjectState`` instance, so we cannot use # it to get "original" main_app ``AppConfig`` instance needed to # connect signal receiver, that's the reason we are using # ``apps`` imported directly from ``django.apps`` self.main_app_config = apps.get_app_config('main_app') pre_migrate.connect( self.pre_migrate_receiver_mock, sender=self.main_app_config, ) post_migrate.connect( self.post_migrate_receiver_mock, sender=self.main_app_config, ) @pytest.mark.skipif( django.VERSION >= (4, 0), reason='requires `Django<4.0`', ) def test_signal_receivers_added_in_tests(self): """Ensure migration signals receivers connected in tests are called.""" verbosity = 0 interactive = False # call `migrate` management command to trigger ``pre_migrate`` and # ``post_migrate`` signals call_command('migrate', verbosity=verbosity, interactive=interactive) common_kwargs = { 'sender': self.main_app_config, 'app_config': self.main_app_config, 'apps': mock.ANY, # we don't have any reference to this object 'using': DEFAULT_DB_ALIAS, 'verbosity': verbosity, 'interactive': interactive, 'plan': mock.ANY, # not important for this test } self.pre_migrate_receiver_mock.assert_called_once_with( **common_kwargs, signal=pre_migrate, ) self.post_migrate_receiver_mock.assert_called_once_with( **common_kwargs, signal=post_migrate, ) @pytest.mark.skipif( django.VERSION < (4, 0), reason='requires `Django>=4.0`', ) def test_signal_receivers_added_in_tests_django40(self): """Ensure migration signals receivers connected in tests are called.""" verbosity = 0 interactive = False # call `migrate` management command to trigger ``pre_migrate`` and # ``post_migrate`` signals call_command('migrate', verbosity=verbosity, interactive=interactive) common_kwargs = { 'sender': self.main_app_config, 'app_config': self.main_app_config, 'apps': mock.ANY, # we don't have any reference to this object 'using': DEFAULT_DB_ALIAS, 'verbosity': verbosity, 'interactive': interactive, # following kwargs are not important for this test 'stdout': mock.ANY, 'plan': mock.ANY, } self.pre_migrate_receiver_mock.assert_called_once_with( **common_kwargs, signal=pre_migrate, ) self.post_migrate_receiver_mock.assert_called_once_with( **common_kwargs, signal=post_migrate, ) python-django-test-migrations-1.3.0/tests/test_contrib/test_unittest_case/test_unittest_case.py000066400000000000000000000033441454762243100334740ustar00rootroot00000000000000from django_test_migrations.constants import MIGRATION_TEST_MARKER from django_test_migrations.contrib.unittest_case import MigratorTestCase class TestDirectMigration(MigratorTestCase): """This class is used to test direct migrations.""" migrate_from = ('main_app', '0002_someitem_is_clean') migrate_to = ('main_app', '0003_update_is_clean') def prepare(self): """Prepare some data before the migration.""" SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem') SomeItem.objects.create(string_field='a') SomeItem.objects.create(string_field='a b') def test_migration_main0003(self): """Run the test itself.""" SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.count() == 2 assert SomeItem.objects.filter(is_clean=True).count() == 1 class TestBackwardMigration(MigratorTestCase): """This class is used to test backward migrations.""" migrate_from = ('main_app', '0002_someitem_is_clean') migrate_to = ('main_app', '0001_initial') def prepare(self): """Prepare some data before the migration.""" SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem') SomeItem.objects.create(string_field='a') SomeItem.objects.create(string_field='a b') def test_migration_main0001(self): """Run the test itself.""" SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem') assert SomeItem.objects.count() == 2 def test_migration_test_marker_tag(): """Ensure ``MigratorTestCase`` subclasses are properly tagged.""" assert MIGRATION_TEST_MARKER in TestDirectMigration.tags assert MIGRATION_TEST_MARKER in TestBackwardMigration.tags python-django-test-migrations-1.3.0/tests/test_db/000077500000000000000000000000001454762243100222215ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_backends/000077500000000000000000000000001454762243100250325ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_exceptions.py000066400000000000000000000012201454762243100306170ustar00rootroot00000000000000from django_test_migrations.db.backends import exceptions def test_database_configuration_not_found(): """Ensure exception returns proper string representation.""" vendor = 'ms_sql' exception = exceptions.DatabaseConfigurationNotFound(vendor) assert vendor in str(exception) def test_database_configuration_setting_not_found(): """Ensure exception returns proper string representation.""" vendor = 'ms_sql' setting_name = 'fake_setting' exception = exceptions.DatabaseConfigurationSettingNotFound( vendor, setting_name, ) assert vendor in str(exception) assert setting_name in str(exception) python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_mysql/000077500000000000000000000000001454762243100272365ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_mysql/__init__.py000066400000000000000000000000001454762243100313350ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_mysql/test_configuration.py000066400000000000000000000034031454762243100335160ustar00rootroot00000000000000import pytest from django_test_migrations.db.backends import mysql from django_test_migrations.db.backends.exceptions import ( DatabaseConfigurationSettingNotFound, ) def test_get_setting_value(mocker): """Ensure expected SQL query is executed.""" setting_name = 'MAX_EXECUTION_TIME' connection_mock = mocker.MagicMock() connection_mock.ops.quote_name = lambda name: name database_configuration = mysql.configuration.DatabaseConfiguration( connection_mock, ) database_configuration.get_setting_value(setting_name) cursor_mock = connection_mock.cursor().__enter__() # noqa: WPS609 cursor_mock.execute.assert_called_once_with( 'SELECT @@{0};'.format(setting_name), ) def test_get_existing_setting_value(mocker): """Ensure setting value is returned for existing setting.""" expected_setting_value = 74747 connection_mock = mocker.MagicMock() cursor_mock = connection_mock.cursor().__enter__() # noqa: WPS609 cursor_mock.fetchone.return_value = (expected_setting_value,) database_configuration = mysql.configuration.DatabaseConfiguration( connection_mock, ) setting_value = database_configuration.get_setting_value('testing_setting') assert setting_value == expected_setting_value def test_get_not_existing_setting_value(mocker): """Ensure exception is raised when setting does not exist.""" connection_mock = mocker.MagicMock() cursor_mock = connection_mock.cursor().__enter__() # noqa: WPS609 cursor_mock.fetchone.return_value = None database_configuration = mysql.configuration.DatabaseConfiguration( connection_mock, ) with pytest.raises(DatabaseConfigurationSettingNotFound): database_configuration.get_setting_value('testing_setting') python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_postgresql/000077500000000000000000000000001454762243100302745ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_postgresql/__init__.py000066400000000000000000000000001454762243100323730ustar00rootroot00000000000000test_configuration.py000066400000000000000000000035151454762243100345010ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_postgresqlimport pytest from django_test_migrations.db.backends import postgresql from django_test_migrations.db.backends.exceptions import ( DatabaseConfigurationSettingNotFound, ) def test_get_setting_value(mocker): """Ensure expected SQL query is executed.""" setting_name = 'statement_timeout' connection_mock = mocker.MagicMock() connection_mock.ops.quote_name = lambda name: name database_configuration = postgresql.configuration.DatabaseConfiguration( connection_mock, ) database_configuration.get_setting_value(setting_name) cursor_mock = connection_mock.cursor().__enter__() # noqa: WPS609 cursor_mock.execute.assert_called_once_with( 'SELECT setting FROM pg_settings WHERE name = %s;', # noqa: WPS323 (setting_name,), ) def test_get_existing_setting_value(mocker): """Ensure setting value is returned for existing setting.""" expected_setting_value = 74747 connection_mock = mocker.MagicMock() cursor_mock = connection_mock.cursor().__enter__() # noqa: WPS609 cursor_mock.fetchone.return_value = (expected_setting_value,) database_configuration = postgresql.configuration.DatabaseConfiguration( connection_mock, ) setting_value = database_configuration.get_setting_value('testing_setting') assert setting_value == expected_setting_value def test_get_not_existing_setting_value(mocker): """Ensure exception is raised when setting does not exist.""" connection_mock = mocker.MagicMock() cursor_mock = connection_mock.cursor().__enter__() # noqa: WPS609 cursor_mock.fetchone.return_value = None database_configuration = postgresql.configuration.DatabaseConfiguration( connection_mock, ) with pytest.raises(DatabaseConfigurationSettingNotFound): database_configuration.get_setting_value('testing_setting') python-django-test-migrations-1.3.0/tests/test_db/test_backends/test_registry.py000066400000000000000000000037221454762243100303170ustar00rootroot00000000000000import pytest from django_test_migrations.db.backends import mysql, postgresql, registry from django_test_migrations.db.backends.base.configuration import ( BaseDatabaseConfiguration, ) from django_test_migrations.db.backends.exceptions import ( DatabaseConfigurationNotFound, ) def test_all_db_backends_registered(): """Ensures all database backends all registered.""" registered_vendors = list(registry.database_configuration_registry.keys()) assert sorted(registered_vendors) == ['mysql', 'postgresql'] def test_abc_subclasses_are_not_registered(): """Test registration of ``BaseDatabaseConfiguration`` abstract subclasses. Ensures ``BaseDatabaseConfiguration`` abstract subclasses are not registered. """ vendor = 'abstract_subclass' # creates abstract subclass type('DatabaseConfiguration', (BaseDatabaseConfiguration,), { 'vendor': vendor, }) assert vendor not in registry.database_configuration_registry @pytest.mark.parametrize(('vendor', 'database_configuration_class'), [ ('postgresql', postgresql.configuration.DatabaseConfiguration), ('mysql', mysql.configuration.DatabaseConfiguration), ]) def test_get_database_configuration_vendor_registered( mocker, vendor, database_configuration_class, ): """Ensures database configuration is returned when vendor registered.""" connection_mock = mocker.Mock() connection_mock.vendor = vendor database_configuration = registry.get_database_configuration( connection_mock, ) assert isinstance(database_configuration, database_configuration_class) def test_get_database_configuration_vendor_not_registered(mocker): """Ensures proper exception is raised when vendor not registered.""" vendor = 'not_registered_vendor' connection_mock = mocker.Mock() connection_mock.vendor = vendor with pytest.raises(DatabaseConfigurationNotFound, match=vendor): registry.get_database_configuration(connection_mock) python-django-test-migrations-1.3.0/tests/test_db/test_checks/000077500000000000000000000000001454762243100245205ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_db/test_checks/test_statement_timeout_check.py000066400000000000000000000072711454762243100330470ustar00rootroot00000000000000import datetime import pytest from django_test_migrations.db.checks.statement_timeout import ( CHECK_NAME, check_statement_timeout_setting, ) from django_test_migrations.logic.datetime import timedelta_to_milliseconds ALL_CONNECTIONS_MOCK_PATH = ( 'django_test_migrations.db.checks.statement_timeout.connections.all' ) @pytest.fixture() def connection_mock_factory(mocker): """Factory of DB connection mocks.""" def factory(vendor, fetch_one_result=None): connection_mock = mocker.MagicMock(vendor=vendor) cursor_mock = connection_mock.cursor.return_value cursor_mock = cursor_mock.__enter__.return_value # noqa: WPS609 cursor_mock.fetchone.return_value = fetch_one_result return connection_mock return factory @pytest.mark.parametrize('vendor', ['postgresql', 'mysql']) def test_correct_statement_timeout(mocker, connection_mock_factory, vendor): """Ensure empty list returned when ``statement_timeout`` value correct.""" connection_mock = connection_mock_factory(vendor, (20000,)) mocker.patch(ALL_CONNECTIONS_MOCK_PATH, return_value=[connection_mock]) assert not check_statement_timeout_setting() @pytest.mark.parametrize('vendor', ['postgresql', 'mysql']) def test_statement_timeout_not_set(mocker, connection_mock_factory, vendor): """Ensure W001 is returned in list when ``statement_timeout`` not set.""" connection_mock = connection_mock_factory(vendor, (0,)) mocker.patch(ALL_CONNECTIONS_MOCK_PATH, return_value=[connection_mock]) check_messages = check_statement_timeout_setting() assert len(check_messages) == 1 assert check_messages[0].id.endswith('W001') @pytest.mark.parametrize('vendor', ['postgresql', 'mysql']) def test_statement_timeout_too_high(mocker, connection_mock_factory, vendor): """Ensure W002 is returned in list when ``statement_timeout`` too high.""" connection_mock = connection_mock_factory( vendor, (timedelta_to_milliseconds(datetime.timedelta(hours=2)),), ) mocker.patch(ALL_CONNECTIONS_MOCK_PATH, return_value=[connection_mock]) check_messages = check_statement_timeout_setting() assert len(check_messages) == 1 assert check_messages[0].id.endswith('W002') def test_unsupported_vendors(mocker): """Ensure empty list returned when no connections vendors supported.""" vendors = ['sqlite3', 'custom'] connection_mocks = [mocker.MagicMock(vendor=vendor) for vendor in vendors] mocker.patch(ALL_CONNECTIONS_MOCK_PATH, return_value=connection_mocks) assert not check_statement_timeout_setting() @pytest.mark.parametrize('vendor', ['postgresql', 'mysql']) def test_statement_timeout_setting_not_found( mocker, connection_mock_factory, vendor, ): """Ensure empty list returned when ``statement_timeout`` not found.""" connection_mock = connection_mock_factory(vendor, None) mocker.patch(ALL_CONNECTIONS_MOCK_PATH, return_value=[connection_mock]) assert not check_statement_timeout_setting() def test_multiple_connections(mocker, connection_mock_factory): """Ensure list with many items returned when many connections present.""" connections_mocks = [ connection_mock_factory('sqlite', None), connection_mock_factory('postgresql', (0,)), connection_mock_factory( 'mysql', (timedelta_to_milliseconds(datetime.timedelta(hours=2)),), ), ] mocker.patch(ALL_CONNECTIONS_MOCK_PATH, return_value=connections_mocks) check_messages = check_statement_timeout_setting() expected_messages_ids = [ '{0}.W001'.format(CHECK_NAME), '{0}.W002'.format(CHECK_NAME), ] assert expected_messages_ids == [message.id for message in check_messages] python-django-test-migrations-1.3.0/tests/test_exceptions/000077500000000000000000000000001454762243100240155ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_exceptions/test_migration_not_found.py000066400000000000000000000007731454762243100315010ustar00rootroot00000000000000import pytest from django_test_migrations import exceptions @pytest.mark.parametrize(('target', 'expected_str'), [ (('app', None), "Migration ('app', None) not found in migrations plan"), ( ('app', '0047_magic'), "Migration ('app', '0047_magic') not found in migrations plan", ), ]) def test_representation(target, expected_str): """Ensure ``MigrationNotInPlan`` has expected string representation.""" assert str(exceptions.MigrationNotInPlan(target)) == expected_str python-django-test-migrations-1.3.0/tests/test_logic/000077500000000000000000000000001454762243100227315ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_logic/test_datetime.py000066400000000000000000000012611454762243100261360ustar00rootroot00000000000000import datetime import pytest from django_test_migrations.logic.datetime import timedelta_to_milliseconds @pytest.mark.parametrize(('timedelta', 'expected_result'), [ (datetime.timedelta(seconds=1), 1000), (datetime.timedelta(minutes=3), 3 * 60 * 1000), (datetime.timedelta(hours=2.6), 2.6 * 60 * 60 * 1000), ( datetime.timedelta(days=4), 4 * 24 * 60 * 60 * 1000, ), ( datetime.timedelta(minutes=7.4, seconds=47), 7.4 * 60 * 1000 + 47 * 1000, ), ]) def test_timedelta_to_milliseconds(timedelta, expected_result): """Ensure expected value is returned.""" assert timedelta_to_milliseconds(timedelta) == expected_result python-django-test-migrations-1.3.0/tests/test_logic/test_migrations.py000066400000000000000000000007141454762243100265200ustar00rootroot00000000000000from django_test_migrations.logic.migrations import normalize def test_normalize_raw_target(): """Ensure normalize works for ``MigrationTarget``.""" assert normalize(('app', '0074_magic')) == [('app', '0074_magic')] def test_normalize_list_of_targets(): """Ensure normalize works for list of ``MigrationTarget``.""" migration_targets = [('app1', None), ('app2', '0001_initial')] assert normalize(migration_targets) == migration_targets python-django-test-migrations-1.3.0/tests/test_migrator/000077500000000000000000000000001454762243100234605ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_migrator/test_migrator.py000066400000000000000000000016771454762243100267300ustar00rootroot00000000000000import pytest from django.db.migrations.state import ProjectState from django_test_migrations.migrator import Migrator @pytest.mark.django_db() def test_migrator(transactional_db): """We only need this test for coverage.""" migrator = Migrator() old_state = migrator.apply_initial_migration(('main_app', None)) new_state = migrator.apply_tested_migration(('main_app', '0001_initial')) assert isinstance(old_state, ProjectState) assert isinstance(new_state, ProjectState) assert migrator.reset() is None @pytest.mark.django_db() def test_migrator_list(transactional_db): """We only need this test for coverage.""" migrator = Migrator() old_state = migrator.apply_initial_migration([('main_app', None)]) new_state = migrator.apply_tested_migration([('main_app', '0001_initial')]) assert isinstance(old_state, ProjectState) assert isinstance(new_state, ProjectState) assert migrator.reset() is None python-django-test-migrations-1.3.0/tests/test_plan/000077500000000000000000000000001454762243100225665ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_plan/conftest.py000066400000000000000000000011471454762243100247700ustar00rootroot00000000000000import pytest from django.db.migrations import Migration @pytest.fixture() def plan(): """Fake migrations plan for testing purposes.""" migrations_plan = [ Migration('0001_initial', 'app1'), Migration('0002_second', 'app1'), Migration('0001_initial', 'app2'), Migration('0003_third', 'app1'), Migration('0004_fourth', 'app1'), Migration('0002_second', 'app2'), Migration('0005_fifth', 'app1'), Migration('0001_initial', 'app3'), Migration('0006_sixth', 'app1'), ] return [(migration, False) for migration in migrations_plan] python-django-test-migrations-1.3.0/tests/test_plan/test_all_migrations.py000066400000000000000000000021171454762243100272040ustar00rootroot00000000000000import pytest from django_test_migrations.plan import all_migrations, nodes_to_tuples @pytest.mark.django_db() def test_all_migrations_main(): """Testing migrations for a single app only.""" main_migrations = all_migrations('default', ['main_app']) assert nodes_to_tuples(main_migrations) == [ ('main_app', '0001_initial'), ('main_app', '0002_someitem_is_clean'), ('main_app', '0003_update_is_clean'), ('main_app', '0004_auto_20191119_2125'), ('main_app', '0005_auto_20200329_1118'), ] @pytest.mark.django_db() def test_all_migrations_missing(): """Testing migrations for a missing app.""" with pytest.raises(LookupError): all_migrations('default', ['missing_app']) @pytest.mark.django_db() def test_all_migrations_auth(): """Testing migrations for a builtin app.""" auth_migrations = all_migrations('default', ['auth']) assert len(auth_migrations) >= 10 @pytest.mark.django_db() def test_all_migrations_all(): """Testing migrations for all apps.""" assert len(all_migrations()) >= 17 # noqa: WPS432 python-django-test-migrations-1.3.0/tests/test_plan/test_truncate_plan.py000066400000000000000000000024451454762243100270430ustar00rootroot00000000000000import pytest from django_test_migrations.exceptions import MigrationNotInPlan from django_test_migrations.plan import truncate_plan @pytest.mark.parametrize(('targets', 'index'), [ ([], 9), # full plan for empty targets ([('app1', None)], 0), ([('app1', None), ('app3', None)], 7), ([('app2', '0002_second')], 6), ([('app1', '0002_second'), ('app2', None)], 2), ([('app1', '0003_third'), ('app2', None)], 4), ([('app1', '0003_third'), ('app1', '0005_fifth')], 7), ([('app1', '0003_third'), ('app2', None), ('app3', '0001_initial')], 8), ]) def test_truncate_plan(plan, targets, index): """Ensure plan is properly truncated for both types migrations names.""" assert truncate_plan(targets, plan) == plan[:index] def test_empty_plan(): """Ensure function work when plan is empty.""" assert not truncate_plan([('app1', '0001_initial')], []) @pytest.mark.parametrize('targets', [ [('app4', None)], [('app1', '0047_magic')], [('app1', '0005_fifth'), ('app4', None)], [('app1', '0005_fifth'), ('app4', '0047_magic'), ('app3', None)], ]) def test_migration_target_does_not_exist(plan, targets): """Ensure ``MigrationNotInPlan`` is raised when target not in plan.""" with pytest.raises(MigrationNotInPlan): truncate_plan(targets, plan) python-django-test-migrations-1.3.0/tests/test_sql/000077500000000000000000000000001454762243100224335ustar00rootroot00000000000000python-django-test-migrations-1.3.0/tests/test_sql/test_drop_models_table.py000066400000000000000000000034531454762243100275270ustar00rootroot00000000000000from django_test_migrations.sql import drop_models_tables TESTING_DATABASE_NAME = 'test' def test_drop_models_table_no_tables_detected(mocker): """Ensure any ``DROP TABLE`` statement executed when no tables detected.""" testing_connection_mock = mocker.MagicMock() testing_connection_mock.introspection.django_table_names.return_value = [] connections_mock = mocker.patch('django.db.connections._connections') connections_mock.test = testing_connection_mock drop_models_tables(TESTING_DATABASE_NAME) testing_connection_mock.ops.execute_sql_flush.assert_not_called() def test_drop_models_table_table_detected(mocker): """Ensure ``DROP TABLE`` statements are executed when any table detected.""" testing_connection_mock = mocker.MagicMock() testing_connection_mock.introspection.django_table_names.return_value = [ 'foo_bar', 'foo_baz', ] connections_mock = mocker.patch('django.db.connections._connections') connections_mock.test = testing_connection_mock drop_models_tables(TESTING_DATABASE_NAME) testing_connection_mock.ops.execute_sql_flush.assert_called_once() def test_drop_models_table_on_mysql(mocker): """Ensure queries disabling/enabling `FOREIGN_KEY_CHECKS` are executed.""" testing_connection_mock = mocker.MagicMock(vendor='mysql') testing_connection_mock.introspection.django_table_names.return_value = [ 'foo_bar', 'foo_baz', ] connections_mock = mocker.patch('django.db.connections._connections') connections_mock.test = testing_connection_mock drop_models_tables(TESTING_DATABASE_NAME) testing_connection_mock.ops.execute_sql_flush.assert_called_once_with([ 'SET FOREIGN_KEY_CHECKS = 0;', mocker.ANY, mocker.ANY, 'SET FOREIGN_KEY_CHECKS = 1;', ]) python-django-test-migrations-1.3.0/tests/test_sql/test_flush_utils.py000066400000000000000000000017571454762243100264170ustar00rootroot00000000000000import pytest from django.core.management.color import Style from django_test_migrations import sql @pytest.fixture() def testing_connection_mock(mocker): """Mock Django connections to check the methods called.""" testing_connection_mock = mocker.MagicMock() testing_connection_mock.introspection.get_sequences.return_value = [] connections_mock = mocker.patch('django.db.connections._connections') connections_mock.test = testing_connection_mock return testing_connection_mock def test_flush_django_migration_table(mocker, testing_connection_mock): """Ensure expected ``connection`` methods are called.""" style = Style() sql.flush_django_migrations_table('test', style) testing_connection_mock.ops.sql_flush.assert_called_once_with( style, [sql.DJANGO_MIGRATIONS_TABLE_NAME], reset_sequences=True, allow_cascade=False, ) testing_connection_mock.ops.execute_sql_flush.assert_called_once_with( mocker.ANY, )