diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..835e4c8 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,9 @@ +[run] +omit = + */virtualenvs/*, + */migrations/*, + */site-packages/* +include = import_export_celery/* +plugins = + django_coverage_plugin +branch = True diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e2ebaf9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +max_line_length = 150 + +[*.{scss,js,html}] +max_line_length = 150 + +[*.rst] +max_line_length = 150 + +[*.yml] +indent_size = 2 diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..d40199a --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,105 @@ +name: CI + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13 + ports: + - 5432:5432 + env: + POSTGRES_DB: pguser + POSTGRES_USER: pguser + POSTGRES_PASSWORD: foobar + options: >- + --health-cmd "pg_isready -U pguser" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + redis: + image: redis:latest + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + django-version: ["3.2", "4.0", "4.1", "4.2", "5.0", "5.1"] + exclude: + - python-version: "3.8" + django-version: "5.0" + - python-version: "3.8" + django-version: "5.1" + + - python-version: "3.9" + django-version: "5.0" + - python-version: "3.9" + django-version: "5.1" + + - python-version: "3.11" + django-version: "3.2" + + - python-version: "3.12" + django-version: "3.2" + - python-version: "3.12" + django-version: "4.0" + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox tox-gh-actions + + - name: Run Tox + env: + DJANGO_VERSION: ${{ matrix.django-version }} + run: | + PYTHON_VERSION=`echo ${{ matrix.python-version }} | sed 's/\.//'` + DJANGO_VERSION=`echo $DJANGO_VERSION | sed 's/\.//'` + tox -e py${PYTHON_VERSION}-django${DJANGO_VERSION} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage.xml + fail_ci_if_error: true + + flake8: + name: flake8 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - run: pip install --upgrade flake8 + - name: flake8 + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: flake8 + run: flake8 diff --git a/.github/workflows/publish-to-live-pypi.yml b/.github/workflows/publish-to-live-pypi.yml new file mode 100644 index 0000000..4a1ec95 --- /dev/null +++ b/.github/workflows/publish-to-live-pypi.yml @@ -0,0 +1,42 @@ +name: Publish Python šŸ distributions šŸ“¦ to pypi + +on: + release: + types: + - published + +jobs: + build-n-publish: + name: Build and publish Python šŸ distributions šŸ“¦ to pypi + runs-on: ubuntu-latest + environment: pypi + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python 3.9 + uses: actions/setup-python@v3 + with: + python-version: 3.9 + + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + + - name: Publish distribution šŸ“¦ to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml new file mode 100644 index 0000000..2c76669 --- /dev/null +++ b/.github/workflows/publish-to-test-pypi.yml @@ -0,0 +1,43 @@ +name: Publish Python šŸ distributions šŸ“¦ to TestPyPI + +on: + push: + branches: + - master + +jobs: + build-n-publish: + name: Build and publish Python šŸ distributions šŸ“¦ to TestPyPI + environment: pypi-test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python 3.9 + uses: actions/setup-python@v3 + with: + python-version: 3.9 + + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + + - name: Publish distribution šŸ“¦ to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true diff --git a/.gitignore b/.gitignore index b8fc03c..9fd409b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/ example/django-import-export-celery-import-change-summaries/ example/django-import-export-celery-import-jobs/ example/django-import-export-celery-export-jobs/ -pipenv/ +pyenv/ django_import_export_celery.egg-info/ db/ +.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0e90cc9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +default_language_version: + python: python3.7 + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: https://github.com/asottile/pyupgrade + rev: v2.31.1 + hooks: + - id: pyupgrade + args: [--py37-plus] + +- repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + +- repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear + - flake8-comprehensions + - flake8-tidy-imports + - flake8-typing-imports diff --git a/.travis.yml b/.travis.yml index 82dcea6..d44624a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,19 @@ env: global: - DATABASE_NAME="travis_ci" - DATABASE_USER="postgres" + - DATABASE_PORT=5432 - DATABASE_HOST="" - DATABASE_PASSWORD="" - DJANGO_SETTINGS_MODULE="project.settings" - SECRET_KEY="sadfjasasdfasdfsadfsadfsadfeq" matrix: - - DJANGO_VERSION="Django==3.1" + - DJANGO_VERSION="Django==3.2" python: - "3.7" + - "3.8" + - "3.9" +# - "3.10" install: - pip install poetry - cd example diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..55f0d7a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +Please follow the spirit of [The PSF code of conduct](https://www.python.org/psf/conduct/) and try to be nice. diff --git a/Dockerfile b/Dockerfile index 2892bd8..5cd24d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ FROM python:3.7 RUN pip3 install poetry celery RUN apt-get update ; apt-get install -yq python3-psycopg2 gdal-bin -RUN useradd test +ARG UID +RUN useradd test --uid $UID RUN chsh test -s /bin/bash diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..da3ca87 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include import_export_celery/static *.js *.css *.map *.png *.ico *.eot *.svg *.ttf *.woff *.woff2 +recursive-include import_export_celery/templates *.html +recursive-include import_export_celery/locale *.mo diff --git a/Makefile b/Makefile index 459dfa2..01ff748 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,8 @@ -VERSION = $(shell git describe --abbrev=0 --tags) -REPO_FILES ?= git ls-files -z | xargs --null -I '{}' find '{}' -type f -print0 -REPO_TXT_FILES ?= git ls-files -z | xargs --null -I '{}' find '{}' -type f -print0 | egrep -zZv '(png|svg)$$' +docker-compose: Dockerfile + mkdir -p pyenv + mkdir -p db + sudo docker-compose build --build-arg UID=$(shell id -u) + sudo docker-compose up -d web postgres + sudo docker exec -it django-import-export-celery_web_1 /proj/setup-dev-env.sh + sudo docker-compose down -packages: clean - python3 setup.py bdist_wheel - git archive --format=tar.gz $(VERSION) > dist/django-import-export-celery-$(VERSION).tar.gz - cd dist ; gpg --detach-sign -a *.whl - cd dist ; gpg --detach-sign -a *.tar.gz - -deploy-pypi: - twine upload dist/*.whl - twine upload dist/*.whl.asc - -deploy: deploy-pypi - echo Deployed - -clean: - rm -r dist ; exit 0 - rm -r django_import_export_celery.egg-info ; exit 0 - rm -r build ; exit 0 - -qa: qa-miscellaneous qa-https-everywhere - -qa-miscellaneous: - $(REPO_TXT_FILES) | grep -zZv 'setup.py$$' | xargs --null sed -i 's#LGPL\s*v3#LGPL-3.0#g;' - -qa-https-everywhere: - $(REPO_TXT_FILES) | xargs --null sed --regexp-extended --in-place 's#http(:\\?/\\?/)(momentjs\.com|overpass-turbo\.eu|www\.gnu\.org|stackoverflow\.com|(:?www\.)?openstreetmap\.(org|de)|nominatim\.openstreetmap\.org|taginfo\.openstreetmap\.org|wiki\.openstreetmap\.org|josm.openstreetmap.de|www.openstreetmap.org\\/copyright|github\.com|xkcd\.com|www\.heise\.de|www\.readthedocs\.org|askubuntu\.com|xpra\.org|docker\.com|linuxcontainers\.org|www\.ecma-international\.org|www\.w3\.org|example\.com|www\.example\.com)#https\1\2#g;' - $(REPO_TXT_FILES) | xargs --null sed -i 's#http://overpass-api\.de#https://overpass-api.de#g;' - $(REPO_TXT_FILES) | xargs --null sed --regexp-extended --in-place 's#http://(\w+\.wikipedia\.org)#https://\1#g;' diff --git a/README.rst b/README.rst index 4b9dab5..24a6a8b 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,6 @@ +.. image:: https://img.shields.io/pypi/v/django-import-export-celery.svg + :target: https://pypi.org/project/django-import-export-celery/#history + django-import-export-celery: process slow django imports and exports in celery ============================================================================== @@ -48,7 +51,14 @@ A fully configured example project can be found in the example directory of this 3. Done -Preforming an import +By default a dry run of the import is initiated when the import object is created. To instead import the file immediately without a dry-run set the `IMPORT_DRY_RUN_FIRST_TIME` to `False` + + :: + + IMPORT_DRY_RUN_FIRST_TIME = False + + +Performing an import -------------------- You will find an example django application that uses django-import-export-celery for importing data. There are instructions for running the example application in the example directory's README file. Once you have it running, you can perform an import with the following steps. @@ -61,7 +71,7 @@ You will find an example django application that uses django-import-export-celer .. image:: screenshots/import_jobs.png -3. Create a new import job. There is an example import CSV file in the example/example-data directory. Select that file. Select csv as the file format. We'll be importing to the Winner's model table. +3. Create a new import job. There is an example import CSV file in the example/example-data directory. Select that file. Select csv as the file format. We'll be importing to the Winner's model table. .. image:: screenshots/new_import_job.png @@ -118,7 +128,19 @@ As with imports, a fully configured example project can be found in the `example create_export_job_action, ) -3. Done! +3. To customise export queryset you need to add `get_export_queryset` to the `ModelResource`. + :: + + class WinnersResource(ModelResource): + class Meta: + model = Winner + + def get_export_queryset(self): + """To customise the queryset of the model resource with annotation override""" + return self.Meta.model.objects.annotate(device_type=Subquery(FCMDevice.objects.filter( + user=OuterRef("pk")).values("type")[:1]) +4. Done! + Performing exports with celery ------------------------------ @@ -136,3 +158,113 @@ Performing exports with celery 6. Click on the link near the bottom of the page titled `Exported file`. +Excluding export file formats in the admin site +----------------------------------------------- + +All available file formats to export are taken from the `Tablib project `__. + +To exclude or disable file formats from the admin site, configure `IMPORT_EXPORT_CELERY_EXCLUDED_FORMATS` django settings variable. This variable is a list of format strings written in lower case. + + :: + + IMPORT_EXPORT_CELERY_EXCLUDED_FORMATS = ["csv", "xls"] + + +Customizing File Storage Backend +-------------------------------- + +**If you are using the new Django 4.2 STORAGES**: + +By default, `import_export_celery` uses Django `default` storage. +To use your own storage, use the the `IMPORT_EXPORT_CELERY_STORAGE_ALIAS` variable in your Django settings and adding the STORAGES definition. +For instance: + + :: + + STORAGES = { + "import_export_celery": { + "BACKEND": "storages.backends.s3boto3.S3Boto3Storage", + }, + } + IMPORT_EXPORT_CELERY_STORAGE_ALIAS = 'import_export_celery' + +**DEPRECATED: If you are using old style storages**: + +Define a custom storage backend by adding the `IMPORT_EXPORT_CELERY_STORAGE` to your Django settings. For instance: + + :: + + IMPORT_EXPORT_CELERY_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" + + +Customizing Task Time Limits +---------------------------- + +By default, there is no time limit on celery import/export tasks. This can be customized by setting the following variables in your Django settings file. + + :: + + # set import time limits (in seconds) + IMPORT_EXPORT_CELERY_IMPORT_SOFT_TIME_LIMIT = 300 # 5 minutes + IMPORT_EXPORT_CELERY_IMPORT_HARD_TIME_LIMIT = 360 # 6 minutes + + # set export time limits (in seconds) + IMPORT_EXPORT_CELERY_EXPORT_SOFT_TIME_LIMIT = 300 # 5 minutes + IMPORT_EXPORT_CELERY_EXPORT_HARD_TIME_LIMIT = 360 # 6 minutes + +Customizing email settings for export job completion +---------------------------------------------------------- + +By default the export job completion email uses the following settings + + + :: + + Subject: 'Django: Export job completed' + Email template: 'email/export_job_completion.html' + Email on completion: True + + +The default email template can be found `here `__ + +The default email subject, template and sending behavior can be customized by overriding these values from django settings:- + + + :: + + EXPORT_JOB_COMPLETION_MAIL_SUBJECT="Your custom subject" + EXPORT_JOB_COMPLETION_MAIL_TEMPLATE="path_to_folder/your_custom_template.html" + EXPORT_JOB_EMAIL_ON_COMPLETION = True # Set to False to disable email + + +The email template will get some context variables that you can use to customize your template. + + + :: + + { + export_job: The current instance of ExportJob model + app_label: export_job.app_label + model: export_job.model + link: A link to go to the export_job instance on django admin + } + + +For developers of this library +------------------------------ + +You can enter a preconfigured dev environment by first running `make` and then launching `./develop.sh` to get into a docker compose environment packed with **redis**, **celery**, **postgres** and everything you need to run and test django-import-export-celery. + +Before submitting a PR please run `flake8` and (in the examples directory) `python3 manange.py test`. + +Please note, that you need to restart celery for changes to propogate to the workers. Do this with `docker-compose down celery`, `docker-compose up celery`. + +Commercial support +------------------ + +Commercial support is provided by `gradesta s.r.o `_. + +Credits +------- + +`django-import-export-celery` was developed by the Czech non-profit `auto*mat z.s. `_. diff --git a/SHOWCASE_AND_THANKS.md b/SHOWCASE_AND_THANKS.md new file mode 100644 index 0000000..2ddbfde --- /dev/null +++ b/SHOWCASE_AND_THANKS.md @@ -0,0 +1,12 @@ +User showcase and thankyous +---------------------------- + +Do you use `django-import-export-celery`? Do you want to promote your project or just say thanks? Place a pull request and get included in the showcase and thanks. + + + +[Do PrĆ”ce na Kole](https://www.dopracenakole.cz/) (Open source on [github](https://github.com/auto-mat/do-prace-na-kole)) + +Do PrĆ”ce na Kole is a month long bike to work challenge in the Czech Republic. It is run by the non profit [Auto-mat z.s.](https://auto-mat.cz). django-import-export-celery is used to import discount codes from our eshop and export lists of users for data-anaylsis using jupyter. + +Auto-mat z.s. also uses django-import-export-celery for its internal donor database [klub přatel](https://github.com/auto-mat/klub) (also open source). diff --git a/dev-entrypoint.sh b/dev-entrypoint.sh new file mode 100755 index 0000000..f1ce914 --- /dev/null +++ b/dev-entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd example +poetry shell diff --git a/develop.sh b/develop.sh new file mode 100755 index 0000000..86897e3 --- /dev/null +++ b/develop.sh @@ -0,0 +1,4 @@ +#!/bin/bash +docker-compose down +docker-compose up -d +exec docker exec -u test -it django-import-export-celery_web_1 bash --init-file "/proj/dev-entrypoint.sh" diff --git a/docker-compose.yaml b/docker-compose.yaml index 3b377a9..51fe731 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,10 +14,10 @@ services: user: test volumes: - ./:/proj/ - - ./pipenv:/home/test + - ./pyenv:/home/test celery: build: . - entrypoint: poetry run celery worker -A project.celery + entrypoint: poetry run celery -A project.celery worker -l info links: - postgres - redis @@ -27,7 +27,7 @@ services: user: test volumes: - ./:/proj/ - - ./pipenv:/home/test + - ./pyenv:/home/test redis: image: redis postgres: diff --git a/example/README.rst b/example/README.rst index 36310c5..36d6c97 100644 --- a/example/README.rst +++ b/example/README.rst @@ -69,6 +69,6 @@ Actually run the server python manage.py runserver 0.0.0.0:8000 -The example app will be available from http://127.0.0.1:8001/admin +The example app will be available from http://127.0.0.1:8001/admin Note: parts of this example app were taken from the [djano-leaflet](https://github.com/makinacorpus/django-leaflet/tree/master/example) example app. diff --git a/example/manage.py b/example/manage.py index df611ff..f78a6d2 100755 --- a/example/manage.py +++ b/example/manage.py @@ -13,7 +13,7 @@ # issue is really that Django is missing to avoid masking other # exceptions on Python 2. try: - import django + import django # noqa except ImportError: raise ImportError( "Couldn't import Django. Are you sure it's installed and " diff --git a/example/poetry.lock b/example/poetry.lock index 257d678..fa14308 100644 --- a/example/poetry.lock +++ b/example/poetry.lock @@ -1,93 +1,115 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + [[package]] name = "amqp" version = "5.0.6" description = "Low-level AMQP client for Python (fork of amqplib)." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "amqp-5.0.6-py3-none-any.whl", hash = "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"}, + {file = "amqp-5.0.6.tar.gz", hash = "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2"}, +] [package.dependencies] vine = "5.0.0" -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "asgiref" version = "3.4.1" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, +] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] - -[[package]] -name = "attrs" -version = "21.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "billiard" version = "3.6.4.0" description = "Python multiprocessing fork with improvements and bugfixes" -category = "main" optional = false python-versions = "*" +files = [ + {file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"}, + {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"}, +] [[package]] name = "black" -version = "19.10b0" +version = "22.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" +files = [ + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, +] [package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cached-property" version = "1.5.2" description = "A decorator for caching properties in classes." -category = "main" optional = false python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] [[package]] name = "celery" version = "5.0.2" description = "Distributed Task Queue." -category = "main" optional = false python-versions = ">=3.6," +files = [ + {file = "celery-5.0.2-py3-none-any.whl", hash = "sha256:930c3acd55349d028c4e7104a7d377729cbcca19d9fce470c17172d9e7f9a8b6"}, + {file = "celery-5.0.2.tar.gz", hash = "sha256:012c814967fe89e3f5d2cf49df2dba3de5f29253a7f4f2270e8fce6b901b4ebf"}, +] [package.dependencies] billiard = ">=3.6.3.0,<4.0" @@ -101,7 +123,7 @@ vine = ">=5.0.0,<6.0" [package.extras] arangodb = ["pyArango (>=1.3.2)"] auth = ["cryptography"] -azureblockblob = ["azure-storage (==0.36.0)", "azure-common (==1.1.5)", "azure-storage-common (==1.1.0)"] +azureblockblob = ["azure-common (==1.1.5)", "azure-storage (==0.36.0)", "azure-storage-common (==1.1.0)"] brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] cassandra = ["cassandra-driver (<3.21.0)"] consul = ["python-consul"] @@ -135,9 +157,12 @@ zstd = ["zstandard"] name = "click" version = "8.0.1" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -147,9 +172,11 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "click-didyoumean" version = "0.0.3" description = "Enable git-like did-you-mean feature in click." -category = "main" optional = false python-versions = "*" +files = [ + {file = "click-didyoumean-0.0.3.tar.gz", hash = "sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"}, +] [package.dependencies] click = "*" @@ -158,9 +185,12 @@ click = "*" name = "click-repl" version = "0.2.0" description = "REPL plugin for Click" -category = "main" optional = false python-versions = "*" +files = [ + {file = "click-repl-0.2.0.tar.gz", hash = "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"}, + {file = "click_repl-0.2.0-py3-none-any.whl", hash = "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b"}, +] [package.dependencies] click = "*" @@ -171,33 +201,45 @@ six = "*" name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] [[package]] name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] [[package]] name = "diff-match-patch" version = "20200713" description = "Repackaging of Google's Diff Match and Patch libraries. Offers robust algorithms to perform the operations required for synchronizing plain text." -category = "main" optional = false python-versions = ">=2.7" +files = [ + {file = "diff-match-patch-20200713.tar.gz", hash = "sha256:da6f5a01aa586df23dfc89f3827e1cafbb5420be9d87769eeb079ddfd9477a18"}, + {file = "diff_match_patch-20200713-py3-none-any.whl", hash = "sha256:8bf9d9c4e059d917b5c6312bac0c137971a32815ddbda9c682b949f2986b4d34"}, +] [[package]] name = "django" -version = "3.2.7" +version = "3.2.25" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "Django-3.2.25-py3-none-any.whl", hash = "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38"}, + {file = "Django-3.2.25.tar.gz", hash = "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777"}, +] [package.dependencies] asgiref = ">=3.3.2,<4" @@ -212,9 +254,11 @@ bcrypt = ["bcrypt"] name = "django-admin-smoke-tests" version = "0.3.0" description = "Runs some quick tests on your admin site objects to make sure there aren't non-existant fields listed, etc." -category = "dev" optional = false python-versions = "*" +files = [ + {file = "django-admin-smoke-tests-0.3.0.tar.gz", hash = "sha256:5dc35c61610d4e762bc21a6d2ba4599d67491ff5260e576e0454c14b341a2be9"}, +] [package.dependencies] django = ">=1.6" @@ -224,9 +268,11 @@ six = "*" name = "django-author" version = "1.0.2" description = "Add special User ForeignKey fields which update automatically" -category = "main" optional = false python-versions = "*" +files = [ + {file = "django-author-1.0.2.tar.gz", hash = "sha256:0238b6280f66a8ba6d1c730ab4acc52bc2bf37686940fd4db42e7af458c96635"}, +] [package.dependencies] setuptools-git = "*" @@ -235,9 +281,12 @@ setuptools-git = "*" name = "django-import-export" version = "2.5.0" description = "Django application and library for importing and exporting data with included admin integration." -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "django-import-export-2.5.0.tar.gz", hash = "sha256:c39c003bfc803fb63ba7742562f1667603a4a8d7426261845d75ce8582d40f48"}, + {file = "django_import_export-2.5.0-py3-none-any.whl", hash = "sha256:cf6f3dabdd4f32dcb26e25c7ddcba7aee3168b55d380b0da79f0349afa17c011"}, +] [package.dependencies] diff-match-patch = "*" @@ -248,34 +297,54 @@ tablib = {version = ">=0.14.0", extras = ["html", "ods", "xls", "xlsx", "yaml"]} name = "et-xmlfile" version = "1.1.0" description = "An implementation of lxml.xmlfile for the standard library" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + +[[package]] +name = "html2text" +version = "2020.1.16" +description = "Turn HTML into equivalent Markdown-structured text." +optional = false +python-versions = ">=3.5" +files = [ + {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, + {file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"}, +] [[package]] name = "importlib-metadata" version = "4.8.1" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"] [[package]] name = "kombu" version = "5.1.0" description = "Messaging library for Python." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "kombu-5.1.0-py3-none-any.whl", hash = "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"}, + {file = "kombu-5.1.0.tar.gz", hash = "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d"}, +] [package.dependencies] amqp = ">=5.0.6,<6.0.0" @@ -303,17 +372,32 @@ zookeeper = ["kazoo (>=1.3.1)"] name = "markuppy" version = "1.14" description = "An HTML/XML generator" -category = "main" optional = false python-versions = "*" +files = [ + {file = "MarkupPy-1.14.tar.gz", hash = "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f"}, +] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +optional = false +python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] [[package]] name = "odfpy" version = "1.4.1" description = "Python API and tools to manipulate OpenDocument files" -category = "main" optional = false python-versions = "*" +files = [ + {file = "odfpy-1.4.1.tar.gz", hash = "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec"}, +] [package.dependencies] defusedxml = "*" @@ -322,9 +406,12 @@ defusedxml = "*" name = "openpyxl" version = "3.0.7" description = "A Python library to read/write Excel 2010 xlsx/xlsm files" -category = "main" optional = false python-versions = ">=3.6," +files = [ + {file = "openpyxl-3.0.7-py2.py3-none-any.whl", hash = "sha256:46af4eaf201a89b610fcca177eed957635f88770a5462fb6aae4a2a52b0ff516"}, + {file = "openpyxl-3.0.7.tar.gz", hash = "sha256:6456a3b472e1ef0facb1129f3c6ef00713cebf62e736cd7a75bcc3247432f251"}, +] [package.dependencies] et-xmlfile = "*" @@ -333,17 +420,38 @@ et-xmlfile = "*" name = "pathspec" version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] + +[[package]] +name = "platformdirs" +version = "2.5.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, +] + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "prompt-toolkit" version = "3.0.20" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false python-versions = ">=3.6.2" +files = [ + {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, + {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, +] [package.dependencies] wcwidth = "*" @@ -352,17 +460,56 @@ wcwidth = "*" name = "psycopg2-binary" version = "2.9.1" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661509f51531ec125e52357a489ea3806640d0ca37d9dada461ffc69ee1e7b6e"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:d92272c7c16e105788efe2cfa5d680f07e34e0c29b03c1908f8636f55d5f915a"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:736b8797b58febabb85494142c627bd182b50d2a7ec65322983e71065ad3034c"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-win32.whl", hash = "sha256:ebccf1123e7ef66efc615a68295bf6fdba875a75d5bba10a05073202598085fc"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:1f6ca4a9068f5c5c57e744b4baa79f40e83e3746875cac3c45467b16326bab45"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-win32.whl", hash = "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, +] [[package]] name = "pudb" version = "2019.2" description = "A full-screen, console-based Python debugger" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "pudb-2019.2.tar.gz", hash = "sha256:e8f0ea01b134d802872184b05bffc82af29a1eb2f9374a277434b932d68f58dc"}, +] [package.dependencies] pygments = ">=1.0" @@ -370,78 +517,124 @@ urwid = ">=1.1.1" [[package]] name = "pygments" -version = "2.10.0" +version = "2.15.0" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, + {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, +] + +[package.extras] +plugins = ["importlib-metadata"] [[package]] name = "pytz" version = "2021.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" +files = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] [[package]] name = "pyyaml" version = "5.4.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] [[package]] name = "redis" version = "3.5.3" description = "Python client for Redis key-value store" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, + {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, +] [package.extras] hiredis = ["hiredis (>=0.1.3)"] -[[package]] -name = "regex" -version = "2021.8.28" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "setuptools-git" version = "1.2" description = "Setuptools revision control system plugin for Git" -category = "main" optional = false python-versions = "*" +files = [ + {file = "setuptools-git-1.2.tar.gz", hash = "sha256:ff64136da01aabba76ae88b050e7197918d8b2139ccbf6144e14d472b9c40445"}, + {file = "setuptools_git-1.2-py2.py3-none-any.whl", hash = "sha256:e7764dccce7d97b4b5a330d7b966aac6f9ac026385743fd6cedad553f2494cfa"}, +] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" 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 = "sqlparse" version = "0.4.1" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, +] [[package]] name = "tablib" -version = "3.0.0" +version = "3.2.1" description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV)" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "tablib-3.2.1-py3-none-any.whl", hash = "sha256:870d7e688f738531a14937a055e8bba404fbc388e77d4d500b2c904075d1019c"}, + {file = "tablib-3.2.1.tar.gz", hash = "sha256:a57f2770b8c225febec1cb1e65012a69cf30dd28be810e0ff98d024768c7d0f1"}, +] [package.dependencies] markuppy = {version = "*", optional = true, markers = "extra == \"html\""} @@ -462,63 +655,112 @@ xlsx = ["openpyxl (>=2.6.0)"] yaml = ["pyyaml"] [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +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 = "typed-ast" version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] [[package]] name = "typing-extensions" version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" optional = false python-versions = "*" +files = [ + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] [[package]] name = "urwid" version = "2.1.2" description = "A full-featured console (xterm et al.) user interface library" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "urwid-2.1.2.tar.gz", hash = "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"}, +] [[package]] name = "vine" version = "5.0.0" description = "Promises, promises, promises." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] [[package]] name = "wcwidth" version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" +files = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] [[package]] name = "xlrd" version = "2.0.1" description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"}, + {file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"}, +] [package.extras] -build = ["wheel", "twine"] +build = ["twine", "wheel"] docs = ["sphinx"] test = ["pytest", "pytest-cov"] @@ -526,317 +768,29 @@ test = ["pytest", "pytest-cov"] name = "xlwt" version = "1.3.0" description = "Library to create spreadsheet files compatible with MS Excel 97/2000/XP/2003 XLS files, on any platform, with Python 2.6, 2.7, 3.3+" -category = "main" optional = false python-versions = "*" +files = [ + {file = "xlwt-1.3.0-py2.py3-none-any.whl", hash = "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e"}, + {file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"}, +] [[package]] name = "zipp" version = "3.5.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, +] [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.7" -content-hash = "ff6bc8b6226fe4cda9ac55e605e2f8e31a1d3d9ca1f9c9d1608b9a0f829ddb2c" - -[metadata.files] -amqp = [ - {file = "amqp-5.0.6-py3-none-any.whl", hash = "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"}, - {file = "amqp-5.0.6.tar.gz", hash = "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2"}, -] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -asgiref = [ - {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, - {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, -] -attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -billiard = [ - {file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"}, - {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"}, -] -black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, -] -cached-property = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, -] -celery = [ - {file = "celery-5.0.2-py3-none-any.whl", hash = "sha256:930c3acd55349d028c4e7104a7d377729cbcca19d9fce470c17172d9e7f9a8b6"}, - {file = "celery-5.0.2.tar.gz", hash = "sha256:012c814967fe89e3f5d2cf49df2dba3de5f29253a7f4f2270e8fce6b901b4ebf"}, -] -click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, -] -click-didyoumean = [ - {file = "click-didyoumean-0.0.3.tar.gz", hash = "sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"}, -] -click-repl = [ - {file = "click-repl-0.2.0.tar.gz", hash = "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"}, - {file = "click_repl-0.2.0-py3-none-any.whl", hash = "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -defusedxml = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] -diff-match-patch = [ - {file = "diff-match-patch-20200713.tar.gz", hash = "sha256:da6f5a01aa586df23dfc89f3827e1cafbb5420be9d87769eeb079ddfd9477a18"}, - {file = "diff_match_patch-20200713-py3-none-any.whl", hash = "sha256:8bf9d9c4e059d917b5c6312bac0c137971a32815ddbda9c682b949f2986b4d34"}, -] -django = [ - {file = "Django-3.2.7-py3-none-any.whl", hash = "sha256:e93c93565005b37ddebf2396b4dc4b6913c1838baa82efdfb79acedd5816c240"}, - {file = "Django-3.2.7.tar.gz", hash = "sha256:95b318319d6997bac3595517101ad9cc83fe5672ac498ba48d1a410f47afecd2"}, -] -django-admin-smoke-tests = [ - {file = "django-admin-smoke-tests-0.3.0.tar.gz", hash = "sha256:5dc35c61610d4e762bc21a6d2ba4599d67491ff5260e576e0454c14b341a2be9"}, -] -django-author = [ - {file = "django-author-1.0.2.tar.gz", hash = "sha256:0238b6280f66a8ba6d1c730ab4acc52bc2bf37686940fd4db42e7af458c96635"}, -] -django-import-export = [ - {file = "django-import-export-2.5.0.tar.gz", hash = "sha256:c39c003bfc803fb63ba7742562f1667603a4a8d7426261845d75ce8582d40f48"}, - {file = "django_import_export-2.5.0-py3-none-any.whl", hash = "sha256:cf6f3dabdd4f32dcb26e25c7ddcba7aee3168b55d380b0da79f0349afa17c011"}, -] -et-xmlfile = [ - {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, - {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, -] -kombu = [ - {file = "kombu-5.1.0-py3-none-any.whl", hash = "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"}, - {file = "kombu-5.1.0.tar.gz", hash = "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d"}, -] -markuppy = [ - {file = "MarkupPy-1.14.tar.gz", hash = "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f"}, -] -odfpy = [ - {file = "odfpy-1.4.1-py2.7.egg", hash = "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0"}, - {file = "odfpy-1.4.1.tar.gz", hash = "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec"}, -] -openpyxl = [ - {file = "openpyxl-3.0.7-py2.py3-none-any.whl", hash = "sha256:46af4eaf201a89b610fcca177eed957635f88770a5462fb6aae4a2a52b0ff516"}, - {file = "openpyxl-3.0.7.tar.gz", hash = "sha256:6456a3b472e1ef0facb1129f3c6ef00713cebf62e736cd7a75bcc3247432f251"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] -prompt-toolkit = [ - {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, - {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, -] -psycopg2-binary = [ - {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-win32.whl", hash = "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, -] -pudb = [ - {file = "pudb-2019.2.tar.gz", hash = "sha256:e8f0ea01b134d802872184b05bffc82af29a1eb2f9374a277434b932d68f58dc"}, -] -pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, -] -pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, -] -pyyaml = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, -] -redis = [ - {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, - {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, -] -regex = [ - {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, - {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, - {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, - {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, - {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, - {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, - {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, - {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, - {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, - {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, - {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, - {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, - {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, - {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, - {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, - {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, -] -setuptools-git = [ - {file = "setuptools-git-1.2.tar.gz", hash = "sha256:ff64136da01aabba76ae88b050e7197918d8b2139ccbf6144e14d472b9c40445"}, - {file = "setuptools_git-1.2-py2.py3-none-any.whl", hash = "sha256:e7764dccce7d97b4b5a330d7b966aac6f9ac026385743fd6cedad553f2494cfa"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -sqlparse = [ - {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, - {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, -] -tablib = [ - {file = "tablib-3.0.0-py3-none-any.whl", hash = "sha256:41aa40981cddd7ec4d1fabeae7c38d271601b306386bd05b5c3bcae13e5aeb20"}, - {file = "tablib-3.0.0.tar.gz", hash = "sha256:f83cac08454f225a34a305daa20e2110d5e6335135d505f93bc66583a5f9c10d"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, -] -urwid = [ - {file = "urwid-2.1.2.tar.gz", hash = "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"}, -] -vine = [ - {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, - {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, -] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] -xlrd = [ - {file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"}, - {file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"}, -] -xlwt = [ - {file = "xlwt-1.3.0-py2.py3-none-any.whl", hash = "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e"}, - {file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"}, -] -zipp = [ - {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, - {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, -] +content-hash = "a3648819f33a1e0bf8777c6ea2b3404fd85ab3102dce0a6ccde9547efe96f9ff" diff --git a/example/project/__init__.py b/example/project/__init__.py index e69de29..53f4ccb 100644 --- a/example/project/__init__.py +++ b/example/project/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/example/project/celery.py b/example/project/celery.py index 30eaa2d..41e393c 100644 --- a/example/project/celery.py +++ b/example/project/celery.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import os import sys diff --git a/example/project/settings.py b/example/project/settings.py index 16a1956..c4ea7fe 100644 --- a/example/project/settings.py +++ b/example/project/settings.py @@ -58,7 +58,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": ["templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -67,6 +67,7 @@ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], + "debug": DEBUG, }, }, ] @@ -78,17 +79,24 @@ # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", - "NAME": os.environ.get("DATABASE_NAME", "pguser"), - "USER": os.environ.get("DATABASE_USER", "pguser"), - "PASSWORD": os.environ.get("DATABASE_PASSWORD", "foobar"), - "HOST": os.environ.get("DATABASE_HOST", "postgres"), - "PORT": os.environ.get("DATABASE_PORT", ""), - }, -} - +if os.environ.get("DATABASE_TYPE") == "sqlite": + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.environ.get("DATABASE_NAME", os.path.join(BASE_DIR, "db.sqlite3")), + } + } +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": os.environ.get("DATABASE_NAME", "pguser"), + "USER": os.environ.get("DATABASE_USER", "pguser"), + "PASSWORD": os.environ.get("DATABASE_PASSWORD", "foobar"), + "HOST": os.environ.get("DATABASE_HOST", "postgres"), + "PORT": os.environ.get("DATABASE_PORT", ""), + }, + } # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -97,9 +105,15 @@ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, - {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, ] @@ -129,3 +143,11 @@ } EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +# Default import time limits (in seconds) +IMPORT_EXPORT_CELERY_IMPORT_SOFT_TIME_LIMIT = 300 # 5 minutes +IMPORT_EXPORT_CELERY_IMPORT_HARD_TIME_LIMIT = 360 # 6 minutes + +# Default export time limits (in seconds) +IMPORT_EXPORT_CELERY_EXPORT_SOFT_TIME_LIMIT = 300 # 5 minutes +IMPORT_EXPORT_CELERY_EXPORT_HARD_TIME_LIMIT = 360 # 6 minutes diff --git a/example/pyproject.toml b/example/pyproject.toml index 513fccc..745b5e7 100644 --- a/example/pyproject.toml +++ b/example/pyproject.toml @@ -13,12 +13,13 @@ celery = "*" django-author = "*" redis = "*" psycopg2-binary = "^2.8.4" +html2text = "^2020.1.16" [tool.poetry.dev-dependencies] django-admin-smoke-tests = "^0.3.0" pudb = "^2019.2" -black = "^19.10b0" +black = "^22.3.0" [build-system] requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" \ No newline at end of file +build-backend = "poetry.masonry.api" diff --git a/example/winners/admin.py b/example/winners/admin.py index 9112d3b..9e7a117 100644 --- a/example/winners/admin.py +++ b/example/winners/admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2016 o.s. Auto*Mat from django.contrib import admin diff --git a/example/winners/models.py b/example/winners/models.py index be311e9..0609d60 100644 --- a/example/winners/models.py +++ b/example/winners/models.py @@ -1,12 +1,16 @@ from django.db import models -from django.contrib.auth.models import User from import_export.resources import ModelResource from import_export.fields import Field class Winner(models.Model): - name = models.CharField(max_length=80, null=False, blank=False, default="",) + name = models.CharField( + max_length=80, + null=False, + blank=False, + default="", + ) @classmethod def export_resource_classes(cls): @@ -23,6 +27,10 @@ class WinnersResource(ModelResource): class Meta: model = Winner + def get_export_queryset(self): + """To customise the queryset of the model resource with annotation override""" + return self.Meta.model.objects.all() + class WinnersWithAllCapsResource(WinnersResource): name_all_caps = Field() diff --git a/example/winners/tests/test_admin.py b/example/winners/tests/test_admin.py index e739ade..939a652 100644 --- a/example/winners/tests/test_admin.py +++ b/example/winners/tests/test_admin.py @@ -1,16 +1,13 @@ -from django.contrib.auth.models import User from django.contrib.messages.storage.fallback import FallbackStorage from django_admin_smoke_tests import tests -from import_export_celery.models import ExportJob, ImportJob - class AdminSmokeTest(tests.AdminSiteSmokeTest): exclude_apps = [] fixtures = [] - def post_request(self, post_data={}, params=None): + def post_request(self, post_data={}, params=None): # noqa request = self.factory.post("/", data=post_data) request.user = self.superuser request._dont_enforce_csrf_checks = True diff --git a/example/winners/tests/test_fields.py b/example/winners/tests/test_fields.py new file mode 100644 index 0000000..80ff76a --- /dev/null +++ b/example/winners/tests/test_fields.py @@ -0,0 +1,60 @@ +import django +from django.test import TestCase, override_settings +from django.conf import settings +from django.core.files.storage import FileSystemStorage +import unittest + +from import_export_celery.fields import lazy_initialize_storage_class + + +class FooTestingStorage(FileSystemStorage): + pass + + +class InitializeStorageClassTests(TestCase): + + def test_default(self): + self.assertIsInstance(lazy_initialize_storage_class(), FileSystemStorage) + + @unittest.skipUnless(django.VERSION < (5, 1), "Test only applicable for Django versions < 5.1") + @override_settings( + IMPORT_EXPORT_CELERY_STORAGE="winners.tests.test_fields.FooTestingStorage" + ) + def test_old_style(self): + del settings.IMPORT_EXPORT_CELERY_STORAGE_ALIAS + del settings.STORAGES + self.assertIsInstance(lazy_initialize_storage_class(), FooTestingStorage) + + @unittest.skipUnless((4, 2) <= django.VERSION, "Test only applicable for Django 4.2 and later") + @override_settings( + IMPORT_EXPORT_CELERY_STORAGE_ALIAS="test_import_export_celery", + STORAGES={ + "test_import_export_celery": { + "BACKEND": "winners.tests.test_fields.FooTestingStorage", + }, + "staticfiles": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + } + } + + ) + def test_new_style(self): + self.assertIsInstance(lazy_initialize_storage_class(), FooTestingStorage) + + @unittest.skipUnless((4, 2) <= django.VERSION, "Test only applicable for Django 4.2 and later") + @override_settings( + STORAGES={ + "staticfiles": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "default": { + "BACKEND": "winners.tests.test_fields.FooTestingStorage", + } + } + ) + def test_default_storage(self): + """ Test that "default" storage is used when no alias is provided """ + self.assertIsInstance(lazy_initialize_storage_class(), FooTestingStorage) diff --git a/example/winners/tests/test_models.py b/example/winners/tests/test_models.py new file mode 100644 index 0000000..cfc2916 --- /dev/null +++ b/example/winners/tests/test_models.py @@ -0,0 +1,36 @@ +import os +from django.test import TestCase, override_settings +from django.core.files.base import ContentFile + +from import_export_celery.models.exportjob import ExportJob +from import_export_celery.models.importjob import ImportJob + + +class ImportJobTestCases(TestCase): + + def test_delete_file_on_job_delete(self): + job = ImportJob.objects.create( + file=ContentFile(b"", "file.csv"), + ) + file_path = job.file.path + assert os.path.exists(file_path) + job.delete() + assert not os.path.exists(file_path) + assert not ImportJob.objects.filter(id=job.id).exists() + + +class ExportJobTestCases(TestCase): + def test_create_export_job_default_email_on_completion(self): + job = ExportJob.objects.create( + app_label="winners", model="Winner" + ) + job.refresh_from_db() + self.assertTrue(job.email_on_completion) + + @override_settings(EXPORT_JOB_EMAIL_ON_COMPLETION=False) + def test_create_export_job_false_email_on_completion(self): + job = ExportJob.objects.create( + app_label="winners", model="Winner" + ) + job.refresh_from_db() + self.assertFalse(job.email_on_completion) diff --git a/example/winners/tests/test_utils.py b/example/winners/tests/test_utils.py new file mode 100644 index 0000000..cefc09f --- /dev/null +++ b/example/winners/tests/test_utils.py @@ -0,0 +1,53 @@ +from django.test import TestCase, override_settings + +from import_export_celery.utils import ( + get_export_job_mail_subject, + get_export_job_mail_template, + get_export_job_mail_context, + get_export_job_email_on_completion, + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT, + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE, + DEFAULT_EXPORT_JOB_EMAIL_ON_COMPLETION, +) +from import_export_celery.models import ExportJob + + +class UtilsTestCases(TestCase): + def test_get_export_job_mail_subject_by_default(self): + self.assertEqual( + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT, get_export_job_mail_subject() + ) + + @override_settings(EXPORT_JOB_COMPLETION_MAIL_SUBJECT="New subject") + def test_get_export_job_mail_subject_overridden(self): + self.assertEqual("New subject", get_export_job_mail_subject()) + + def test_get_export_job_mail_template_default(self): + self.assertEqual( + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE, get_export_job_mail_template() + ) + + @override_settings(EXPORT_JOB_COMPLETION_MAIL_TEMPLATE="mytemplate.html") + def test_get_export_job_mail_template_overridden(self): + self.assertEqual("mytemplate.html", get_export_job_mail_template()) + + def test_get_export_job_email_on_completion_default(self): + self.assertEqual( + DEFAULT_EXPORT_JOB_EMAIL_ON_COMPLETION, get_export_job_email_on_completion() + ) + + @override_settings(EXPORT_JOB_EMAIL_ON_COMPLETION=False) + def test_get_export_job_email_on_completion_overridden(self): + self.assertEqual(False, get_export_job_email_on_completion()) + + def test_get_export_job_mail_context(self): + export_job = ExportJob.objects.create( + app_label="winners", model="Winner", site_of_origin="http://127.0.0.1:8000" + ) + context = get_export_job_mail_context(export_job) + expected_context = { + "app_label": "winners", + "model": "Winner", + "link": f"http://127.0.0.1:8000/adminimport_export_celery/exportjob/{export_job.id}/change/", + } + self.assertEqual(context, expected_context) diff --git a/example/winners/urls.py b/example/winners/urls.py index 65c9303..620353e 100644 --- a/example/winners/urls.py +++ b/example/winners/urls.py @@ -14,11 +14,10 @@ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf import settings -from django.conf.urls import url from django.conf.urls.static import static from django.contrib import admin -from django.views.generic import TemplateView +from django.urls import path -urlpatterns = [url(r"^admin/", admin.site.urls),] + static( - settings.MEDIA_URL, document_root=settings.MEDIA_ROOT -) +urlpatterns = [ + path("admin", admin.site.urls), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/import_export_celery/__init__.py b/import_export_celery/__init__.py index 8b13789..e69de29 100644 --- a/import_export_celery/__init__.py +++ b/import_export_celery/__init__.py @@ -1 +0,0 @@ - diff --git a/import_export_celery/admin.py b/import_export_celery/admin.py index 04f0d05..3e1ea08 100644 --- a/import_export_celery/admin.py +++ b/import_export_celery/admin.py @@ -1,13 +1,15 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2019 o.s. Auto*Mat from django import forms +from django.conf import settings from django.contrib import admin from django.core.cache import cache +from django.utils.translation import gettext_lazy as _ from . import admin_actions, models class JobWithStatusMixin: + @admin.display(description=_("Job status info")) def job_status_info(self, obj): job_status = cache.get(self.direction + "_job_status_%s" % obj.pk) if job_status: @@ -17,12 +19,17 @@ def job_status_info(self, obj): class ImportJobForm(forms.ModelForm): + model = forms.ChoiceField(label=_("Name of model to import to")) + class Meta: model = models.ImportJob fields = "__all__" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.fields["model"].choices = [ + (x, x) for x in getattr(settings, "IMPORT_EXPORT_CELERY_MODELS", {}).keys() + ] self.fields["format"].widget = forms.Select( choices=self.instance.get_format_choices() ) diff --git a/import_export_celery/admin_actions.py b/import_export_celery/admin_actions.py index 8f7f4ed..2c06434 100644 --- a/import_export_celery/admin_actions.py +++ b/import_export_celery/admin_actions.py @@ -2,7 +2,7 @@ import json from uuid import UUID -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.shortcuts import redirect @@ -54,7 +54,12 @@ def create_export_job_action(modeladmin, request, queryset): site_of_origin=request.scheme + "://" + request.get_host(), ) rurl = reverse( - "admin:%s_%s_change" % (ej._meta.app_label, ej._meta.model_name,), args=[ej.pk], + "admin:%s_%s_change" + % ( + ej._meta.app_label, + ej._meta.model_name, + ), + args=[ej.pk], ) return redirect(rurl) diff --git a/import_export_celery/apps.py b/import_export_celery/apps.py new file mode 100644 index 0000000..26d5701 --- /dev/null +++ b/import_export_celery/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ImportExportCeleryConfig(AppConfig): + name = "import_export_celery" + verbose_name = _("Import Export Celery") diff --git a/import_export_celery/fields.py b/import_export_celery/fields.py new file mode 100644 index 0000000..74f8097 --- /dev/null +++ b/import_export_celery/fields.py @@ -0,0 +1,28 @@ +from django.db import models + + +def lazy_initialize_storage_class(): + from django.conf import settings + try: + from django.core.files.storage import storages + storages_defined = True + except ImportError: + storages_defined = False + + if not hasattr(settings, 'IMPORT_EXPORT_CELERY_STORAGE') and storages_defined: + # Use new style storages if defined + storage_alias = getattr(settings, "IMPORT_EXPORT_CELERY_STORAGE_ALIAS", "default") + storage_class = storages[storage_alias] + else: + # Use old style storages if defined + from django.core.files.storage import get_storage_class + storage_class = get_storage_class(getattr(settings, "IMPORT_EXPORT_CELERY_STORAGE", "django.core.files.storage.FileSystemStorage")) + return storage_class() + + return storage_class + + +class ImportExportFileField(models.FileField): + def __init__(self, *args, **kwargs): + kwargs["storage"] = lazy_initialize_storage_class + super().__init__(*args, **kwargs) diff --git a/import_export_celery/locale/eu/LC_MESSAGES/django.po b/import_export_celery/locale/eu/LC_MESSAGES/django.po new file mode 100644 index 0000000..1d2097c --- /dev/null +++ b/import_export_celery/locale/eu/LC_MESSAGES/django.po @@ -0,0 +1,149 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Urtzi Odriozola , 2023. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-17 20:55+0000\n" +"PO-Revision-Date: 2023-06-07 21:56+0100\n" +"Last-Translator: Urtzi Odriozola \n" +"Language-Team: \n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: import_export_celery/admin.py:12 +msgid "Job status info" +msgstr "Lanaren egoera" + +#: import_export_celery/admin.py:22 import_export_celery/models/importjob.py:62 +msgid "Name of model to import to" +msgstr "Inportatu beharreko modeloaren izena" + +#: import_export_celery/admin_actions.py:20 +msgid "Perform import" +msgstr "Hasi inportazioa" + +#: import_export_celery/admin_actions.py:29 +msgid "Perform dry import" +msgstr "Hasi probako inportazioa" + +#: import_export_celery/admin_actions.py:39 +msgid "Run export job" +msgstr "Hasi esportazio lana" + +#: import_export_celery/admin_actions.py:67 +msgid "Export with celery" +msgstr "Exportatu celery bidez" + +#: import_export_celery/apps.py:7 +msgid "Import Export Celery" +msgstr "Inportatu - Esportatu Celery bidez" + +#: import_export_celery/models/exportjob.py:27 +msgid "exported file" +msgstr "esportatutako fitxategia" + +#: import_export_celery/models/exportjob.py:35 +#: import_export_celery/models/importjob.py:30 +msgid "Have we started processing the file? If so when?" +msgstr "Fitxategia prozesatzen hasi gara? Hala bada, noiz?" + +#: import_export_celery/models/exportjob.py:42 +#: import_export_celery/models/importjob.py:67 +msgid "Status of the job" +msgstr "Lanaren egoera" + +#: import_export_celery/models/exportjob.py:48 +msgid "Format of file to be exported" +msgstr "Esportatu beharreko fitxategiaren formatua" + +#: import_export_celery/models/exportjob.py:55 +msgid "App label of model to export from" +msgstr "Esportatu beharreko modeloaren aplikazio izena" + +#: import_export_celery/models/exportjob.py:60 +msgid "Name of model to export from" +msgstr "Esportatu beharreko modeloaren izena" + +#: import_export_celery/models/exportjob.py:65 +msgid "Resource to use when exporting" +msgstr "Esportatzeko erabili beharreko baliabidea" + +#: import_export_celery/models/exportjob.py:71 +msgid "JSON list of pks to export" +msgstr "Esportatzeko ID-en JSON zerrenda" + +#: import_export_celery/models/exportjob.py:76 +msgid "Send me an email when this export job is complete" +msgstr "Bidali eposta bat esportazio lan hau amaitzen denean" + +#: import_export_celery/models/exportjob.py:81 +msgid "Site of origin" +msgstr "Jatorrizko gunea" + +#: import_export_celery/models/exportjob.py:87 +msgid "Export job" +msgstr "Esportazio lana" + +#: import_export_celery/models/exportjob.py:88 +msgid "Export jobs" +msgstr "Esportazio lanak" + +#: import_export_celery/models/importjob.py:22 +msgid "File to be imported" +msgstr "Inportatu beharreko fitxategia" + +#: import_export_celery/models/importjob.py:37 +msgid "Has the import been completed? If so when?" +msgstr "Inportazioa amaitu da? Hala bada, noiz?" + +#: import_export_celery/models/importjob.py:44 +msgid "Format of file to be imported" +msgstr "Inportatu beharreko fitxategiaren formatua" + +#: import_export_celery/models/importjob.py:49 +msgid "Summary of changes made by this import" +msgstr "Inportazio honek eginiko aldaketen laburpena" + +#: import_export_celery/models/importjob.py:56 +msgid "Errors" +msgstr "Erroreak" + +#: import_export_celery/models/importjob.py:73 +msgid "Import job" +msgstr "Inportazio lana" + +#: import_export_celery/models/importjob.py:74 +msgid "Import jobs" +msgstr "Inportazio lanak" + +#: import_export_celery/tasks.py:61 +#, python-format +msgid "Imported file has a wrong encoding: %s" +msgstr "Inportatutako fitxategiak kodetze okerra du: %s" + +#: import_export_celery/tasks.py:68 +#, python-format +msgid "Error reading file: %s" +msgstr "Errorea fitxategia irakurtzean: %s" + +#: import_export_celery/tasks.py:101 +#, python-format +msgid "" +"Line: %s - %s\n" +"\t%s\n" +"%s" +msgstr "" +"Lerroa: %s - %s\n" +"\t%s\n" +"%s" + +#: import_export_celery/tasks.py:190 +#, python-format +msgid "Import error %s" +msgstr "Inportazio errorea %s" diff --git a/import_export_celery/locale/pt/LC_MESSAGES/django.mo b/import_export_celery/locale/pt/LC_MESSAGES/django.mo new file mode 100644 index 0000000..52405db Binary files /dev/null and b/import_export_celery/locale/pt/LC_MESSAGES/django.mo differ diff --git a/import_export_celery/locale/pt/LC_MESSAGES/django.po b/import_export_celery/locale/pt/LC_MESSAGES/django.po new file mode 100644 index 0000000..268d022 --- /dev/null +++ b/import_export_celery/locale/pt/LC_MESSAGES/django.po @@ -0,0 +1,150 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Dan , 2020. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-17 20:55+0000\n" +"PO-Revision-Date: 2023-05-17 21:56+0100\n" +"Last-Translator: Daniel Pluth \n" +"Language-Team: \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.3.1\n" + +#: import_export_celery/admin.py:12 +msgid "Job status info" +msgstr "Informação do estado do trabalho" + +#: import_export_celery/admin.py:22 import_export_celery/models/importjob.py:62 +msgid "Name of model to import to" +msgstr "Nome do modelo a importar" + +#: import_export_celery/admin_actions.py:20 +msgid "Perform import" +msgstr "Confirmar importação" + +#: import_export_celery/admin_actions.py:29 +msgid "Perform dry import" +msgstr "Confirmar importação" + +#: import_export_celery/admin_actions.py:39 +msgid "Run export job" +msgstr "Correr trabalho de exportação" + +#: import_export_celery/admin_actions.py:67 +msgid "Export with celery" +msgstr "Exportar com o celery" + +#: import_export_celery/apps.py:7 +msgid "Import Export Celery" +msgstr "Importar ou Exportar com o celery" + +#: import_export_celery/models/exportjob.py:27 +msgid "exported file" +msgstr "ficheiro exportado" + +#: import_export_celery/models/exportjob.py:35 +#: import_export_celery/models/importjob.py:30 +msgid "Have we started processing the file? If so when?" +msgstr "ComeƧou a processar o ficheiro? Se sim, quando?" + +#: import_export_celery/models/exportjob.py:42 +#: import_export_celery/models/importjob.py:67 +msgid "Status of the job" +msgstr "Estado do trabalho" + +#: import_export_celery/models/exportjob.py:48 +msgid "Format of file to be exported" +msgstr "Formato do ficheiro a ser exportado" + +#: import_export_celery/models/exportjob.py:55 +msgid "App label of model to export from" +msgstr "Nome da aplicação do modelo para exportar de" + +#: import_export_celery/models/exportjob.py:60 +msgid "Name of model to export from" +msgstr "Nome do modelo a exportar de" + +#: import_export_celery/models/exportjob.py:65 +msgid "Resource to use when exporting" +msgstr "Recurso a utilizar aquando da exportação" + +#: import_export_celery/models/exportjob.py:71 +msgid "JSON list of pks to export" +msgstr "Lista de ids em formato json para exportar" + +#: import_export_celery/models/exportjob.py:76 +msgid "Send me an email when this export job is complete" +msgstr "Enviar um email quando o trabalho de exportação estiver completo" + +#: import_export_celery/models/exportjob.py:81 +msgid "Site of origin" +msgstr "Site de origem" + +#: import_export_celery/models/exportjob.py:87 +msgid "Export job" +msgstr "Trabalho de exportação" + +#: import_export_celery/models/exportjob.py:88 +msgid "Export jobs" +msgstr "Trabalhos de exportação" + +#: import_export_celery/models/importjob.py:22 +msgid "File to be imported" +msgstr "Arquivo a ser importado" + +#: import_export_celery/models/importjob.py:37 +msgid "Has the import been completed? If so when?" +msgstr "A importação foi completa? Se sim, quando?" + +#: import_export_celery/models/importjob.py:44 +msgid "Format of file to be imported" +msgstr "Formato do ficheiro a ser importado" + +#: import_export_celery/models/importjob.py:49 +msgid "Summary of changes made by this import" +msgstr "Resumo das alteraƧƵes feitas por esta importação" + +#: import_export_celery/models/importjob.py:56 +msgid "Errors" +msgstr "Erros" + +#: import_export_celery/models/importjob.py:73 +msgid "Import job" +msgstr "Trabalho de importação" + +#: import_export_celery/models/importjob.py:74 +msgid "Import jobs" +msgstr "Trabalhos de importação" + +#: import_export_celery/tasks.py:61 +#, python-format +msgid "Imported file has a wrong encoding: %s" +msgstr "O arquivo importado tem uma codificação errada: %s" + +#: import_export_celery/tasks.py:68 +#, python-format +msgid "Error reading file: %s" +msgstr "Erro a ler o ficheiro: %s" + +#: import_export_celery/tasks.py:101 +#, python-format +msgid "" +"Line: %s - %s\n" +"\t%s\n" +"%s" +msgstr "" +"Linha: %s - %s\n" +"\t%s\n" +"%s" + +#: import_export_celery/tasks.py:190 +#, python-format +msgid "Import error %s" +msgstr "Erro de importação %s" diff --git a/import_export_celery/migrations/0008_alter_exportjob_id_alter_importjob_id.py b/import_export_celery/migrations/0008_alter_exportjob_id_alter_importjob_id.py new file mode 100644 index 0000000..d8eab09 --- /dev/null +++ b/import_export_celery/migrations/0008_alter_exportjob_id_alter_importjob_id.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.6 on 2023-05-15 16:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('import_export_celery', '0007_auto_20210210_1831'), + ] + + operations = [ + migrations.AlterField( + model_name='exportjob', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='importjob', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/import_export_celery/migrations/0009_alter_exportjob_options_alter_importjob_options_and_more.py b/import_export_celery/migrations/0009_alter_exportjob_options_alter_importjob_options_and_more.py new file mode 100644 index 0000000..317cb44 --- /dev/null +++ b/import_export_celery/migrations/0009_alter_exportjob_options_alter_importjob_options_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.1.9 on 2023-05-27 22:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('import_export_celery', '0008_alter_exportjob_id_alter_importjob_id'), + ] + + operations = [ + migrations.AlterModelOptions( + name='exportjob', + options={'verbose_name': 'Export job', 'verbose_name_plural': 'Export jobs'}, + ), + migrations.AlterModelOptions( + name='importjob', + options={'verbose_name': 'Import job', 'verbose_name_plural': 'Import jobs'}, + ), + migrations.AlterField( + model_name='exportjob', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='exportjob', + name='site_of_origin', + field=models.TextField(default='', max_length=255, verbose_name='Site of origin'), + ), + migrations.AlterField( + model_name='importjob', + name='errors', + field=models.TextField(blank=True, default='', verbose_name='Errors'), + ), + migrations.AlterField( + model_name='importjob', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/import_export_celery/migrations/0010_auto_20231013_0904.py b/import_export_celery/migrations/0010_auto_20231013_0904.py new file mode 100644 index 0000000..c4ea04d --- /dev/null +++ b/import_export_celery/migrations/0010_auto_20231013_0904.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.18 on 2023-10-13 09:04 + +from django.db import migrations +import import_export_celery.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('import_export_celery', '0009_alter_exportjob_options_alter_importjob_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='exportjob', + name='file', + field=import_export_celery.fields.ImportExportFileField(max_length=255, storage=import_export_celery.fields.lazy_initialize_storage_class, upload_to='django-import-export-celery-export-jobs', verbose_name='exported file'), + ), + migrations.AlterField( + model_name='importjob', + name='change_summary', + field=import_export_celery.fields.ImportExportFileField(blank=True, null=True, storage=import_export_celery.fields.lazy_initialize_storage_class, upload_to='django-import-export-celery-import-change-summaries', verbose_name='Summary of changes made by this import'), + ), + migrations.AlterField( + model_name='importjob', + name='file', + field=import_export_celery.fields.ImportExportFileField(max_length=255, storage=import_export_celery.fields.lazy_initialize_storage_class, upload_to='django-import-export-celery-import-jobs', verbose_name='File to be imported'), + ), + ] diff --git a/import_export_celery/migrations/0011_alter_exportjob_email_on_completion.py b/import_export_celery/migrations/0011_alter_exportjob_email_on_completion.py new file mode 100644 index 0000000..809e71d --- /dev/null +++ b/import_export_celery/migrations/0011_alter_exportjob_email_on_completion.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.25 on 2024-11-22 12:19 + +from django.db import migrations, models +import import_export_celery.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('import_export_celery', '0010_auto_20231013_0904'), + ] + + operations = [ + migrations.AlterField( + model_name='exportjob', + name='email_on_completion', + field=models.BooleanField(default=import_export_celery.utils.get_export_job_email_on_completion, verbose_name='Send me an email when this export job is complete'), + ), + ] diff --git a/import_export_celery/models/__init__.py b/import_export_celery/models/__init__.py index 727c613..55d1682 100644 --- a/import_export_celery/models/__init__.py +++ b/import_export_celery/models/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2019 o.s. Auto*Mat """Import all models.""" diff --git a/import_export_celery/models/exportjob.py b/import_export_celery/models/exportjob.py index 7fa2efa..9876d19 100644 --- a/import_export_celery/models/exportjob.py +++ b/import_export_celery/models/exportjob.py @@ -1,36 +1,20 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2019 o.s. Auto*Mat -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from django.utils import timezone import json from author.decorators import with_author -from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db import models +from django.db import transaction from django.dispatch import receiver from django.db.models.signals import post_save -from django.utils.translation import ugettext_lazy as _ - -from import_export.formats.base_formats import DEFAULT_FORMATS +from django.utils.translation import gettext_lazy as _ +from ..fields import ImportExportFileField from ..tasks import run_export_job +from ..utils import get_formats, get_export_job_email_on_completion @with_author @@ -39,7 +23,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._content_type = None - file = models.FileField( + file = ImportExportFileField( verbose_name=_("exported file"), upload_to="django-import-export-celery-export-jobs", blank=False, @@ -55,7 +39,9 @@ def __init__(self, *args, **kwargs): ) job_status = models.CharField( - verbose_name=_("Status of the job"), max_length=160, blank=True, + verbose_name=_("Status of the job"), + max_length=160, + blank=True, ) format = models.CharField( @@ -66,27 +52,40 @@ def __init__(self, *args, **kwargs): ) app_label = models.CharField( - verbose_name=_("App label of model to export from"), max_length=160, + verbose_name=_("App label of model to export from"), + max_length=160, ) model = models.CharField( - verbose_name=_("Name of model to export from"), max_length=160, + verbose_name=_("Name of model to export from"), + max_length=160, ) resource = models.CharField( - verbose_name=_("Resource to use when exporting"), max_length=255, default="", + verbose_name=_("Resource to use when exporting"), + max_length=255, + default="", ) queryset = models.TextField( - verbose_name=_("JSON list of pks to export"), null=False, + verbose_name=_("JSON list of pks to export"), + null=False, ) email_on_completion = models.BooleanField( verbose_name=_("Send me an email when this export job is complete"), - default=True, + default=get_export_job_email_on_completion, + ) + + site_of_origin = models.TextField( + verbose_name=_("Site of origin"), + max_length=255, + default="", ) - site_of_origin = models.TextField(max_length=255, default="",) + class Meta: + verbose_name = _("Export job") + verbose_name_plural = _("Export jobs") def get_resource_class(self): if self.resource: @@ -99,12 +98,19 @@ def get_resource_class(self): def get_content_type(self): if not self._content_type: self._content_type = ContentType.objects.get( - app_label=self.app_label, model=self.model, + app_label=self.app_label, + model=self.model, ) return self._content_type def get_queryset(self): pks = json.loads(self.queryset) + # If customised queryset for the model exists + # then it'll apply filter on that otherwise it'll + # apply filter directly on the model. + resource_class = self.get_resource_class() + if hasattr(resource_class, "get_export_queryset"): + return resource_class().get_export_queryset().filter(pk__in=pks) return self.get_content_type().model_class().objects.filter(pk__in=pks) def get_resource_choices(self): @@ -118,10 +124,10 @@ def get_resource_choices(self): @staticmethod def get_format_choices(): - """ returns choices of available export formats """ + """returns choices of available export formats""" return [ (f.CONTENT_TYPE, f().get_title()) - for f in DEFAULT_FORMATS + for f in get_formats() if f().can_export() ] @@ -131,4 +137,4 @@ def exportjob_post_save(sender, instance, **kwargs): if instance.resource and not instance.processing_initiated: instance.processing_initiated = timezone.now() instance.save() - run_export_job.delay(instance.pk) + transaction.on_commit(lambda: run_export_job.delay(instance.pk)) diff --git a/import_export_celery/models/importjob.py b/import_export_celery/models/importjob.py index c5b6517..74e545e 100644 --- a/import_export_celery/models/importjob.py +++ b/import_export_celery/models/importjob.py @@ -1,39 +1,29 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2019 o.s. Auto*Mat -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django.conf import settings from django.utils import timezone from author.decorators import with_author -from django.conf import settings from django.db import models, transaction from django.dispatch import receiver -from django.db.models.signals import post_save -from django.utils.translation import ugettext_lazy as _ +from django.db.models.signals import post_save, post_delete +from django.utils.translation import gettext_lazy as _ from import_export.formats.base_formats import DEFAULT_FORMATS +from ..fields import ImportExportFileField from ..tasks import run_import_job +import logging + +logger = logging.getLogger(__name__) + @with_author class ImportJob(models.Model): - file = models.FileField( + file = ImportExportFileField( verbose_name=_("File to be imported"), upload_to="django-import-export-celery-import-jobs", blank=False, @@ -56,33 +46,41 @@ class ImportJob(models.Model): ) format = models.CharField( - verbose_name=_("Format of file to be imported"), max_length=255, + verbose_name=_("Format of file to be imported"), + max_length=255, ) - change_summary = models.FileField( + change_summary = ImportExportFileField( verbose_name=_("Summary of changes made by this import"), upload_to="django-import-export-celery-import-change-summaries", blank=True, null=True, ) - errors = models.TextField(default="", blank=True,) + errors = models.TextField( + verbose_name=_("Errors"), + default="", + blank=True, + ) model = models.CharField( verbose_name=_("Name of model to import to"), max_length=160, - choices=[ - (x, x) for x in getattr(settings, "IMPORT_EXPORT_CELERY_MODELS", {}).keys() - ], ) job_status = models.CharField( - verbose_name=_("Status of the job"), max_length=160, blank=True, + verbose_name=_("Status of the job"), + max_length=160, + blank=True, ) + class Meta: + verbose_name = _("Import job") + verbose_name_plural = _("Import jobs") + @staticmethod def get_format_choices(): - """ returns choices of available import formats """ + """returns choices of available import formats""" return [ (f.CONTENT_TYPE, f().get_title()) for f in DEFAULT_FORMATS @@ -95,4 +93,24 @@ def importjob_post_save(sender, instance, **kwargs): if not instance.processing_initiated: instance.processing_initiated = timezone.now() instance.save() - transaction.on_commit(lambda: run_import_job.delay(instance.pk, dry_run=True)) + transaction.on_commit( + lambda: run_import_job.delay( + instance.pk, + dry_run=getattr(settings, "IMPORT_DRY_RUN_FIRST_TIME", True), + ) + ) + + +@receiver(post_delete, sender=ImportJob) +def auto_delete_file_on_delete(sender, instance, **kwargs): + """ + Deletes file related to the import job + """ + if instance.file: + try: + instance.file.delete() + except Exception as e: + logger.error( + "Some error occurred while deleting ImportJob file: {0}".format(e) + ) + ImportJob.objects.filter(id=instance.id).delete() diff --git a/import_export_celery/tasks.py b/import_export_celery/tasks.py index 873dc63..2b87759 100644 --- a/import_export_celery/tasks.py +++ b/import_export_celery/tasks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Author: Timothy Hobbs hobbs.cz> from django.utils import timezone import os @@ -8,16 +7,13 @@ from django.conf import settings from django.core.files.base import ContentFile from django.core.cache import cache -from django.core.mail import send_mail -from django.urls import reverse -from django.utils.encoding import force_text -from django.utils.translation import ugettext as _ - -from import_export.formats.base_formats import DEFAULT_FORMATS +from django.utils.encoding import force_str +from django.utils.translation import gettext_lazy as _ from . import models from .model_config import ModelConfig +from .utils import send_export_job_completion_mail, get_formats from celery.utils.log import get_task_logger import logging @@ -41,7 +37,7 @@ def change_job_status(job, direction, job_status, dry_run=False): def get_format(job): - for format in DEFAULT_FORMATS: + for format in get_formats(): if job.format == format.CONTENT_TYPE: return format() break @@ -53,13 +49,16 @@ def _run_import_job(import_job, dry_run=True): import_job.errors = "" model_config = ModelConfig(**importables[import_job.model]) import_format = get_format(import_job) - try: # Copied from https://github.com/django-import-export/django-import-export/blob/3c082f98afe7996e79f936418fced3094f141c26/import_export/admin.py#L260 sorry + # Copied from https://github.com/django-import-export/django-import-export/blob/3c082f98afe7996e79f936418fced3094f141c26/import_export/admin.py#L260 sorry # noqa + try: data = import_job.file.read() if not import_format.is_binary(): - data = force_text(data, "utf8") + data = force_str(data, "utf8") dataset = import_format.create_dataset(data) except UnicodeDecodeError as e: - import_job.errors += _("Imported file has a wrong encoding: %s" % e) + "\n" + import_job.errors += ( + _("Imported file has a wrong encoding: %s" % e) + "\n" + ) change_job_status( import_job, "import", "Imported file has a wrong encoding", dry_run ) @@ -70,7 +69,9 @@ def _run_import_job(import_job, dry_run=True): change_job_status(import_job, "import", "Error reading file", dry_run) import_job.save() return - change_job_status(import_job, "import", "2/5 Processing import data", dry_run) + change_job_status( + import_job, "import", "2/5 Processing import data", dry_run + ) class Resource(model_config.resource): def __init__(self, import_job, *args, **kwargs): @@ -84,17 +85,21 @@ def before_import_row(self, row, **kwargs): change_job_status( import_job, "import", - "3/5 Importing row %s/%s" % (row_number, len(dataset)), + f"3/5 Importing row {row_number}/{len(dataset)}", dry_run, ) - return super(Resource, self).before_import_row(row, **kwargs) + return super().before_import_row(row, **kwargs) resource = Resource(import_job=import_job) + skip_diff = resource._meta.skip_diff or resource._meta.skip_html_diff + result = resource.import_data(dataset, dry_run=dry_run) - change_job_status(import_job, "import", "4/5 Generating import summary", dry_run) + change_job_status( + import_job, "import", "4/5 Generating import summary", dry_run + ) for error in result.base_errors: - import_job.errors += "\n%s\n%s\n" % (error.error, error.traceback) + import_job.errors += f"\n{error.error}\n{error.traceback}\n" for line, errors in result.row_errors(): for error in errors: import_job.errors += _("Line: %s - %s\n\t%s\n%s") % ( @@ -110,9 +115,11 @@ def before_import_row(self, row, **kwargs): summary += '' summary += "" summary += "" - summary += '' # TODO refactor the existing template so we can use it for this + summary += ( # TODO refactor the existing template so we can use it for this + '
' + ) # https://github.com/django-import-export/django-import-export/blob/6575c3e1d89725701e918696fbc531aeb192a6f7/import_export/templates/admin/import_export/import.html - if not result.invalid_rows: + if not result.invalid_rows and not skip_diff: cols = lambda row: "" ) else: - cols = lambda row: "" + "
".join([field for field in row.diff]) summary += ( "
change_type" @@ -132,18 +139,26 @@ def before_import_row(self, row, **kwargs): + "
".join([str(field) for field in row.values]) - cols_error = lambda row: "".join( - [ - "" - + key - + "" - + "
" - + row.error.message_dict[key][0] - + "
" - for key in row.error.message_dict.keys() - ] + cols = lambda row: "
".join( + [str(field) for field in row.values] ) + + def cols_error(row): + if hasattr(row.error, "message_dict"): + return "".join( + [ + "" + + key + + "" + + "
" + + row.error.message_dict[key][0] + + "
" + for key in row.error.message_dict.keys() + ] + ) + else: + return "".join(message + "
" for message in row.error.messages) + summary += ( "
rowerrors" @@ -179,9 +194,13 @@ def before_import_row(self, row, **kwargs): import_job.save() -@shared_task(bind=False) +@shared_task( + bind=False, + soft_time_limit=getattr(settings, "IMPORT_EXPORT_CELERY_IMPORT_SOFT_TIME_LIMIT", 0), + time_limit=getattr(settings, "IMPORT_EXPORT_CELERY_IMPORT_HARD_TIME_LIMIT", 0), +) def run_import_job(pk, dry_run=True): - log.info("Importing %s dry-run %s" % (pk, dry_run)) + log.info(f"Importing {pk} dry-run {dry_run}") import_job = models.ImportJob.objects.get(pk=pk) try: _run_import_job(import_job, dry_run) @@ -192,7 +211,11 @@ def run_import_job(pk, dry_run=True): return -@shared_task(bind=False) +@shared_task( + bind=False, + soft_time_limit=getattr(settings, "IMPORT_EXPORT_CELERY_EXPORT_SOFT_TIME_LIMIT", 0), + time_limit=getattr(settings, "IMPORT_EXPORT_CELERY_EXPORT_HARD_TIME_LIMIT", 0), +) def run_export_job(pk): log.info("Exporting %s" % pk) export_job = models.ExportJob.objects.get(pk=pk) @@ -211,10 +234,10 @@ def export_resource(self, *args, **kwargs): change_job_status( export_job, "export", - "Exporting row %s/%s" % (self.row_number, qs_len), + f"Exporting row {self.row_number}/{qs_len}", ) self.row_number += 1 - return super(Resource, self).export_resource(*args, **kwargs) + return super().export_resource(*args, **kwargs) resource = Resource(export_job=export_job) @@ -232,21 +255,5 @@ def export_resource(self, *args, **kwargs): serialized = serialized.encode("utf8") export_job.file.save(filename, ContentFile(serialized)) if export_job.email_on_completion: - send_mail( - _("Django: Export job completed"), - _( - "Your export job on model {app_label}.{model} has completed. You can download the file at the following link:\n\n{link}" - ).format( - app_label=export_job.app_label, - model=export_job.model, - link=export_job.site_of_origin - + reverse( - "admin:%s_%s_change" - % (export_job._meta.app_label, export_job._meta.model_name,), - args=[export_job.pk], - ), - ), - settings.SERVER_EMAIL, - [export_job.updated_by.email], - ) + send_export_job_completion_mail(export_job) return diff --git a/import_export_celery/templates/email/export_job_completion.html b/import_export_celery/templates/email/export_job_completion.html new file mode 100644 index 0000000..845c8c5 --- /dev/null +++ b/import_export_celery/templates/email/export_job_completion.html @@ -0,0 +1,14 @@ + + + + + + + Document + + + +

Your export job on model {{app_label}}.{{model}} has completed. You can download the file at the following link:

+ {{link}} + + diff --git a/import_export_celery/utils.py b/import_export_celery/utils.py new file mode 100644 index 0000000..cbe05a7 --- /dev/null +++ b/import_export_celery/utils.py @@ -0,0 +1,98 @@ +import html2text +from django.core.mail import send_mail +from django.template.loader import get_template +from django.conf import settings +from django.urls import reverse +from import_export.formats.base_formats import DEFAULT_FORMATS + +DEFAULT_EXPORT_JOB_EMAIL_ON_COMPLETION = True +DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT = "Django: Export job completed" +DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE = ( + "email/export_job_completion.html" +) +IMPORT_EXPORT_CELERY_EXCLUDED_FORMATS = getattr( + settings, + "IMPORT_EXPORT_CELERY_EXCLUDED_FORMATS", + [], +) + + +def get_formats(): + return [ + format + for format in DEFAULT_FORMATS + if format.TABLIB_MODULE.split(".")[-1].strip("_") + not in IMPORT_EXPORT_CELERY_EXCLUDED_FORMATS + ] + + +def build_html_and_text_message(template_name, context={}): + """ + Render the given template with the context and returns + the data in html and plain text format. + """ + template = get_template(template_name) + html_message = template.render(context) + text_message = html2text.html2text(html_message) + return html_message, text_message + + +def get_export_job_mail_context(export_job): + context = { + "app_label": export_job.app_label, + "model": export_job.model, + "link": export_job.site_of_origin + + reverse( + "admin:%s_%s_change" + % ( + export_job._meta.app_label, + export_job._meta.model_name, + ), + args=[export_job.pk], + ), + } + return context + + +def get_export_job_email_on_completion(): + return getattr( + settings, + "EXPORT_JOB_EMAIL_ON_COMPLETION", + DEFAULT_EXPORT_JOB_EMAIL_ON_COMPLETION, + ) + + +def get_export_job_mail_subject(): + return getattr( + settings, + "EXPORT_JOB_COMPLETION_MAIL_SUBJECT", + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT, + ) + + +def get_export_job_mail_template(): + return getattr( + settings, + "EXPORT_JOB_COMPLETION_MAIL_TEMPLATE", + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE, + ) + + +def send_export_job_completion_mail(export_job): + """ + Send export job completion mail + """ + subject = get_export_job_mail_subject() + template_name = get_export_job_mail_template() + context = get_export_job_mail_context(export_job) + context.update({"export_job": export_job}) + html_message, text_message = build_html_and_text_message( + template_name, context + ) + send_mail( + subject=subject, + message=text_message, + html_message=html_message, + from_email=settings.SERVER_EMAIL, + recipient_list=[export_job.updated_by.email], + ) diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..9a18bf2 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,7 @@ +Django +django-import-export +django-author +html2text +celery +psycopg2-binary +django-admin-smoke-tests diff --git a/setup-dev-env.sh b/setup-dev-env.sh new file mode 100755 index 0000000..58bd72f --- /dev/null +++ b/setup-dev-env.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd example +poetry install +poetry run python3 manage.py migrate diff --git a/setup.cfg b/setup.cfg index 987b3e5..8d4f8ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,7 @@ [flake8] max-line-length = 150 exclude = + pyenv/*, env/*, env-dj19/*, *migrations/*, @@ -23,4 +24,4 @@ exclude = *.xml max-complexity = 10 enable-extensions = import-order, blind-except -ignore = C000, W504 +ignore = C000, C416, C901, E731, W503, W504 diff --git a/setup.py b/setup.py index a7a48fa..415e322 100644 --- a/setup.py +++ b/setup.py @@ -1,38 +1,51 @@ +import codecs import os from setuptools import setup, find_packages -import sys import subprocess +import datetime here = os.path.abspath(os.path.dirname(__file__)) -import codecs -requires = ['Django', 'django-import-export', 'django-author'] +requires = ["Django", "django-import-export", "django-author", "html2text"] -version = subprocess.check_output(['git','describe','--abbrev=0','--tags']).decode("utf-8") +try: + version = ( + subprocess.check_output(["git", "describe", "--abbrev=0", "--tags"]) + .decode("utf-8") + .strip() + ) +except subprocess.CalledProcessError: + version = "0.dev" + datetime.datetime.now().strftime("%Y%m%d%H%M%S") setup( - name='django-import-export-celery', - version=version, - author='Timothy Hobbs', - author_email='timothy.hobbs@auto-mat.cz', - url='https://github.com/auto-mat/django-import-export-celery', - download_url="http://pypi.python.org/pypi/django-import-export-celery/", - description="Process long running django imports and exports in celery", - long_description=codecs.open( - os.path.join( - here, 'README.rst'), 'r', 'utf-8').read(), - license='License :: OSI Approved :: GNU Lesser General Public License v3.0 or later (LGPLv3.0+)', - install_requires=requires, - packages=find_packages(), - include_package_data=True, - zip_safe=False, - classifiers=['Topic :: Utilities', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Intended Audience :: Developers', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5'], + name="django-import-export-celery", + version=version, + author="Timothy Hobbs", + author_email="timothy.hobbs@auto-mat.cz", + url="https://github.com/auto-mat/django-import-export-celery", + download_url="http://pypi.python.org/pypi/django-import-export-celery/", + description="Process long running django imports and exports in celery", + long_description=codecs.open( + os.path.join(here, "README.rst"), "r", "utf-8" + ).read(), + long_description_content_type="text/x-rst", + license=( + "License :: OSI Approved :: GNU Lesser General Public License v3.0 or" + " later (LGPLv3.0+)" + ), + install_requires=requires, + packages=find_packages(), + include_package_data=True, + zip_safe=False, + classifiers=[ + "Topic :: Utilities", + "Natural Language :: English", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Environment :: Web Environment", + "Framework :: Django", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + ], ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..53ee723 --- /dev/null +++ b/tox.ini @@ -0,0 +1,38 @@ +[tox] +envlist = + py{36,37,38,39,310}-django32 + py{38,39,310}-django40 + py{38,39,310,311}-django41 + py{38,39,310,311,312}-django42 + py{310,311,312}-django50 + py{310,311,312}-django51 + +[testenv] +deps = + -rrequirements_test.txt + coverage + django-coverage-plugin + django32: django>=3.2,<3.3 + django40: django>=4.0,<4.1 + django41: django>=4.1,<4.2 + django42: django>=4.2,<4.3 + django50: django>=5.0,<5.1 + django51: django>=5.1a1,<5.2 + +setenv = + DATABASE_TYPE=sqlite + REDIS_URL=redis://127.0.0.1:6379/0 + +allowlist_externals = coverage + +test-executable = + python --version + python -c "import django; print(django.get_version())" + pip install -r requirements_test.txt + {envbindir}/python -Wall {envbindir}/coverage run --append + +commands = + python example/manage.py migrate + {[testenv]test-executable} example/manage.py test winners + coverage report + coverage xml -o coverage.xml