pax_global_header00006660000000000000000000000064145305010610014505gustar00rootroot0000000000000052 comment=034996f90fdbba4e51878c959ef59b4cc381b35d django-timezone-field-6.1.0/000077500000000000000000000000001453050106100156645ustar00rootroot00000000000000django-timezone-field-6.1.0/.flake8000066400000000000000000000000511453050106100170330ustar00rootroot00000000000000[flake8] ignore=W503 max-line-length=120 django-timezone-field-6.1.0/.github/000077500000000000000000000000001453050106100172245ustar00rootroot00000000000000django-timezone-field-6.1.0/.github/workflows/000077500000000000000000000000001453050106100212615ustar00rootroot00000000000000django-timezone-field-6.1.0/.github/workflows/ci.yml000066400000000000000000000137371453050106100224120ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: lint: runs-on: ubuntu-latest name: Lint steps: - name: Git checkout uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: "3.12" - name: Install Poetry uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - name: Load cached venv id: cached-poetry-dependencies uses: actions/cache@v3 with: path: .venv key: venv-py3.12-${{ hashFiles('poetry.lock') }} - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root - name: Lint with black run: poetry run black --check --diff . - name: Lint with flake8 run: poetry run flake8 --exclude .venv - name: Lint with isort run: poetry run isort --check --diff . - name: Lint with pylint run: poetry run pylint tests timezone_field test: runs-on: ubuntu-latest name: Test py${{ matrix.python-version }}, dj${{ matrix.django-version }}, ${{ matrix.tz-engine}}, ${{ matrix.db-engine }} strategy: fail-fast: false matrix: # https://docs.djangoproject.com/en/4.1/faq/install/#what-python-version-can-i-use-with-django python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] django-version: ["3.2", "4.0", "4.1", "4.2", "5.0rc1"] db-engine: [sqlite, postgres] tz-engine: [pytz, zoneinfo] exclude: - django-version: "3.2" python-version: "3.11" - django-version: "3.2" python-version: "3.12" - django-version: "3.2" tz-engine: zoneinfo - django-version: "4.0" python-version: "3.11" - django-version: "4.0" python-version: "3.12" - django-version: "4.1" python-version: "3.12" - django-version: "5.0rc1" python-version: "3.8" - django-version: "5.0rc1" python-version: "3.9" - django-version: "5.0rc1" tz-engine: pytz env: PYTHON_VERSION: ${{ matrix.python-version }} DJANGO_VERSION: ${{ matrix.django-version }} DB_ENGINE: ${{ matrix.db-engine }} TZ_ENGINE: ${{ matrix.tz-engine }} services: postgres: image: postgres env: POSTGRES_DB: postgres POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Git checkout uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install Poetry uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - name: Load cached venv id: cached-poetry-dependencies uses: actions/cache@v3 with: path: .venv key: venv-py${{ matrix.python-version}}-dj${{ matrix.django-version }}-${{ matrix.tz-engine }}-${{ hashFiles('poetry.lock') }} - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root - name: Install django ${{ matrix.django-version }} if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: | source .venv/bin/activate pip install --pre "Django~=${{ matrix.django-version }}" - name: Install pytz if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' && matrix.tz-engine == 'pytz' run: | source .venv/bin/activate pip install pytz - name: Remove pytz if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' && matrix.tz-engine != 'pytz' run: | source .venv/bin/activate pip uninstall --yes pytz - name: Test with coverage env: POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_DB: postgres POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres run: poetry run pytest --cov=timezone_field --ignore=tests/test_serializer_field.py - name: Test serializer fiels with coverage if: matrix.tz-engine == 'pytz' env: POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_DB: postgres POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres run: poetry run pytest --cov=timezone_field tests/test_serializer_field.py - name: Generate coverage report run: poetry run coverage xml - name: Upload coverage report to codecov uses: codecov/codecov-action@v3 with: env_vars: PYTHON_VERSION,DJANGO_VERSION,DB_ENGINE fail_ci_if_error: true build: runs-on: ubuntu-latest name: Build steps: - name: Git checkout uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: "3.12" - name: Install Poetry uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - name: Load cached venv id: cached-poetry-dependencies uses: actions/cache@v3 with: path: .venv key: venv-py3.12-${{ hashFiles('poetry.lock') }} - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root - name: Build with poetry run: poetry build django-timezone-field-6.1.0/.gitignore000066400000000000000000000000631453050106100176530ustar00rootroot00000000000000.coverage .tox *.pyc *.egg-info htmlcov build dist django-timezone-field-6.1.0/LICENSE.txt000066400000000000000000000024651453050106100175160ustar00rootroot00000000000000Copyright (c) 2014, Mike Fogel All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-timezone-field-6.1.0/README.md000066400000000000000000000246451453050106100171560ustar00rootroot00000000000000# django-timezone-field [![CI](https://github.com/mfogel/django-timezone-field/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mfogel/django-timezone-field/actions) [![codecov](https://codecov.io/gh/mfogel/django-timezone-field/branch/main/graph/badge.svg?token=Rwekzmim3l)](https://codecov.io/gh/mfogel/django-timezone-field) [![pypi downloads](https://img.shields.io/pypi/dm/django-timezone-field.svg)](https://pypi.python.org/pypi/django-timezone-field/) [![pypi python support](https://img.shields.io/pypi/pyversions/django-timezone-field.svg)](https://pypi.python.org/pypi/django-timezone-field/) [![pypi django support](https://img.shields.io/pypi/djversions/django-timezone-field.svg)](https://pypi.python.org/pypi/django-timezone-field/) A Django app providing DB, form, and REST framework fields for [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and [`pytz`](http://pypi.python.org/pypi/pytz/) timezone objects. ## The transition from `pytz` to `zoneinfo` Like Django, this app supports both `pytz` and `zoneinfo` objects while the community transitions away from `pytz` to `zoneinfo`. All exposed fields and functions that return a timezone object accept an optional boolean kwarg `use_pytz`. If not explicitly specified, the default value used for `use_pytz` matches Django's behavior: - Django <= 3.X: `use_pytz` defaults to `True` - Django == 4.X: `use_pytz` defaults to the value of [`django.conf.settings.USE_DEPRECATED_PYTZ`](https://docs.djangoproject.com/en/4.0/ref/settings/#use-deprecated-pytz), which itself defaults to `False` - Django >= 5.X: django plans to [drop support for `pytz` altogether](https://docs.djangoproject.com/en/4.0/releases/4.0/#zoneinfo-default-timezone-implementation), and this app will likely do the same. Note that this app does _not_ declare `pytz` to be a dependency, so if you're using this app with `use_pytz=True`, you'll need to ensure `pytz` is included in the environment yourself. ### Differences in recognized timezones between `pytz` and `zoneinfo` `pytz` and `zoneinfo` search for timezone data differently. - `pytz` bundles and searches within its own copy of the [IANA timezone DB](https://www.iana.org/time-zones) - `zoneinfo` first searches the local system's timezone DB for a match. If no match is found, it then searches within the [`tzdata`](https://pypi.org/project/tzdata/) package _if it is installed_. The `tzdata` package contains a copy of the IANA timezone DB. If the local system's timezone DB doesn't cover the entire IANA timezone DB and the `tzdata` package is not installed, you may run across errors like `ZoneInfoNotFoundError: 'No time zone found with key Pacific/Kanton'` for seemingly valid timezones when transitioning from `pytz` to `zoneinfo`. The easy fix is to add `tzdata` to your project with `poetry add tzdata` or `pip install tzdata`. Assuming you have the `tzdata` package installed if needed, no [data migration](https://docs.djangoproject.com/en/4.0/topics/migrations/#data-migrations) should be necessary when switching from `pytz` to `zoneinfo`. ## Examples ### Database Field ```python import zoneinfo import pytz from django.db import models from timezone_field import TimeZoneField class MyModel(models.Model): tz1 = TimeZoneField(default="Asia/Dubai") # defaults supported, in ModelForm renders like "Asia/Dubai" tz2 = TimeZoneField(choices_display="WITH_GMT_OFFSET") # in ModelForm renders like "GMT+04:00 Asia/Dubai" tz3 = TimeZoneField(use_pytz=True) # returns pytz timezone objects tz4 = TimeZoneField(use_pytz=False) # returns zoneinfo objects my_model = MyModel( tz2="America/Vancouver", # assignment of a string tz3=pytz.timezone("America/Vancouver"), # assignment of a pytz timezone tz4=zoneinfo.ZoneInfo("America/Vancouver"), # assignment of a zoneinfo ) my_model.full_clean() # validates against pytz.common_timezones by default my_model.save() # values stored in DB as strings my_model.tz3 # value returned as pytz timezone: my_model.tz4 # value returned as zoneinfo: zoneinfo.ZoneInfo(key='America/Vancouver') ``` ### Form Field ```python from django import forms from timezone_field import TimeZoneFormField class MyForm(forms.Form): tz1 = TimeZoneFormField() # renders like "Asia/Dubai" tz2 = TimeZoneFormField(choices_display="WITH_GMT_OFFSET") # renders like "GMT+04:00 Asia/Dubai" tz3 = TimeZoneFormField(use_pytz=True) # returns pytz timezone objects tz4 = TimeZoneFormField(use_pytz=False) # returns zoneinfo objects my_form = MyForm({"tz3": "Europe/Berlin", "tz4": "Europe/Berlin"}) my_form.full_clean() # validates against pytz.common_timezones by default my_form.cleaned_data["tz3"] # value returned as pytz timezone: my_form.cleaned_data["tz4"] # value returned as zoneinfo: zoneinfo.ZoneInfo(key='Europe/Berlin') ``` ### REST Framework Serializer Field ```python from rest_framework import serializers from timezone_field.rest_framework import TimeZoneSerializerField class MySerializer(serializers.Serializer): tz1 = TimeZoneSerializerField(use_pytz=True) tz2 = TimeZoneSerializerField(use_pytz=False) my_serializer = MySerializer(data={ "tz1": "America/Argentina/Buenos_Aires", "tz2": "America/Argentina/Buenos_Aires", }) my_serializer.is_valid() my_serializer.validated_data["tz1"] # my_serializer.validated_data["tz2"] # zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires') ``` ## Installation Releases are hosted on [`pypi`](https://pypi.org/project/django-timezone-field/) and can be installed using various python packaging tools. ```bash # with poetry poetry add django-timezone-field # with pip pip install django-timezone-field ``` ## Running the tests From the repository root, with [`poetry`](https://python-poetry.org/): ```bash poetry install poetry run pytest ``` ## Changelog #### 6.1.0 (2023-11-25) - Add support for django 5.0 - Add support for python 3.12 - Fix issue with `Factory` timezone on some BSD systems ([#114](https://github.com/mfogel/django-timezone-field/issues/114)) #### 6.0.1 (2023-09-07) - Use correct default backend when running with django 3.X ([#109](https://github.com/mfogel/django-timezone-field/issues/109)) #### 6.0 (2023-08-20) - BREAKING: `pytz` removed from dependencies. If you use this package with `use_pytz=True`, you'll need to install `pytz` yourself. - Drop support for django 2.2 - Drop support for python 3.7 #### 5.1 (2023-06-18) - Add django as a dependency of this package, with correct version constraints ([#90](https://github.com/mfogel/django-timezone-field/issues/90)) - Add support for django 4.1, 4.2 - Add support for python 3.11 #### 5.0 (2022-02-08) - Add support for `zoneinfo` objects ([#79](https://github.com/mfogel/django-timezone-field/issues/79)) - Add support for django 4.0 - Remove `timezone_field.utils.add_gmt_offset_to_choices`, `display_GMT_offset` kwarg (use `choices_display` instead) - Drop support for django 3.0, 3.1 - Drop support for python 3.5, 3.6 #### 4.2.3 (2022-01-13) - Fix sdist installs ([#78](https://github.com/mfogel/django-timezone-field/issues/78)) - Officially support python 3.10 #### 4.2.1 (2021-07-07) - Reinstate `TimeZoneField.default_choices` ([#76](https://github.com/mfogel/django-timezone-field/issues/76)) #### 4.2 (2021-07-07) - Officially support django 3.2, python 3.9 - Fix bug with field deconstruction ([#74](https://github.com/mfogel/django-timezone-field/issues/74)) - Housekeeping: use poetry, github actions, pytest #### 4.1.2 (2021-03-17) - Avoid `NonExistentTimeError` during DST transition ([#70](https://github.com/mfogel/django-timezone-field/issues/70)) #### 4.1.1 (2020-11-28) - Don't import `rest_framework` from package root ([#67](https://github.com/mfogel/django-timezone-field/issues/67)) #### 4.1 (2020-11-28) - Add Django REST Framework serializer field - Add new `choices_display` kwarg with supported values `WITH_GMT_OFFSET` and `STANDARD` - Deprecate `display_GMT_offset` kwarg #### 4.0 (2019-12-03) - Add support for django 3.0, python 3.8 - Drop support for django 1.11, 2.0, 2.1, python 2.7, 3.4 #### 3.1 (2019-10-02) - Officially support django 2.2 (already worked) - Add option to display TZ offsets in form field ([#46](https://github.com/mfogel/django-timezone-field/issues/46)) #### 3.0 (2018-09-15) - Support django 1.11, 2.0, 2.1 - Add support for python 3.7 - Change default human-readable timezone names to exclude underscores ([#32](https://github.com/mfogel/django-timezone-field/issues/32) & [#37](https://github.com/mfogel/django-timezone-field/issues/37)) #### 2.1 (2018-03-01) - Add support for django 1.10, 1.11 - Add support for python 3.6 - Add wheel support - Support bytes in DB fields ([#38](https://github.com/mfogel/django-timezone-field/issues/38) & [#39](https://github.com/mfogel/django-timezone-field/issues/39)) #### 2.0 (2016-01-31) - Drop support for django 1.7, add support for django 1.9 - Drop support for python 3.2, 3.3, add support for python 3.5 - Remove tests from source distribution #### 1.3 (2015-10-12) - Drop support for django 1.6, add support for django 1.8 - Various [bug fixes](https://github.com/mfogel/django-timezone-field/issues?q=milestone%3A1.3) #### 1.2 (2015-02-05) - For form field, changed default list of accepted timezones from `pytz.all_timezones` to `pytz.common_timezones`, to match DB field behavior. #### 1.1 (2014-10-05) - Django 1.7 compatibility - Added support for formatting `choices` kwarg as `[[, ], ...]`, in addition to previous format of `[[, ], ...]`. - Changed default list of accepted timezones from `pytz.all_timezones` to `pytz.common_timezones`. If you have timezones in your DB that are in `pytz.all_timezones` but not in `pytz.common_timezones`, this is a backward-incompatible change. Old behavior can be restored by specifying `choices=[(tz, tz) for tz in pytz.all_timezones]` in your model definition. #### 1.0 (2013-08-04) - Initial release as `timezone_field`. ## Credits Originally adapted from [Brian Rosner's django-timezones](https://github.com/brosner/django-timezones). Made possible thanks to the work of the [contributors](https://github.com/mfogel/django-timezone-field/graphs/contributors). django-timezone-field-6.1.0/poetry.lock000066400000000000000000001674251453050106100200770ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.7" files = [ {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, ] [package.dependencies] typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "astroid" version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, ] [package.dependencies] lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} wrapt = [ {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, ] [[package]] name = "atomicwrites" version = "1.4.1" description = "Atomic file writes." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] [[package]] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" optional = false python-versions = ">=3.6" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] [package.extras] tzdata = ["tzdata"] [[package]] name = "black" version = "23.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] 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 = "click" version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "6.5.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" files = [ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "dill" version = "0.3.7" description = "serialize all of Python" optional = false python-versions = ">=3.7" files = [ {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, ] [package.extras] graph = ["objgraph (>=1.7.2)"] [[package]] name = "django" version = "4.2.7" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ {file = "Django-4.2.7-py3-none-any.whl", hash = "sha256:e1d37c51ad26186de355cbcec16613ebdabfa9689bbade9c538835205a8abbe9"}, {file = "Django-4.2.7.tar.gz", hash = "sha256:8e0f1c2c2786b5c0e39fe1afce24c926040fad47c8ea8ad30aaf1188df29fc41"}, ] [package.dependencies] asgiref = ">=3.6.0,<4" "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] [[package]] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.6" files = [ {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] [package.dependencies] django = ">=3.0" pytz = "*" [[package]] name = "flake8" version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.6.1" files = [ {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, ] [package.extras] colors = ["colorama (>=0.4.3)"] pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.7" files = [ {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.7" files = [ {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] [[package]] name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" optional = false python-versions = ">=3.6" files = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] [[package]] name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.6" files = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] [[package]] name = "pylint" version = "2.17.7" description = "python code static checker" optional = false python-versions = ">=3.7.2" files = [ {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, ] [package.dependencies] astroid = ">=2.15.8,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, ] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] [[package]] name = "pytest" version = "6.2.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.6" files = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] name = "pytest-cov" version = "3.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.6" files = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-django" version = "4.5.2" description = "A Django plugin for pytest." optional = false python-versions = ">=3.5" files = [ {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, ] [package.dependencies] pytest = ">=5.4.0" [package.extras] docs = ["sphinx", "sphinx-rtd-theme"] testing = ["Django", "django-configurations (>=2.0)"] [[package]] name = "pytest-lazy-fixture" version = "0.6.3" description = "It helps to use fixtures in pytest.mark.parametrize" optional = false python-versions = "*" files = [ {file = "pytest-lazy-fixture-0.6.3.tar.gz", hash = "sha256:0e7d0c7f74ba33e6e80905e9bfd81f9d15ef9a790de97993e34213deb5ad10ac"}, {file = "pytest_lazy_fixture-0.6.3-py3-none-any.whl", hash = "sha256:e0b379f38299ff27a653f03eaa69b08a6fd4484e46fd1c9907d984b9f9daeda6"}, ] [package.dependencies] pytest = ">=3.2.5" [[package]] name = "pytest-pythonpath" version = "0.7.4" description = "pytest plugin for adding to the PYTHONPATH from command line or configs." optional = false python-versions = ">=2.6, <4" files = [ {file = "pytest-pythonpath-0.7.4.tar.gz", hash = "sha256:64e195b23a8f8c0c631fb16882d9ad6fa4137ed1f2961ddd15d52065cd435db6"}, {file = "pytest_pythonpath-0.7.4-py3-none-any.whl", hash = "sha256:e73e11dab2f0b83e73229e261242b251f0a369d7f527dbfec068822fd26a6ce5"}, ] [package.dependencies] pytest = ">=2.5.2,<7" [[package]] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." optional = false python-versions = ">=3.5" files = [ {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, ] [package.extras] dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tomlkit" version = "0.12.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, ] [[package]] name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] [[package]] name = "wrapt" version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] [metadata] lock-version = "2.0" python-versions = "^3.8" content-hash = "30436b1c225fd7570ea535458ea6f4c02328a0592b543c6751a34f2598035649" django-timezone-field-6.1.0/pyproject.toml000066400000000000000000000031551453050106100206040ustar00rootroot00000000000000[build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 [tool.coverage.run] relative_files = true [tool.isort] profile = "black" line_length = 120 [tool.poetry] name = "django-timezone-field" version = "6.1.0" description = "A Django app providing DB, form, and REST framework fields for zoneinfo and pytz timezone objects." license = "BSD-2-Clause" authors = ["Mike Fogel "] readme = "README.md" repository = "https://github.com/mfogel/django-timezone-field/" classifiers = [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 3.2", "Framework :: Django :: 4.0", "Framework :: Django :: 4.1", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", "Intended Audience :: Developers", "Operating System :: OS Independent", "Topic :: Utilities", ] packages = [ { include = "timezone_field" }, ] [tool.poetry.dependencies] Django = ">=3.2,<6.0" python = "^3.8" "backports.zoneinfo" = { version = "^0.2.1", python = "<3.9" } [tool.poetry.dev-dependencies] coverage = {extras = ["toml"], version = "^6.2"} djangorestframework = "^3.13.1" flake8 = "^5.0.4" psycopg2-binary = "^2.9.3" pytest = "^6.2.5" pytest-django = "^4.5.2" pytest-pythonpath = "^0.7.3" pytest-lazy-fixture = "^0.6.3" pytest-cov = "^3.0.0" black = "^23.3.0" isort = "^5.11.5" pylint = "^2.13.9" tzdata = "^2023.3" [tool.pylint.'MESSAGES CONTROL'] max-line-length = 120 disable = "C, R, redefined-outer-name" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "tests.settings" django-timezone-field-6.1.0/tests/000077500000000000000000000000001453050106100170265ustar00rootroot00000000000000django-timezone-field-6.1.0/tests/__init__.py000066400000000000000000000000001453050106100211250ustar00rootroot00000000000000django-timezone-field-6.1.0/tests/conftest.py000066400000000000000000000066021453050106100212310ustar00rootroot00000000000000import os import pytest from django import forms from django.db import models from timezone_field import TimeZoneField, backends USA_TZS = [ "US/Alaska", "US/Arizona", "US/Central", "US/Eastern", "US/Hawaii", "US/Mountain", "US/Pacific", ] # we have to define these Models at import time, so django will create the DB table # we then redefine-the model at runtime to customize the fields further class _Model(models.Model): tz = TimeZoneField() tz_opt = TimeZoneField() tz_opt_default = TimeZoneField() class _ModelChoice(models.Model): tz_superset = TimeZoneField() tz_subset = TimeZoneField() class _ModelOldChoiceFormat(models.Model): tz_superset = TimeZoneField() tz_subset = TimeZoneField() @pytest.fixture def Model(use_pytz): class _Model(models.Model): tz = TimeZoneField(use_pytz=use_pytz) tz_opt = TimeZoneField(blank=True, use_pytz=use_pytz) tz_opt_default = TimeZoneField(blank=True, default="America/Los_Angeles", use_pytz=use_pytz) yield _Model @pytest.fixture def ModelChoice(use_pytz, all_tzstrs): class _ModelChoice(models.Model): tz_superset = TimeZoneField( choices=[(tz, tz) for tz in all_tzstrs], blank=True, use_pytz=use_pytz, ) tz_subset = TimeZoneField( choices=[(tz, tz) for tz in USA_TZS], blank=True, use_pytz=use_pytz, ) yield _ModelChoice @pytest.fixture def ModelOldChoiceFormat(use_pytz, all_tzstrs, to_tzobj): class _ModelOldChoiceFormat(models.Model): tz_superset = TimeZoneField( choices=[(to_tzobj(tz), tz) for tz in all_tzstrs], blank=True, use_pytz=use_pytz, ) tz_subset = TimeZoneField( choices=[(to_tzobj(tz), tz) for tz in USA_TZS], blank=True, use_pytz=use_pytz, ) yield _ModelOldChoiceFormat @pytest.fixture def ModelForm(Model): class _ModelForm(forms.ModelForm): class Meta: model = Model fields = "__all__" yield _ModelForm @pytest.fixture(params=[(os.environ["TZ_ENGINE"])] if "TZ_ENGINE" in os.environ else ["pytz", "zoneinfo"]) def use_pytz(request): yield request.param == "pytz" @pytest.fixture def to_tzobj(use_pytz): tz_backend = backends.get_tz_backend(use_pytz) yield tz_backend.to_tzobj @pytest.fixture def utc_tzobj(use_pytz): tz_backend = backends.get_tz_backend(use_pytz) yield tz_backend.utc_tzobj @pytest.fixture def all_tzstrs(use_pytz): tz_backend = backends.get_tz_backend(use_pytz) yield tz_backend.all_tzstrs @pytest.fixture def base_tzstrs(use_pytz): tz_backend = backends.get_tz_backend(use_pytz) yield tz_backend.base_tzstrs @pytest.fixture def pst(): yield "America/Los_Angeles" @pytest.fixture def pst_tz(to_tzobj, pst): yield to_tzobj(pst) # for pytz: pytz.tzinfo.DstTzInfo @pytest.fixture def gmt(): yield "GMT" @pytest.fixture def gmt_tz(to_tzobj, gmt): yield to_tzobj(gmt) # for pytz: pytz.tzinfo.StaticTzInfo @pytest.fixture def utc(): yield "UTC" @pytest.fixture def utc_tz(utc_tzobj): yield utc_tzobj # for pytz: pytz.utc singleton @pytest.fixture def uncommon_tz(use_pytz): # there are no Zoneinfo "uncommon" tzs yield "Singapore" if use_pytz else "foobar" @pytest.fixture def invalid_tz(): yield "foobar" django-timezone-field-6.1.0/tests/models.py000066400000000000000000000000331453050106100206570ustar00rootroot00000000000000# intentionally left blank django-timezone-field-6.1.0/tests/settings.py000066400000000000000000000047211453050106100212440ustar00rootroot00000000000000""" Django settings for tests project. For more information on this file, see https://docs.djangoproject.com/en/1.7/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.7/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "unused" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True TEMPLATE_DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "tests", "rest_framework", ) MIDDLEWARE_CLASSES = ( "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.SessionAuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ) WSGI_APPLICATION = "tests.wsgi.application" # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases db_engine = os.environ.get("DB_ENGINE", "sqlite") if db_engine == "sqlite": DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", }, } if db_engine == "postgres": DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", "HOST": os.environ.get("POSTGRES_HOST"), "PORT": os.environ.get("POSTGRES_PORT", 5432), "NAME": os.environ.get("POSTGRES_DB", "timezone_field_tests"), "USER": os.environ.get("POSTGRES_USER", "postgres"), "PASSWORD": os.environ.get("POSTGRES_PASSWORD"), }, } # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.7/howto/static-files/ STATIC_URL = "/static/" DEFAULT_AUTO_FIELD = "django.db.models.AutoField" django-timezone-field-6.1.0/tests/test_backends.py000066400000000000000000000013101453050106100222040ustar00rootroot00000000000000from django import VERSION from timezone_field.backends import USE_PYTZ_DEFAULT, get_tz_backend from timezone_field.backends.zoneinfo import ZoneInfoBackend def test_use_pytz_default_USE_DEPRECATED_PYTZ_unset(): assert USE_PYTZ_DEFAULT is (VERSION < (4, 0)) def test_get_tz_backend_when_use_pytz_is_none(): assert get_tz_backend(None) is get_tz_backend(USE_PYTZ_DEFAULT) def test_get_tz_backend_when_use_pytz_is_false(): assert isinstance(get_tz_backend(False), ZoneInfoBackend) try: from timezone_field.backends.pytz import PYTZBackend except ImportError: pass else: def test_get_tz_backend_when_use_pytz_is_true(): assert isinstance(get_tz_backend(True), PYTZBackend) django-timezone-field-6.1.0/tests/test_choices.py000066400000000000000000000102001453050106100220450ustar00rootroot00000000000000from datetime import datetime import pytest from timezone_field.choices import standard, with_gmt_offset @pytest.fixture def tzs1(): # test timezones out of order, but they should appear in order in result. # avoiding an timezones that go through a Daylight Savings change here yield [ "Asia/Kathmandu", # 45 min off the hour "Asia/Kolkata", # 30 min off the hour "America/Argentina/Buenos_Aires", # on the hour "Asia/Qatar", # on the hour ] @pytest.fixture def tzs2(): # test timezones out of order, but they should appear in order in result. yield [ "Europe/London", "Canada/Newfoundland", # 30 min off the hour "America/Los_Angeles", # on the hour "America/Santiago", # southern hemisphere ] @pytest.fixture def tzs3_names(): yield [ "America/Los_Angeles", "Europe/London", "America/Argentina/Buenos_Aires", ] @pytest.fixture def tzs3_standard_displays(): yield [ "America/Los Angeles", "Europe/London", "America/Argentina/Buenos Aires", ] def test_with_gmt_offset_using_timezone_names(tzs1, use_pytz): assert with_gmt_offset(tzs1, use_pytz=use_pytz) == [ ("America/Argentina/Buenos_Aires", "GMT-03:00 America/Argentina/Buenos Aires"), ("Asia/Qatar", "GMT+03:00 Asia/Qatar"), ("Asia/Kolkata", "GMT+05:30 Asia/Kolkata"), ("Asia/Kathmandu", "GMT+05:45 Asia/Kathmandu"), ] def test_with_gmt_offset_using_timezone_objects(tzs1, use_pytz, to_tzobj): tz_objects = [to_tzobj(name) for name in tzs1] assert with_gmt_offset(tz_objects, use_pytz=use_pytz) == [ ( to_tzobj("America/Argentina/Buenos_Aires"), "GMT-03:00 America/Argentina/Buenos Aires", ), (to_tzobj("Asia/Qatar"), "GMT+03:00 Asia/Qatar"), (to_tzobj("Asia/Kolkata"), "GMT+05:30 Asia/Kolkata"), (to_tzobj("Asia/Kathmandu"), "GMT+05:45 Asia/Kathmandu"), ] def test_with_gmt_offset_in_northern_summer(tzs2, use_pytz, utc_tzobj): now = datetime(2020, 7, 15, tzinfo=utc_tzobj) assert with_gmt_offset(tzs2, now=now, use_pytz=use_pytz) == [ ("America/Los_Angeles", "GMT-07:00 America/Los Angeles"), ("America/Santiago", "GMT-04:00 America/Santiago"), ("Canada/Newfoundland", "GMT-02:30 Canada/Newfoundland"), ("Europe/London", "GMT+01:00 Europe/London"), ] def test_with_gmt_offset_in_northern_winter(tzs2, use_pytz, utc_tzobj): now = datetime(2020, 1, 15, tzinfo=utc_tzobj) assert with_gmt_offset(tzs2, now=now, use_pytz=use_pytz) == [ ("America/Los_Angeles", "GMT-08:00 America/Los Angeles"), ("Canada/Newfoundland", "GMT-03:30 Canada/Newfoundland"), ("America/Santiago", "GMT-03:00 America/Santiago"), ("Europe/London", "GMT+00:00 Europe/London"), ] def test_with_gmt_offset_transition_forward(use_pytz, utc_tzobj): tz_names = ["Europe/London"] before = datetime(2021, 3, 28, 0, 59, 59, 999999, tzinfo=utc_tzobj) after = datetime(2021, 3, 28, 1, 0, 0, 0, tzinfo=utc_tzobj) assert with_gmt_offset(tz_names, now=before, use_pytz=use_pytz) == [("Europe/London", "GMT+00:00 Europe/London")] assert with_gmt_offset(tz_names, now=after, use_pytz=use_pytz) == [("Europe/London", "GMT+01:00 Europe/London")] def test_with_gmt_offset_transition_backward(use_pytz, utc_tzobj): tz_names = ["Europe/London"] before = datetime(2021, 10, 31, 0, 59, 59, 999999, tzinfo=utc_tzobj) after = datetime(2021, 10, 31, 1, 0, 0, 0, tzinfo=utc_tzobj) assert with_gmt_offset(tz_names, now=before, use_pytz=use_pytz) == [("Europe/London", "GMT+01:00 Europe/London")] assert with_gmt_offset(tz_names, now=after, use_pytz=use_pytz) == [("Europe/London", "GMT+00:00 Europe/London")] def test_standard_using_timezone_names(tzs3_names, tzs3_standard_displays): assert standard(tzs3_names) == list(zip(tzs3_names, tzs3_standard_displays)) def test_standard_using_timezone_objects(tzs3_names, tzs3_standard_displays, to_tzobj): tzs3_objects = [to_tzobj(tz) for tz in tzs3_names] assert standard(tzs3_objects) == list(zip(tzs3_objects, tzs3_standard_displays)) django-timezone-field-6.1.0/tests/test_choices_display_option.py000066400000000000000000000175451453050106100252050ustar00rootroot00000000000000import pytest from django import forms from django.db import models from timezone_field import TimeZoneField, TimeZoneFormField pytestmark = pytest.mark.filterwarnings("ignore:Model 'tests._choicesdisplaymodel' was already registered.") @pytest.fixture def base_tzobjs(to_tzobj, base_tzstrs): yield [to_tzobj(tz) for tz in base_tzstrs] @pytest.fixture def ChoicesDisplayForm(use_pytz): class _ChoicesDisplayForm(forms.Form): limited_tzs = [ "Asia/Tokyo", "Asia/Dubai", "America/Argentina/Buenos_Aires", "Africa/Nairobi", ] limited_choices = [(tz, tz) for tz in limited_tzs] tz_none = TimeZoneFormField(use_pytz=use_pytz) tz_standard = TimeZoneFormField(choices_display="STANDARD", use_pytz=use_pytz) tz_with_gmt_offset = TimeZoneFormField(choices_display="WITH_GMT_OFFSET", use_pytz=use_pytz) tz_limited_none = TimeZoneFormField(choices=limited_choices, use_pytz=use_pytz) tz_limited_standard = TimeZoneFormField( choices=limited_choices, choices_display="STANDARD", use_pytz=use_pytz, ) tz_limited_with_gmt_offset = TimeZoneFormField( choices=limited_choices, choices_display="WITH_GMT_OFFSET", use_pytz=use_pytz, ) yield _ChoicesDisplayForm # we have to define these Models at import time, so django will create the DB table # we then redefine-the model at runtime to customize the fields further class _ChoicesDisplayModel(models.Model): tz_none = TimeZoneField() tz_standard = TimeZoneField() tz_with_gmt_offset = TimeZoneField() tz_limited_none = TimeZoneField() tz_limited_standard = TimeZoneField() tz_limited_with_gmt_offset = TimeZoneField() @pytest.fixture def ChoicesDisplayModel(use_pytz): class _ChoicesDisplayModel(models.Model): limited_tzs = [ "Asia/Tokyo", "Asia/Dubai", "America/Argentina/Buenos_Aires", "Africa/Nairobi", ] limited_choices = [(tz, tz) for tz in limited_tzs] tz_none = TimeZoneField(use_pytz=use_pytz) tz_standard = TimeZoneField(choices_display="STANDARD", use_pytz=use_pytz) tz_with_gmt_offset = TimeZoneField(choices_display="WITH_GMT_OFFSET", use_pytz=use_pytz) tz_limited_none = TimeZoneField(choices=limited_choices, use_pytz=use_pytz) tz_limited_standard = TimeZoneField( choices=limited_choices, choices_display="STANDARD", use_pytz=use_pytz, ) tz_limited_with_gmt_offset = TimeZoneField( choices=limited_choices, choices_display="WITH_GMT_OFFSET", use_pytz=use_pytz, ) yield _ChoicesDisplayModel @pytest.fixture def ChoicesDisplayModelForm(ChoicesDisplayModel): class _ChoicesDisplayModelForm(forms.ModelForm): class Meta: model = ChoicesDisplayModel fields = "__all__" yield _ChoicesDisplayModelForm def test_db_field_invalid_choices_display(use_pytz): with pytest.raises(ValueError): TimeZoneField(choices_display="invalid", use_pytz=use_pytz) def test_form_field_invalid_choices_display(use_pytz): with pytest.raises(ValueError): TimeZoneFormField(choices_display="invalid", use_pytz=use_pytz) def test_form_field_none(ChoicesDisplayForm, base_tzstrs): form = ChoicesDisplayForm() values, displays = zip(*form.fields["tz_none"].choices) assert values == tuple(base_tzstrs) assert displays[values.index("America/Los_Angeles")] == "America/Los Angeles" assert displays[values.index("Asia/Kolkata")] == "Asia/Kolkata" def test_form_field_standard(ChoicesDisplayForm): form = ChoicesDisplayForm() assert form.fields["tz_standard"].choices == form.fields["tz_none"].choices def test_form_field_with_gmt_offset(ChoicesDisplayForm, base_tzstrs): form = ChoicesDisplayForm() values, displays = zip(*form.fields["tz_with_gmt_offset"].choices) assert values != tuple(base_tzstrs) assert sorted(values) == sorted(base_tzstrs) assert displays[values.index("America/Argentina/Buenos_Aires")] == "GMT-03:00 America/Argentina/Buenos Aires" assert displays[values.index("Europe/Moscow")] == "GMT+03:00 Europe/Moscow" def test_form_field_limited_none(ChoicesDisplayForm): form = ChoicesDisplayForm() assert form.fields["tz_limited_none"].choices == [ ("Asia/Tokyo", "Asia/Tokyo"), ("Asia/Dubai", "Asia/Dubai"), ("America/Argentina/Buenos_Aires", "America/Argentina/Buenos_Aires"), ("Africa/Nairobi", "Africa/Nairobi"), ] def test_form_field_limited_standard(ChoicesDisplayForm): form = ChoicesDisplayForm() assert form.fields["tz_limited_standard"].choices == [ ("Asia/Tokyo", "Asia/Tokyo"), ("Asia/Dubai", "Asia/Dubai"), ("America/Argentina/Buenos_Aires", "America/Argentina/Buenos Aires"), ("Africa/Nairobi", "Africa/Nairobi"), ] def test_form_field_limited_with_gmt_offset(ChoicesDisplayForm): form = ChoicesDisplayForm() assert form.fields["tz_limited_with_gmt_offset"].choices == [ ("America/Argentina/Buenos_Aires", "GMT-03:00 America/Argentina/Buenos Aires"), ("Africa/Nairobi", "GMT+03:00 Africa/Nairobi"), ("Asia/Dubai", "GMT+04:00 Asia/Dubai"), ("Asia/Tokyo", "GMT+09:00 Asia/Tokyo"), ] def test_model_form_field_none(ChoicesDisplayModelForm, to_tzobj, base_tzobjs): form = ChoicesDisplayModelForm() values, displays = zip(*form.fields["tz_none"].choices) assert values == ("",) + tuple(base_tzobjs) assert displays[values.index(to_tzobj("America/Los_Angeles"))] == "America/Los Angeles" assert displays[values.index(to_tzobj("Asia/Kolkata"))] == "Asia/Kolkata" def test_model_form_field_standard(ChoicesDisplayModelForm): form = ChoicesDisplayModelForm() assert form.fields["tz_standard"].choices == form.fields["tz_none"].choices def test_model_form_field_with_gmt_offset(ChoicesDisplayModelForm, to_tzobj, base_tzobjs): form = ChoicesDisplayModelForm() values, displays = zip(*form.fields["tz_with_gmt_offset"].choices) assert values != tuple(base_tzobjs) assert sorted(str(v) for v in values) == sorted([""] + [str(tz) for tz in base_tzobjs]) assert ( displays[values.index(to_tzobj("America/Argentina/Buenos_Aires"))] == "GMT-03:00 America/Argentina/Buenos Aires" ) assert displays[values.index(to_tzobj("Europe/Moscow"))] == "GMT+03:00 Europe/Moscow" def test_model_form_field_limited_none(ChoicesDisplayModelForm, to_tzobj): form = ChoicesDisplayModelForm() assert form.fields["tz_limited_none"].choices == [ ("", "---------"), (to_tzobj("Asia/Tokyo"), "Asia/Tokyo"), (to_tzobj("Asia/Dubai"), "Asia/Dubai"), (to_tzobj("America/Argentina/Buenos_Aires"), "America/Argentina/Buenos_Aires"), (to_tzobj("Africa/Nairobi"), "Africa/Nairobi"), ] def test_moel_form_field_limited_standard(ChoicesDisplayModelForm, to_tzobj): form = ChoicesDisplayModelForm() assert form.fields["tz_limited_standard"].choices == [ ("", "---------"), (to_tzobj("Asia/Tokyo"), "Asia/Tokyo"), (to_tzobj("Asia/Dubai"), "Asia/Dubai"), (to_tzobj("America/Argentina/Buenos_Aires"), "America/Argentina/Buenos Aires"), (to_tzobj("Africa/Nairobi"), "Africa/Nairobi"), ] def test_model_form_field_limited_with_gmt_offset(ChoicesDisplayModelForm, to_tzobj): form = ChoicesDisplayModelForm() assert form.fields["tz_limited_with_gmt_offset"].choices == [ ("", "---------"), ( to_tzobj("America/Argentina/Buenos_Aires"), "GMT-03:00 America/Argentina/Buenos Aires", ), (to_tzobj("Africa/Nairobi"), "GMT+03:00 Africa/Nairobi"), (to_tzobj("Asia/Dubai"), "GMT+04:00 Asia/Dubai"), (to_tzobj("Asia/Tokyo"), "GMT+09:00 Asia/Tokyo"), ] django-timezone-field-6.1.0/tests/test_deconstruct.py000066400000000000000000000125521453050106100230010ustar00rootroot00000000000000import pytest from django.db.migrations.writer import MigrationWriter from timezone_field import TimeZoneField @pytest.fixture( params=[ TimeZoneField(), TimeZoneField(default="UTC"), TimeZoneField(max_length=42), TimeZoneField(choices=[("US/Pacific", "US/Pacific"), ("US/Eastern", "US/Eastern")]), TimeZoneField(choices=[(b"US/Pacific", b"US/Pacific"), (b"US/Eastern", b"US/Eastern")]), "use_pytz_1", # placeholder "use_pytz_2", # placeholder ] ) def field(request, to_tzobj, use_pytz): yield TimeZoneField(use_pytz=use_pytz) if request.param == "use_pytz_1" else TimeZoneField( choices=[ (to_tzobj("US/Pacific"), "US/Pacific"), (to_tzobj("US/Eastern"), "US/Eastern"), ], use_pytz=use_pytz, ) if request.param == "use_pytz_2" else request.param def test_deconstruct(field): _name, _path, args, kwargs = field.deconstruct() new_field = TimeZoneField(*args, **kwargs) assert field.max_length == new_field.max_length assert field.choices == new_field.choices def test_full_serialization(field): # ensure the values passed to kwarg arguments can be serialized # the recommended 'deconstruct' testing by django docs doesn't cut it # https://docs.djangoproject.com/en/1.7/howto/custom-model-fields/#field-deconstruction # replicates https://github.com/mfogel/django-timezone-field/issues/12 MigrationWriter.serialize(field) # should not throw def test_from_db_value(utc_tzobj, use_pytz): """ Verify that the field can handle data coming back as bytes from the db. """ field = TimeZoneField(use_pytz=use_pytz) value = field.from_db_value(b"UTC", None, None) assert utc_tzobj == value def test_default_kwargs_not_frozen(): """ Ensure the deconstructed representation of the field does not contain kwargs if they match the default. Don't want to bloat everyone's migration files. """ field = TimeZoneField() _name, _path, _args, kwargs = field.deconstruct() assert "choices" not in kwargs assert "max_length" not in kwargs def test_specifying_defaults_not_frozen(use_pytz, to_tzobj, base_tzstrs): """ If someone's matched the default values with their kwarg args, we shouldn't bothering freezing those (excluding the use_pytz, which changes with your django version). """ field = TimeZoneField(max_length=63) _name, _path, _args, kwargs = field.deconstruct() assert "max_length" not in kwargs choices = [(to_tzobj(tz), tz.replace("_", " ")) for tz in base_tzstrs] field = TimeZoneField(choices=choices, use_pytz=use_pytz) _name, _path, _args, kwargs = field.deconstruct() assert "choices" not in kwargs @pytest.fixture( params=[ [("US/Pacific", "US/Pacific"), ("US/Eastern", "US/Eastern")], None, # placeholder for tz-engine-spefic value ] ) def choices(request, to_tzobj): yield request.param if request.param is not None else [ (to_tzobj("US/Pacific"), "US/Pacific"), (to_tzobj("US/Eastern"), "US/Eastern"), ] def test_deconstruct_when_using_choices(choices, use_pytz): field = TimeZoneField(choices=choices, use_pytz=use_pytz) _name, _path, _args, kwargs = field.deconstruct() assert kwargs == { **{ "choices": [ ("US/Pacific", "US/Pacific"), ("US/Eastern", "US/Eastern"), ] }, **({"use_pytz": use_pytz} if use_pytz is not None else {}), } @pytest.mark.parametrize( "choices_display, expected_kwargs", [ [None, {}], ["STANDARD", {"choices_display": "STANDARD"}], ["WITH_GMT_OFFSET", {"choices_display": "WITH_GMT_OFFSET"}], ], ) def test_deconstruct_when_using_choices_display(choices_display, expected_kwargs): field = TimeZoneField(choices_display=choices_display) _name, _path, _args, kwargs = field.deconstruct() assert kwargs == expected_kwargs @pytest.mark.parametrize( "choices, choices_display, expected_kwargs", [ [ [["US/Pacific", "West Coast Time"]], None, {"choices": [("US/Pacific", "West Coast Time")]}, ], [ [["US/Pacific", "West Coast Time"]], "STANDARD", {"choices": [("US/Pacific", "")], "choices_display": "STANDARD"}, ], [ [["US/Pacific", "West Coast Time"]], "WITH_GMT_OFFSET", {"choices": [("US/Pacific", "")], "choices_display": "WITH_GMT_OFFSET"}, ], ], ) def test_deconstruct_when_using_choices_and_choices_display(choices, choices_display, expected_kwargs): field = TimeZoneField(choices=choices, choices_display=choices_display) _name, _path, _args, kwargs = field.deconstruct() assert kwargs == expected_kwargs def test_deconstruct_when_using_choices_and_choices_display_base_tzstrs(base_tzstrs, use_pytz): choices = [[tz, "ignored"] for tz in base_tzstrs] field = TimeZoneField(choices=choices, choices_display="WITH_GMT_OFFSET", use_pytz=use_pytz) _name, _path, _args, kwargs = field.deconstruct() assert kwargs == {"choices_display": "WITH_GMT_OFFSET", "use_pytz": use_pytz} def test_deconstruct_when_using_use_pytz(use_pytz): field = TimeZoneField(use_pytz=use_pytz) _name, _path, _args, kwargs = field.deconstruct() assert kwargs == {"use_pytz": use_pytz} django-timezone-field-6.1.0/tests/test_field.py000066400000000000000000000070421453050106100215250ustar00rootroot00000000000000import pytest from django.core.exceptions import ValidationError from pytest_lazyfixture import lazy_fixture from timezone_field import TimeZoneField pytestmark = pytest.mark.filterwarnings("ignore:Model 'tests._model.*' was already registered.") @pytest.mark.django_db @pytest.mark.parametrize( "input_tz, output_tz", [ [lazy_fixture("pst"), lazy_fixture("pst_tz")], [lazy_fixture("pst_tz"), lazy_fixture("pst_tz")], [lazy_fixture("gmt"), lazy_fixture("gmt_tz")], [lazy_fixture("gmt_tz"), lazy_fixture("gmt_tz")], [lazy_fixture("utc"), lazy_fixture("utc_tz")], [lazy_fixture("utc_tz"), lazy_fixture("utc_tz")], ], ) def test_valid_dst_tz(Model, input_tz, output_tz): m = Model.objects.create(tz=input_tz, tz_opt=input_tz, tz_opt_default=input_tz) m.full_clean() m = Model.objects.get(pk=m.pk) assert m.tz == output_tz assert m.tz_opt == output_tz assert m.tz_opt_default == output_tz @pytest.mark.django_db def test_valid_default_values(Model, utc_tz, pst_tz): m = Model.objects.create(tz=utc_tz) m.full_clean() m = Model.objects.get(pk=m.pk) assert m.tz_opt is None assert m.tz_opt_default == pst_tz def test_valid_default_values_without_saving_to_db(Model, utc_tz, pst_tz): m = Model(tz=utc_tz) m.full_clean() assert m.tz_opt is None assert m.tz_opt_default == pst_tz @pytest.mark.django_db @pytest.mark.parametrize("tz_opt", ["", None]) def test_valid_blank(Model, pst, tz_opt): m = Model.objects.create(tz=pst, tz_opt=tz_opt) m.full_clean() m = Model.objects.get(pk=m.pk) assert m.tz_opt is None @pytest.mark.django_db @pytest.mark.parametrize("filter_tz", [lazy_fixture("pst"), lazy_fixture("pst_tz")]) def test_string_value_lookup(Model, pst, filter_tz): Model.objects.create(tz=pst) qs = Model.objects.filter(tz=filter_tz) assert qs.count() == 1 @pytest.mark.parametrize("tz", [None, "", "not-a-tz", 4, object()]) def test_invalid_input(Model, tz): m = Model(tz=tz) with pytest.raises(ValidationError): m.full_clean() def test_three_positional_args_does_not_throw(): TimeZoneField("a verbose name", "a name", True) def test_four_positional_args_throws(): with pytest.raises(ValueError): TimeZoneField("a verbose name", "a name", True, 42) def test_default_human_readable_choices_dont_have_underscores(Model, pst_tz): m = Model(tz=pst_tz) assert m.get_tz_display() == "America/Los Angeles" @pytest.mark.django_db def test_with_limited_choices_valid_choice(ModelChoice, pst, pst_tz): ModelChoice.objects.create(tz_superset=pst, tz_subset=pst) m = ModelChoice.objects.get() assert m.tz_superset == pst_tz assert m.tz_subset == pst_tz @pytest.mark.parametrize("kwargs", [{"tz_superset": "not a tz"}, {"tz_subset": "Europe/Brussels"}]) def test_with_limited_choices_invalid_choice(ModelChoice, kwargs): m = ModelChoice(**kwargs) with pytest.raises(ValidationError): m.full_clean() @pytest.mark.django_db def test_with_limited_choices_old_format_valid_choice(ModelOldChoiceFormat, pst, pst_tz): ModelOldChoiceFormat.objects.create(tz_superset=pst, tz_subset=pst) m = ModelOldChoiceFormat.objects.get() assert m.tz_superset == pst_tz assert m.tz_subset == pst_tz @pytest.mark.parametrize("kwargs", [{"tz_superset": "not a tz"}, {"tz_subset": "Europe/Brussels"}]) def test_with_limited_choices_old_format_invalid_choice(ModelOldChoiceFormat, kwargs): m = ModelOldChoiceFormat(**kwargs) with pytest.raises(ValidationError): m.full_clean() django-timezone-field-6.1.0/tests/test_form_field.py000066400000000000000000000036361453050106100225550ustar00rootroot00000000000000import pytest from django import forms from pytest_lazyfixture import lazy_fixture from timezone_field import TimeZoneFormField @pytest.fixture def Form(use_pytz): class _Form(forms.Form): tz = TimeZoneFormField(use_pytz=use_pytz) tz_opt = TimeZoneFormField(required=False, use_pytz=use_pytz) yield _Form @pytest.fixture def FormInvalidChoice(all_tzstrs, invalid_tz, use_pytz): class _FormInvalidChoice(forms.Form): tz = TimeZoneFormField(choices=([(tz, tz) for tz in all_tzstrs] + [(invalid_tz, "UTC")]), use_pytz=use_pytz) yield _FormInvalidChoice def test_form_valid1(Form, pst, pst_tz): form = Form({"tz": pst}) assert form.is_valid() assert form.cleaned_data["tz"] == pst_tz assert form.cleaned_data["tz_opt"] is None def test_form_valid2(Form, gmt, gmt_tz, utc, utc_tz): form = Form({"tz": gmt, "tz_opt": utc}) assert form.is_valid() assert form.cleaned_data["tz"] == gmt_tz assert form.cleaned_data["tz_opt"] == utc_tz @pytest.mark.parametrize( "tz, tz_invalid_choice", [ [lazy_fixture("invalid_tz"), None], [None, lazy_fixture("invalid_tz")], ], ) def test_form_invalid(Form, tz, tz_invalid_choice): form = Form({"tz": tz, "tz_invalid_choice": tz_invalid_choice}) assert not form.is_valid() def test_form_uncommon(Form, uncommon_tz): form = Form({"tz": uncommon_tz}) assert not form.is_valid() def test_form_default_human_readable_choices_dont_have_underscores(Form): form = Form() for choice in form.fields["tz"].choices: assert "_" not in choice[1] def test_form_invalid_choice_valid(FormInvalidChoice, pst, pst_tz): form = FormInvalidChoice({"tz": pst}) assert form.is_valid() assert form.cleaned_data["tz"] == pst_tz def test_form_invalid_chocie_invalid_choice(FormInvalidChoice, invalid_tz): form = FormInvalidChoice({"tz": invalid_tz}) assert not form.is_valid() django-timezone-field-6.1.0/tests/test_model_form_field.py000066400000000000000000000034021453050106100237240ustar00rootroot00000000000000import pytest from pytest_lazyfixture import lazy_fixture pytestmark = pytest.mark.filterwarnings("ignore:Model 'tests._model.*' was already registered.") @pytest.mark.django_db def test_valid_with_defaults(Model, ModelForm, pst_tz, gmt, gmt_tz): # seems there should be a better way to get a form's default values...? # http://stackoverflow.com/questions/7399490/ data = dict((field_name, field.initial) for field_name, field in ModelForm().fields.items()) data.update({"tz": gmt}) form = ModelForm(data=data) assert form.is_valid() form.save() assert Model.objects.count() == 1 m = Model.objects.get() assert m.tz == gmt_tz assert m.tz_opt is None assert m.tz_opt_default == pst_tz @pytest.mark.django_db def test_valid_specify_all(Model, ModelForm, utc, pst, gmt, utc_tz, gmt_tz, pst_tz): form = ModelForm( { "tz": utc, "tz_opt": pst, "tz_opt_default": gmt, } ) assert form.is_valid() form.save() assert Model.objects.count() == 1 m = Model.objects.get() assert m.tz == utc_tz assert m.tz_opt == pst_tz assert m.tz_opt_default == gmt_tz @pytest.mark.parametrize( "tz, error_keyword", [ [None, "required"], [lazy_fixture("invalid_tz"), "choice"], [lazy_fixture("uncommon_tz"), "choice"], ], ) def test_invalid_not_blank(ModelForm, tz, error_keyword): form = ModelForm({"tz": tz}) assert not form.is_valid() assert any(error_keyword in e for e in form.errors["tz"]) def test_default_human_readable_choices_dont_have_underscores(ModelForm, pst_tz): form = ModelForm() pst_choice = [c for c in form.fields["tz"].choices if c[0] == pst_tz] assert pst_choice[0][1] == "America/Los Angeles" django-timezone-field-6.1.0/tests/test_serializer_field.py000066400000000000000000000024651453050106100237620ustar00rootroot00000000000000import pytest from rest_framework import serializers from timezone_field.rest_framework import TimeZoneSerializerField @pytest.fixture def TimeZoneSerializer(use_pytz): class _TimeZoneSerializer(serializers.Serializer): # pylint: disable=abstract-method tz = TimeZoneSerializerField(use_pytz=use_pytz) yield _TimeZoneSerializer def test_invalid_str(TimeZoneSerializer, invalid_tz): serializer = TimeZoneSerializer(data={"tz": invalid_tz}) assert not serializer.is_valid() # https://github.com/mfogel/django-timezone-field/issues/86 def test_empty_str(TimeZoneSerializer): serializer = TimeZoneSerializer(data={"tz": ""}) assert not serializer.is_valid() def test_valid(TimeZoneSerializer, pst, pst_tz): serializer = TimeZoneSerializer(data={"tz": pst}) assert serializer.is_valid() assert serializer.validated_data["tz"] == pst_tz def test_valid_representation(TimeZoneSerializer, pst): serializer = TimeZoneSerializer(data={"tz": pst}) assert serializer.is_valid() assert serializer.data["tz"] == pst def test_valid_with_timezone_object(TimeZoneSerializer, pst, pst_tz): serializer = TimeZoneSerializer(data={"tz": pst_tz}) assert serializer.is_valid() assert serializer.data["tz"] == pst assert serializer.validated_data["tz"] == pst_tz django-timezone-field-6.1.0/timezone_field/000077500000000000000000000000001453050106100206615ustar00rootroot00000000000000django-timezone-field-6.1.0/timezone_field/__init__.py000066400000000000000000000002531453050106100227720ustar00rootroot00000000000000from timezone_field.fields import TimeZoneField from timezone_field.forms import TimeZoneFormField __version__ = "6.1.0" __all__ = ["TimeZoneField", "TimeZoneFormField"] django-timezone-field-6.1.0/timezone_field/backends/000077500000000000000000000000001453050106100224335ustar00rootroot00000000000000django-timezone-field-6.1.0/timezone_field/backends/__init__.py000066400000000000000000000012201453050106100245370ustar00rootroot00000000000000from django import VERSION, conf from .base import TimeZoneNotFoundError USE_PYTZ_DEFAULT = getattr(conf.settings, "USE_DEPRECATED_PYTZ", VERSION < (4, 0)) tz_backend_cache = {} def get_tz_backend(use_pytz): use_pytz = USE_PYTZ_DEFAULT if use_pytz is None else use_pytz if use_pytz not in tz_backend_cache: if use_pytz: from .pytz import PYTZBackend klass = PYTZBackend else: from .zoneinfo import ZoneInfoBackend klass = ZoneInfoBackend tz_backend_cache[use_pytz] = klass() return tz_backend_cache[use_pytz] __all__ = ["TimeZoneNotFoundError", "get_tz_backend"] django-timezone-field-6.1.0/timezone_field/backends/base.py000066400000000000000000000004711453050106100237210ustar00rootroot00000000000000from abc import ABC, abstractmethod class TimeZoneNotFoundError(Exception): pass class TimeZoneBackend(ABC): utc_tzobj = None all_tzstrs = None base_tzstrs = None @abstractmethod def is_tzobj(self, value): pass @abstractmethod def to_tzobj(self, tzstr): pass django-timezone-field-6.1.0/timezone_field/backends/pytz.py000066400000000000000000000007661453050106100240240ustar00rootroot00000000000000import pytz from .base import TimeZoneBackend, TimeZoneNotFoundError class PYTZBackend(TimeZoneBackend): utc_tzobj = pytz.utc all_tzstrs = pytz.all_timezones base_tzstrs = pytz.common_timezones def is_tzobj(self, value): return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo) def to_tzobj(self, tzstr): try: return pytz.timezone(tzstr) except pytz.UnknownTimeZoneError as err: raise TimeZoneNotFoundError from err django-timezone-field-6.1.0/timezone_field/backends/zoneinfo.py000066400000000000000000000015671453050106100246450ustar00rootroot00000000000000try: import zoneinfo except ImportError: from backports import zoneinfo from .base import TimeZoneBackend, TimeZoneNotFoundError class ZoneInfoBackend(TimeZoneBackend): utc_tzobj = zoneinfo.ZoneInfo("UTC") all_tzstrs = zoneinfo.available_timezones() base_tzstrs = zoneinfo.available_timezones() # Remove the "Factory" timezone as it can cause ValueError exceptions on # some systems, e.g. FreeBSD, if the system zoneinfo database is used. all_tzstrs.discard("Factory") base_tzstrs.discard("Factory") def is_tzobj(self, value): return isinstance(value, zoneinfo.ZoneInfo) def to_tzobj(self, tzstr): if tzstr in (None, ""): raise TimeZoneNotFoundError try: return zoneinfo.ZoneInfo(tzstr) except zoneinfo.ZoneInfoNotFoundError as err: raise TimeZoneNotFoundError from err django-timezone-field-6.1.0/timezone_field/choices.py000066400000000000000000000030041453050106100226450ustar00rootroot00000000000000import datetime from timezone_field.backends import get_tz_backend def standard(timezones): """ Given a list of timezones (either strings of timezone objects), return a list of choices with * values equal to what was passed in * display strings as the timezone name without underscores """ choices = [] for tz in timezones: tz_str = str(tz) choices.append((tz, tz_str.replace("_", " "))) return choices def with_gmt_offset(timezones, now=None, use_pytz=None): """ Given a list of timezones (either strings of timezone objects), return a list of choices with * values equal to what was passed in * display strings formated with GMT offsets and without underscores. For example: "GMT-05:00 America/New York" * sorted by their timezone offset """ tz_backend = get_tz_backend(use_pytz) now = now or datetime.datetime.now(tz_backend.utc_tzobj) _choices = [] for tz in timezones: tz_str = str(tz) now_tz = now.astimezone(tz_backend.to_tzobj(tz_str)) delta = now_tz.replace(tzinfo=tz_backend.utc_tzobj) - now display = "GMT{sign}{gmt_diff} {timezone}".format( sign="+" if delta == abs(delta) else "-", gmt_diff=str(abs(delta)).zfill(8)[:-3], timezone=tz_str.replace("_", " "), ) _choices.append((delta, tz, display)) _choices.sort(key=lambda x: x[0]) choices = [(one, two) for zero, one, two in _choices] return choices django-timezone-field-6.1.0/timezone_field/fields.py000066400000000000000000000147471453050106100225160ustar00rootroot00000000000000from django.core.exceptions import ValidationError from django.db import models from django.utils.encoding import force_str from timezone_field.backends import TimeZoneNotFoundError, get_tz_backend from timezone_field.choices import standard, with_gmt_offset class TimeZoneField(models.Field): """ Provides database store for pytz timezone objects. Valid inputs: * use_pytz=True: * any instance of pytz.tzinfo.DstTzInfo or pytz.tzinfo.StaticTzInfo * the pytz.UTC singleton * any string that validates against pytz.common_timezones. pytz will be used to build a timezone object from the string. * use_pytz=False: * any instance of zoneinfo.ZoneInfo * any string that validates against zoneinfo.available_timezones(). * None and the empty string both represent 'no timezone' Valid outputs: * None * use_pytz=True: instances of pytz.tzinfo.DstTzInfo, pytz.tzinfo.StaticTzInfo and the pytz.UTC singleton * use_pytz=False: instances of zoneinfo.ZoneInfo Blank values are stored in the DB as the empty string. Timezones are stored in their string representation. The `choices` kwarg can be specified as a list of either [, ] or [, ]. Internally in memory, it is stored as [, ]. """ description = "A timezone object" # NOTE: these defaults are excluded from migrations. If these are changed, # existing migration files will need to be accomodated. default_max_length = 63 def __init__(self, *args, **kwargs): # allow some use of positional args up until the args we customize # https://github.com/mfogel/django-timezone-field/issues/42 # https://github.com/django/django/blob/1.11.11/django/db/models/fields/__init__.py#L145 if len(args) > 3: raise ValueError("Cannot specify max_length by positional arg") kwargs.setdefault("max_length", self.default_max_length) self.use_pytz = kwargs.pop("use_pytz", None) self.tz_backend = get_tz_backend(self.use_pytz) self.default_tzs = [self.tz_backend.to_tzobj(v) for v in self.tz_backend.base_tzstrs] if "choices" in kwargs: values, displays = zip(*kwargs["choices"]) # Choices can be specified in two forms: either # [, ] or [, ] # # The [, ] format is the one we actually # store the choices in memory because of # https://github.com/mfogel/django-timezone-field/issues/24 # # The [, ] format is supported because since django # can't deconstruct pytz.timezone objects, migration files must # use an alternate format. Representing the timezones as strings # is the obvious choice. if not self.tz_backend.is_tzobj(values[0]): # using force_str b/c of https://github.com/mfogel/django-timezone-field/issues/38 values = [self.tz_backend.to_tzobj(force_str(v)) for v in values] else: values = self.default_tzs displays = None self.choices_display = kwargs.pop("choices_display", None) if self.choices_display == "WITH_GMT_OFFSET": choices = with_gmt_offset(values, use_pytz=self.use_pytz) elif self.choices_display == "STANDARD": choices = standard(values) elif self.choices_display is None: choices = zip(values, displays) if displays else standard(values) else: raise ValueError(f"Unrecognized value for kwarg 'choices_display' of '{self.choices_display}'") kwargs["choices"] = choices super().__init__(*args, **kwargs) def validate(self, value, model_instance): if not self.tz_backend.is_tzobj(value): raise ValidationError(f"'{value}' is not a pytz timezone object") super().validate(value, model_instance) def deconstruct(self): name, path, args, kwargs = super().deconstruct() if kwargs.get("max_length") == self.default_max_length: del kwargs["max_length"] if self.use_pytz is not None: kwargs["use_pytz"] = self.use_pytz if self.choices_display is not None: kwargs["choices_display"] = self.choices_display # don't assume super().deconstruct() will pass us back our kwargs["choices"] # https://github.com/mfogel/django-timezone-field/issues/96 if "choices" in kwargs: if self.choices_display is None: if kwargs["choices"] == standard(self.default_tzs): kwargs.pop("choices") else: values, _ = zip(*kwargs["choices"]) if sorted(values, key=str) == sorted(self.default_tzs, key=str): kwargs.pop("choices") else: kwargs["choices"] = [(value, "") for value in values] # django can't decontruct pytz objects, so transform choices # to [, ] format for writing out to the migration if "choices" in kwargs: kwargs["choices"] = [(str(tz), n) for tz, n in kwargs["choices"]] return name, path, args, kwargs def get_internal_type(self): return "CharField" def get_default(self): # allow defaults to be still specified as strings. Allows for easy # serialization into migration files value = super().get_default() return self._get_python_and_db_repr(value)[0] def from_db_value(self, value, *_args): "Convert to pytz timezone object" return self._get_python_and_db_repr(value)[0] def to_python(self, value): "Convert to pytz timezone object" return self._get_python_and_db_repr(value)[0] def get_prep_value(self, value): "Convert to string describing a valid pytz timezone object" return self._get_python_and_db_repr(value)[1] def _get_python_and_db_repr(self, value): "Returns a tuple of (python representation, db representation)" if value is None or value == "": return (None, "") if self.tz_backend.is_tzobj(value): return (value, str(value)) try: return (self.tz_backend.to_tzobj(force_str(value)), force_str(value)) except TimeZoneNotFoundError as err: raise ValidationError(f"Invalid timezone '{value}'") from err django-timezone-field-6.1.0/timezone_field/forms.py000066400000000000000000000027671453050106100223750ustar00rootroot00000000000000from django import forms from django.core.exceptions import ValidationError from timezone_field.backends import TimeZoneNotFoundError, get_tz_backend from timezone_field.choices import standard, with_gmt_offset def get_coerce(tz_backend): def coerce(val): try: return tz_backend.to_tzobj(val) except TimeZoneNotFoundError as err: raise ValidationError(f"Unknown time zone: '{val}'") from err return coerce class TimeZoneFormField(forms.TypedChoiceField): def __init__(self, *args, **kwargs): self.use_pytz = kwargs.pop("use_pytz", None) self.tz_backend = get_tz_backend(self.use_pytz) kwargs.setdefault("coerce", get_coerce(self.tz_backend)) kwargs.setdefault("empty_value", None) if "choices" in kwargs: values, displays = zip(*kwargs["choices"]) else: values = self.tz_backend.base_tzstrs displays = None choices_display = kwargs.pop("choices_display", None) if choices_display == "WITH_GMT_OFFSET": choices = with_gmt_offset(values, use_pytz=self.use_pytz) elif choices_display == "STANDARD": choices = standard(values) elif choices_display is None: choices = zip(values, displays) if displays else standard(values) else: raise ValueError(f"Unrecognized value for kwarg 'choices_display' of '{choices_display}'") kwargs["choices"] = choices super().__init__(*args, **kwargs) django-timezone-field-6.1.0/timezone_field/models.py000066400000000000000000000000331453050106100225120ustar00rootroot00000000000000# intentionally left blank django-timezone-field-6.1.0/timezone_field/rest_framework.py000066400000000000000000000014741453050106100242730ustar00rootroot00000000000000from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ from rest_framework.fields import Field from timezone_field.backends import TimeZoneNotFoundError, get_tz_backend class TimeZoneSerializerField(Field): default_error_messages = { "invalid": _("A valid timezone is required."), } def __init__(self, *args, **kwargs): self.use_pytz = kwargs.pop("use_pytz", None) self.tz_backend = get_tz_backend(use_pytz=self.use_pytz) super().__init__(*args, **kwargs) def to_internal_value(self, data): data_str = force_str(data) try: return self.tz_backend.to_tzobj(data_str) except TimeZoneNotFoundError: self.fail("invalid") def to_representation(self, value): return str(value)