├── .flake8 ├── .github └── workflows │ ├── test-mysql.yml │ ├── test-pg.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── django_hashids ├── __init__.py ├── exceptions.py └── field.py ├── poetry.lock ├── pyproject.toml └── tests ├── __init__.py ├── settings.py ├── test_app ├── __init__.py └── models.py ├── test_django_hashids.py └── urls.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203, E266, E501, W503 3 | max-line-length = 80 4 | max-complexity = 18 5 | select = B,C,E,F,W,T4,B9 6 | exclude = tests/* -------------------------------------------------------------------------------- /.github/workflows/test-mysql.yml: -------------------------------------------------------------------------------- 1 | name: test-mysql 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test-mysql: 13 | runs-on: ubuntu-20.04 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python-version: [3.12] 18 | django-version: [4.2.9, 5.0.1] 19 | include: 20 | - python-version: "3.9" 21 | django-version: "2.2.13" 22 | - python-version: "3.9" 23 | django-version: "3.2.4" 24 | 25 | env: 26 | MYSQL_PASSWORD: mysql 27 | MYSQL_USER: mysql 28 | MYSQL_DATABASE: test_db 29 | MYSQL_HOST: 127.0.0.1 30 | TEST_WITH_MYSQL: true 31 | 32 | services: 33 | mysql: 34 | image: mysql 35 | ports: ["3306:3306"] 36 | env: 37 | MYSQL_PASSWORD: mysql 38 | MYSQL_ROOT_PASSWORD: mysql 39 | MYSQL_USER: mysql 40 | MYSQL_DATABASE: test_db 41 | options: >- 42 | --health-cmd "mysqladmin ping" 43 | --health-interval 10s 44 | --health-timeout 10s 45 | --health-retries 5 46 | 47 | steps: 48 | - uses: actions/checkout@v1 49 | - name: Set up Python ${{ matrix.python-version }} 50 | uses: actions/setup-python@v2 51 | with: 52 | python-version: ${{ matrix.python-version }} 53 | - name: Install tooling 54 | run: | 55 | python -mpip install poetry codecov 56 | - name: Install dependencies 57 | run: | 58 | poetry install --with testmysql,dev 59 | - name: Setup Django ${{ matrix.django-version }} 60 | run: | 61 | poetry run pip install django==${{ matrix.django-version }} 62 | - name: Test with pytest 63 | run: | 64 | poetry run py.test --cov=./django_hashids/ --no-migrations 65 | - name: Upload coverage to codecov 66 | run: | 67 | codecov 68 | env: 69 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 70 | -------------------------------------------------------------------------------- /.github/workflows/test-pg.yml: -------------------------------------------------------------------------------- 1 | name: test-pg 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test-pg: 13 | runs-on: ubuntu-20.04 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python-version: [3.12] 18 | django-version: [4.2.9, 5.0.1] 19 | include: 20 | - python-version: "3.9" 21 | django-version: "2.2.13" 22 | - python-version: "3.9" 23 | django-version: "3.2.4" 24 | 25 | env: 26 | POSTGRES_PASSWORD: postgres 27 | POSTGRES_USER: postgres 28 | POSTGRES_DB: postgres 29 | POSTGRES_HOST: 127.0.0.1 30 | TEST_WITH_PG: true 31 | 32 | services: 33 | postgres: 34 | image: postgres 35 | ports: ['5432:5432'] 36 | env: 37 | POSTGRES_PASSWORD: postgres 38 | POSTGRES_USER: postgres 39 | POSTGRES_DB: postgres 40 | options: >- 41 | --health-cmd pg_isready 42 | --health-interval 10s 43 | --health-timeout 5s 44 | --health-retries 5 45 | 46 | steps: 47 | - uses: actions/checkout@v1 48 | - name: Set up Python ${{ matrix.python-version }} 49 | uses: actions/setup-python@v2 50 | with: 51 | python-version: ${{ matrix.python-version }} 52 | - name: Install tooling 53 | run: | 54 | python -mpip install poetry codecov 55 | - name: Install dependencies 56 | run: | 57 | poetry install --with testpg,dev 58 | - name: Setup Django ${{ matrix.django-version }} 59 | run: | 60 | poetry run pip install django==${{ matrix.django-version }} 61 | - name: Test with pytest 62 | run: | 63 | poetry run py.test --cov=./django_hashids/ --no-migrations 64 | - name: Upload coverage to codecov 65 | run: | 66 | codecov 67 | env: 68 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 69 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-20.04 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python-version: ["3.12"] 18 | django-version: ["4.2.9", "5.0.1"] 19 | include: 20 | - python-version: "3.8" 21 | django-version: "1.11.28" 22 | - python-version: "3.9" 23 | django-version: "2.2.13" 24 | - python-version: "3.9" 25 | django-version: "3.2.4" 26 | steps: 27 | - uses: actions/checkout@v1 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install tooling 33 | run: | 34 | python -mpip install poetry codecov; python -mpip install six 35 | - name: Install dependencies 36 | run: | 37 | poetry install --with dev 38 | - name: Setup Django ${{ matrix.django-version }} 39 | run: | 40 | poetry run pip install django==${{ matrix.django-version }} 41 | - name: Lint with flake 42 | run: | 43 | poetry run flake8 django_hashids 44 | - name: Test with pytest 45 | run: | 46 | poetry run py.test --cov=./django_hashids/ 47 | - name: Upload coverage to codecov 48 | run: | 49 | codecov 50 | env: 51 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,django 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode,django 3 | 4 | ### Django ### 5 | *.log 6 | *.pot 7 | *.pyc 8 | __pycache__/ 9 | local_settings.py 10 | db.sqlite3 11 | db.sqlite3-journal 12 | media 13 | 14 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 15 | # in your Git repository. Update and uncomment the following line accordingly. 16 | # /staticfiles/ 17 | 18 | ### Django.Python Stack ### 19 | # Byte-compiled / optimized / DLL files 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | pip-wheel-metadata/ 41 | share/python-wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | *.py,cover 68 | .hypothesis/ 69 | .pytest_cache/ 70 | 71 | # Translations 72 | *.mo 73 | 74 | # Django stuff: 75 | 76 | # Flask stuff: 77 | instance/ 78 | .webassets-cache 79 | 80 | # Scrapy stuff: 81 | .scrapy 82 | 83 | # Sphinx documentation 84 | docs/_build/ 85 | 86 | # PyBuilder 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 107 | __pypackages__/ 108 | 109 | # Celery stuff 110 | celerybeat-schedule 111 | celerybeat.pid 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # pytype static type analyzer 144 | .pytype/ 145 | 146 | ### Python ### 147 | # Byte-compiled / optimized / DLL files 148 | 149 | # C extensions 150 | 151 | # Distribution / packaging 152 | 153 | # PyInstaller 154 | # Usually these files are written by a python script from a template 155 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 156 | 157 | # Installer logs 158 | 159 | # Unit test / coverage reports 160 | 161 | # Translations 162 | 163 | # Django stuff: 164 | 165 | # Flask stuff: 166 | 167 | # Scrapy stuff: 168 | 169 | # Sphinx documentation 170 | 171 | # PyBuilder 172 | 173 | # Jupyter Notebook 174 | 175 | # IPython 176 | 177 | # pyenv 178 | 179 | # pipenv 180 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 181 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 182 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 183 | # install all needed dependencies. 184 | 185 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 186 | 187 | # Celery stuff 188 | 189 | # SageMath parsed files 190 | 191 | # Environments 192 | 193 | # Spyder project settings 194 | 195 | # Rope project settings 196 | 197 | # mkdocs documentation 198 | 199 | # mypy 200 | 201 | # Pyre type checker 202 | 203 | # pytype static type analyzer 204 | 205 | ### VisualStudioCode ### 206 | .vscode 207 | *.code-workspace 208 | 209 | ### VisualStudioCode Patch ### 210 | # Ignore all local history of files 211 | .history 212 | 213 | # End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,django 214 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shen Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Hashids 2 | [![Github Actions](https://github.com/ericls/django-hashids/workflows/test/badge.svg)](https://github.com/ericls/django-hashids/actions) 3 | [![Code Coverage](https://codecov.io/gh/ericls/django-hashids/branch/master/graph/badge.svg)](https://codecov.io/gh/ericls/django-hashids) 4 | [![Python Version](https://img.shields.io/pypi/pyversions/django-hashids.svg)](https://pypi.org/project/django-hashids/) 5 | [![PyPI Package](https://img.shields.io/pypi/v/django-hashids.svg)](https://pypi.org/project/django-hashids/) 6 | [![License](https://img.shields.io/pypi/l/django-hashids.svg)](https://github.com/ericls/django-hashids/blob/master/LICENSE) 7 | 8 | django-hashids is a simple and non-intrusive hashids library for Django. It acts as a model field, but it does not touch the database or change the model. 9 | 10 | # Features 11 | - Proxy the internal model `pk` field without storing the value in the database. 12 | - Allows lookups and filtering by hashid string. 13 | - Can be used as sort key 14 | - Allows specifying a salt, min_length and alphabet globally 15 | - Supports custom salt, min_length, and alphabet per field 16 | - Supports Django REST Framework Serializers 17 | - Supports exact ID searches in Django Admin when field is specified in search_fields. 18 | - Supports common filtering lookups, such as __iexact, __contains, __icontains, though matching is the same as __exact. 19 | - Supports other lookups: isnull, gt, gte, lt and lte. 20 | 21 | # Install 22 | 23 | ```bash 24 | pip install django-hashids 25 | ``` 26 | 27 | `django-hashids` is tested with Django 1.11, 2.2, 3.0, 3.1, 3.2, 4.0 and python 3.6, 3.7, 3.8, 3.9, 3.10. 28 | 29 | # Usage 30 | 31 | Add `HashidsField` to any model 32 | 33 | ```python 34 | from django_hashids import HashidsField 35 | 36 | class TestModel(Model): 37 | hashid = HashidsField(real_field_name="id") 38 | ``` 39 | 40 | `TestModel.hashid` field will proxy `TestModel.id` field but all queries will return and receive hashids strings. `TestModel.id` will work as before. 41 | 42 | ## Examples 43 | 44 | ```python 45 | instance = TestModel.objects.create() 46 | instance2 = TestModel.objects.create() 47 | instance.id # 1 48 | instance2.id # 2 49 | 50 | # Allows access to the field 51 | instance.hashid # '1Z' 52 | instance2.hashid # '4x' 53 | 54 | # Allows querying by the field 55 | TestModel.objects.get(hashid="1Z") 56 | TestModel.objects.filter(hashid="1Z") 57 | TestModel.objects.filter(hashid__in=["1Z", "4x"]) 58 | TestModel.objects.filter(hashid__gt="1Z") # same as id__gt=1, would return instance 2 59 | 60 | # Allows usage in queryset.values 61 | TestModel.objects.values_list("hashid", flat=True) # ["1Z", "4x"] 62 | TestModel.objects.filter(hashid__in=TestModel.objects.values("hashid")) 63 | 64 | ``` 65 | 66 | ## Using with URLs 67 | 68 | You can use hashids to identify items in your URLs by treating them as slugs. 69 | 70 | In `urls.py`: 71 | 72 | ```python 73 | urlpatterns = [ 74 | path("item//", YourDetailView.as_view(), name="item-detail"), 75 | ] 76 | ``` 77 | 78 | And in your view: 79 | 80 | ```python 81 | class YourDetailView(DetailView): 82 | model = Item 83 | slug_field = 'hashid' 84 | ``` 85 | 86 | ## Config 87 | 88 | The folloing attributes can be added in settings file to set default arguments of `HashidsField`: 89 | 1. `DJANGO_HASHIDS_SALT`: default salt 90 | 2. `DJANGO_HASHIDS_MIN_LENGTH`: default minimum length 91 | 3. `DJANGO_HASHIDS_ALPHABET`: default alphabet 92 | 93 | `HashidsField` does not reqiure any arguments but the followinig arguments can be supplied to modify its behavior. 94 | 95 | | Name | Description | 96 | | ------------------ | :-------------------------------------------------------: | 97 | | `real_field_name` | The proxied field name | 98 | | `hashids_instance` | The hashids instance used to encode/decode for this field | 99 | | `salt` | The salt used for this field to generate hashids | 100 | | `min_length` | The minimum length of hashids generated for this field | 101 | | `alphabet` | The alphabet used by this field to generate hashids | 102 | 103 | The argument `hashids_instance` is mutually exclusive to `salt`, `min_length` and `alphabet`. See [hashids-python](https://github.com/davidaurelio/hashids-python) for more info about the arguments. 104 | 105 | Some common Model arguments such as `verbose_name` are also supported. 106 | -------------------------------------------------------------------------------- /django_hashids/__init__.py: -------------------------------------------------------------------------------- 1 | from .field import HashidsField 2 | 3 | __all__ = ["HashidsField"] 4 | -------------------------------------------------------------------------------- /django_hashids/exceptions.py: -------------------------------------------------------------------------------- 1 | class ConfigError(Exception): 2 | pass 3 | 4 | 5 | class RealFieldDoesNotExistError(ConfigError): 6 | pass 7 | -------------------------------------------------------------------------------- /django_hashids/field.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import FieldError 3 | from django.db.models import Field 4 | from django.utils.functional import cached_property 5 | from hashids import Hashids 6 | 7 | from .exceptions import ConfigError, RealFieldDoesNotExistError 8 | 9 | 10 | class HashidsField(Field): 11 | concrete = False 12 | allowed_lookups = ("exact", "iexact", "in", "gt", "gte", "lt", "lte", "isnull") 13 | # these should never change, even when Hashids updates 14 | ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" 15 | MIN_LENGTH = 0 16 | 17 | def __init__( 18 | self, 19 | real_field_name="id", 20 | *args, 21 | hashids_instance=None, 22 | salt=None, 23 | alphabet=None, 24 | min_length=None, 25 | **kwargs 26 | ): 27 | kwargs.pop("editable", None) 28 | super().__init__(*args, editable=False, **kwargs) 29 | self.real_field_name = real_field_name 30 | self.salt = salt 31 | self.min_length = min_length 32 | self.alphabet = alphabet 33 | self._explicit_hashids_instance = hashids_instance 34 | 35 | self.hashids_instance = None 36 | self.attached_to_model = None 37 | 38 | def contribute_to_class(self, cls, name): 39 | self.attname = name 40 | self.name = name 41 | 42 | if getattr(self, "model", None) is None and cls._meta.abstract is False: 43 | self.model = cls 44 | 45 | if self.attached_to_model is not None: # pragma: no cover 46 | raise FieldError( 47 | "Field '%s' is already attached to another model(%s)." 48 | % (self.name, self.attached_to_model) 49 | ) 50 | self.attached_to_model = cls 51 | 52 | self.column = None 53 | 54 | if self.verbose_name is None: 55 | self.verbose_name = self.name 56 | 57 | setattr(cls, name, self) 58 | 59 | cls._meta.add_field(self, private=True) 60 | 61 | self.hashids_instance = self.get_hashid_instance() 62 | 63 | def get_hashid_instance(self): 64 | if self._explicit_hashids_instance: 65 | if ( 66 | self.salt is not None 67 | or self.alphabet is not None 68 | or self.min_length is not None 69 | ): 70 | raise ConfigError( 71 | "if hashids_instance is set, salt, min_length and alphabet should not be set" 72 | ) 73 | return self._explicit_hashids_instance 74 | salt = self.salt 75 | min_length = self.min_length 76 | alphabet = self.alphabet 77 | if salt is None: 78 | salt = getattr(settings, "DJANGO_HASHIDS_SALT") 79 | if min_length is None: 80 | min_length = ( 81 | getattr(settings, "DJANGO_HASHIDS_MIN_LENGTH", None) or self.MIN_LENGTH 82 | ) 83 | if alphabet is None: 84 | alphabet = ( 85 | getattr(settings, "DJANGO_HASHIDS_ALPHABET", None) or self.ALPHABET 86 | ) 87 | return Hashids(salt=salt, min_length=min_length, alphabet=alphabet) 88 | 89 | def get_prep_value(self, value): 90 | decoded_values = self.hashids_instance.decode(value) 91 | if not decoded_values: 92 | return None 93 | return decoded_values[0] 94 | 95 | def from_db_value(self, value, expression, connection, *args): 96 | return self.hashids_instance.encode(value) 97 | 98 | def get_col(self, alias, output_field=None): 99 | if output_field is None: 100 | output_field = self 101 | col = self.real_col.get_col(alias, output_field) 102 | return col 103 | 104 | @cached_property 105 | def real_col(self): 106 | # `maybe_field` is intended for `pk`, which does not appear in `_meta.fields` 107 | maybe_field = getattr(self.attached_to_model._meta, self.real_field_name, None) 108 | if isinstance(maybe_field, Field): 109 | return maybe_field 110 | try: 111 | field = next( 112 | col 113 | for col in self.attached_to_model._meta.fields 114 | if col.name == self.real_field_name 115 | or col.attname == self.real_field_name 116 | ) 117 | except StopIteration: 118 | raise RealFieldDoesNotExistError( 119 | "%s(%s) can't find real field using real_field_name: %s" 120 | % (self.__class__.__name__, self, self.real_field_name) 121 | ) 122 | return field 123 | 124 | def __get__(self, instance, name=None): 125 | if not instance: 126 | return self 127 | real_value = getattr(instance, self.real_field_name, None) 128 | # the instance is not saved yet? 129 | if real_value is None: 130 | return "" 131 | assert isinstance(real_value, int) 132 | return self.hashids_instance.encode(real_value) 133 | 134 | def __set__(self, instance, value): 135 | pass 136 | 137 | def __deepcopy__(self, memo=None): 138 | new_instance = super().__deepcopy__(memo) 139 | for attr in ("hashids_instance", "attached_to_model"): 140 | if hasattr(new_instance, attr): 141 | setattr(new_instance, attr, None) 142 | # remove cached values from cached_property 143 | for key in ("real_col",): 144 | if key in new_instance.__dict__: 145 | del new_instance.__dict__[key] # pragma: no cover 146 | return new_instance 147 | 148 | @classmethod 149 | def get_lookups(cls): 150 | all_lookups = super().get_lookups() 151 | return {k: all_lookups[k] for k in cls.allowed_lookups} 152 | 153 | 154 | HashidField = HashidsField 155 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "asgiref" 5 | version = "3.4.1" 6 | description = "ASGI specs, helper code, and adapters" 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, 11 | {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, 12 | ] 13 | 14 | [package.dependencies] 15 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 16 | 17 | [package.extras] 18 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] 19 | 20 | [[package]] 21 | name = "atomicwrites" 22 | version = "1.4.1" 23 | description = "Atomic file writes." 24 | optional = false 25 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 26 | files = [ 27 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 28 | ] 29 | 30 | [[package]] 31 | name = "attrs" 32 | version = "22.2.0" 33 | description = "Classes Without Boilerplate" 34 | optional = false 35 | python-versions = ">=3.6" 36 | files = [ 37 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 38 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 39 | ] 40 | 41 | [package.extras] 42 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 43 | dev = ["attrs[docs,tests]"] 44 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 45 | tests = ["attrs[tests-no-zope]", "zope.interface"] 46 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 47 | 48 | [[package]] 49 | name = "black" 50 | version = "22.8.0" 51 | description = "The uncompromising code formatter." 52 | optional = false 53 | python-versions = ">=3.6.2" 54 | files = [ 55 | {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, 56 | {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, 57 | {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, 58 | {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, 59 | {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, 60 | {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, 61 | {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, 62 | {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, 63 | {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, 64 | {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, 65 | {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, 66 | {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, 67 | {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, 68 | {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, 69 | {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, 70 | {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, 71 | {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, 72 | {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, 73 | {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, 74 | {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, 75 | {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, 76 | {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, 77 | {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, 78 | ] 79 | 80 | [package.dependencies] 81 | click = ">=8.0.0" 82 | dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} 83 | mypy-extensions = ">=0.4.3" 84 | pathspec = ">=0.9.0" 85 | platformdirs = ">=2" 86 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} 87 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} 88 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 89 | 90 | [package.extras] 91 | colorama = ["colorama (>=0.4.3)"] 92 | d = ["aiohttp (>=3.7.4)"] 93 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 94 | uvloop = ["uvloop (>=0.15.2)"] 95 | 96 | [[package]] 97 | name = "click" 98 | version = "8.0.4" 99 | description = "Composable command line interface toolkit" 100 | optional = false 101 | python-versions = ">=3.6" 102 | files = [ 103 | {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, 104 | {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, 105 | ] 106 | 107 | [package.dependencies] 108 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 109 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 110 | 111 | [[package]] 112 | name = "colorama" 113 | version = "0.4.5" 114 | description = "Cross-platform colored terminal text." 115 | optional = false 116 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 117 | files = [ 118 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 119 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 120 | ] 121 | 122 | [[package]] 123 | name = "coverage" 124 | version = "6.2" 125 | description = "Code coverage measurement for Python" 126 | optional = false 127 | python-versions = ">=3.6" 128 | files = [ 129 | {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, 130 | {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, 131 | {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, 132 | {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, 133 | {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, 134 | {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, 135 | {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, 136 | {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, 137 | {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, 138 | {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, 139 | {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, 140 | {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, 141 | {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, 142 | {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, 143 | {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, 144 | {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, 145 | {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, 146 | {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, 147 | {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, 148 | {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, 149 | {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, 150 | {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, 151 | {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, 152 | {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, 153 | {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, 154 | {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, 155 | {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, 156 | {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, 157 | {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, 158 | {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, 159 | {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, 160 | {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, 161 | {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, 162 | {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, 163 | {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, 164 | {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, 165 | {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, 166 | {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, 167 | {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, 168 | {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, 169 | {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, 170 | {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, 171 | {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, 172 | {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, 173 | {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, 174 | {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, 175 | {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, 176 | ] 177 | 178 | [package.dependencies] 179 | tomli = {version = "*", optional = true, markers = "extra == \"toml\""} 180 | 181 | [package.extras] 182 | toml = ["tomli"] 183 | 184 | [[package]] 185 | name = "dataclasses" 186 | version = "0.8" 187 | description = "A backport of the dataclasses module for Python 3.6" 188 | optional = false 189 | python-versions = ">=3.6, <3.7" 190 | files = [ 191 | {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, 192 | {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, 193 | ] 194 | 195 | [[package]] 196 | name = "django" 197 | version = "3.2.23" 198 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 199 | optional = false 200 | python-versions = ">=3.6" 201 | files = [ 202 | {file = "Django-3.2.23-py3-none-any.whl", hash = "sha256:d48608d5f62f2c1e260986835db089fa3b79d6f58510881d316b8d88345ae6e1"}, 203 | {file = "Django-3.2.23.tar.gz", hash = "sha256:82968f3640e29ef4a773af2c28448f5f7a08d001c6ac05b32d02aeee6509508b"}, 204 | ] 205 | 206 | [package.dependencies] 207 | asgiref = ">=3.3.2,<4" 208 | pytz = "*" 209 | sqlparse = ">=0.2.2" 210 | 211 | [package.extras] 212 | argon2 = ["argon2-cffi (>=19.1.0)"] 213 | bcrypt = ["bcrypt"] 214 | 215 | [[package]] 216 | name = "flake8" 217 | version = "7.0.0" 218 | description = "the modular source code checker: pep8 pyflakes and co" 219 | optional = false 220 | python-versions = ">=3.8.1" 221 | files = [ 222 | {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, 223 | {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, 224 | ] 225 | 226 | [package.dependencies] 227 | mccabe = ">=0.7.0,<0.8.0" 228 | pycodestyle = ">=2.11.0,<2.12.0" 229 | pyflakes = ">=3.2.0,<3.3.0" 230 | 231 | [[package]] 232 | name = "hashids" 233 | version = "1.3.1" 234 | description = "Implements the hashids algorithm in python. For more information, visit http://hashids.org/" 235 | optional = false 236 | python-versions = ">=2.7" 237 | files = [ 238 | {file = "hashids-1.3.1-py2.py3-none-any.whl", hash = "sha256:8bddd1acba501bfc9306e7e5a99a1667f4f2cacdc20cbd70bcc5ddfa5147c94c"}, 239 | {file = "hashids-1.3.1.tar.gz", hash = "sha256:6c3dc775e65efc2ce2c157a65acb776d634cb814598f406469abef00ae3f635c"}, 240 | ] 241 | 242 | [package.extras] 243 | test = ["pytest (>=2.1.0)"] 244 | 245 | [[package]] 246 | name = "importlib-metadata" 247 | version = "4.8.3" 248 | description = "Read metadata from Python packages" 249 | optional = false 250 | python-versions = ">=3.6" 251 | files = [ 252 | {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, 253 | {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, 254 | ] 255 | 256 | [package.dependencies] 257 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 258 | zipp = ">=0.5" 259 | 260 | [package.extras] 261 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 262 | perf = ["ipython"] 263 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"] 264 | 265 | [[package]] 266 | name = "iniconfig" 267 | version = "1.1.1" 268 | description = "iniconfig: brain-dead simple config-ini parsing" 269 | optional = false 270 | python-versions = "*" 271 | files = [ 272 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 273 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 274 | ] 275 | 276 | [[package]] 277 | name = "isort" 278 | version = "4.3.21" 279 | description = "A Python utility / library to sort Python imports." 280 | optional = false 281 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 282 | files = [ 283 | {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, 284 | {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, 285 | ] 286 | 287 | [package.extras] 288 | pipfile = ["pipreqs", "requirementslib"] 289 | pyproject = ["toml"] 290 | requirements = ["pip-api", "pipreqs"] 291 | xdg-home = ["appdirs (>=1.4.0)"] 292 | 293 | [[package]] 294 | name = "mccabe" 295 | version = "0.7.0" 296 | description = "McCabe checker, plugin for flake8" 297 | optional = false 298 | python-versions = ">=3.6" 299 | files = [ 300 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 301 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 302 | ] 303 | 304 | [[package]] 305 | name = "mypy-extensions" 306 | version = "1.0.0" 307 | description = "Type system extensions for programs checked with the mypy type checker." 308 | optional = false 309 | python-versions = ">=3.5" 310 | files = [ 311 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 312 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 313 | ] 314 | 315 | [[package]] 316 | name = "mysqlclient" 317 | version = "2.2.3" 318 | description = "Python interface to MySQL" 319 | optional = false 320 | python-versions = ">=3.8" 321 | files = [ 322 | {file = "mysqlclient-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:5a5451b3eea2a2a3316b2b432c89f25b1a28b986aa924a04aca659ad454e9a5f"}, 323 | {file = "mysqlclient-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:2fa388cf076d3fee010d7094ca979fc8236988159c762becfea4d42cd56e6580"}, 324 | {file = "mysqlclient-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:c37a7f641fa2e0582bf6808851dc4b82736b61ccb39e1607e59dce797db3f6c5"}, 325 | {file = "mysqlclient-2.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:c79740385d9df70606e87dade197c5fce5c0d22c0e5c40cd048cfa693daa0e7b"}, 326 | {file = "mysqlclient-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:7d74de2fa08dc7483b5ec82e130fde6d965f53c9ac6bf678f6d6c362c952b8b6"}, 327 | {file = "mysqlclient-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90164463c0bda46ebe9f8ca2b7ec502ff915ab1c23af54bdf60997fc4c59e47c"}, 328 | {file = "mysqlclient-2.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b34574bceb548ac94a31c8cc1f67d454f414e5dee240dd29ad0e09405756638"}, 329 | {file = "mysqlclient-2.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a47d266820fb8da26582cddc98ded1546edc7a0def556b0ca8de4a1a7dd8505c"}, 330 | {file = "mysqlclient-2.2.3.tar.gz", hash = "sha256:ee51656e36fc5a92920b807ee8b9e373e3b0e267c89cdc95d73b1dbe46863631"}, 331 | ] 332 | 333 | [[package]] 334 | name = "packaging" 335 | version = "21.3" 336 | description = "Core utilities for Python packages" 337 | optional = false 338 | python-versions = ">=3.6" 339 | files = [ 340 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 341 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 342 | ] 343 | 344 | [package.dependencies] 345 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 346 | 347 | [[package]] 348 | name = "pathspec" 349 | version = "0.9.0" 350 | description = "Utility library for gitignore style pattern matching of file paths." 351 | optional = false 352 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 353 | files = [ 354 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 355 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 356 | ] 357 | 358 | [[package]] 359 | name = "platformdirs" 360 | version = "2.4.0" 361 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 362 | optional = false 363 | python-versions = ">=3.6" 364 | files = [ 365 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, 366 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, 367 | ] 368 | 369 | [package.extras] 370 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 371 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 372 | 373 | [[package]] 374 | name = "pluggy" 375 | version = "1.0.0" 376 | description = "plugin and hook calling mechanisms for python" 377 | optional = false 378 | python-versions = ">=3.6" 379 | files = [ 380 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 381 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 382 | ] 383 | 384 | [package.dependencies] 385 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 386 | 387 | [package.extras] 388 | dev = ["pre-commit", "tox"] 389 | testing = ["pytest", "pytest-benchmark"] 390 | 391 | [[package]] 392 | name = "psycopg2-binary" 393 | version = "2.9.9" 394 | description = "psycopg2 - Python-PostgreSQL Database Adapter" 395 | optional = false 396 | python-versions = ">=3.7" 397 | files = [ 398 | {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, 399 | {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, 400 | {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, 401 | {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, 402 | {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, 403 | {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, 404 | {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, 405 | {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, 406 | {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, 407 | {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, 408 | {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, 409 | {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, 410 | {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, 411 | {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, 412 | {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, 413 | {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, 414 | {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, 415 | {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, 416 | {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, 417 | {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, 418 | {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, 419 | {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, 420 | {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, 421 | {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, 422 | {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, 423 | {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, 424 | {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, 425 | {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, 426 | {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, 427 | {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, 428 | {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, 429 | {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, 430 | {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, 431 | {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, 432 | {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, 433 | {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, 434 | {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, 435 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, 436 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, 437 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, 438 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, 439 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, 440 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, 441 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, 442 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, 443 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, 444 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, 445 | {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, 446 | {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, 447 | {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, 448 | {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, 449 | {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, 450 | {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, 451 | {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, 452 | {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, 453 | {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, 454 | {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, 455 | {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, 456 | {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, 457 | {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, 458 | {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, 459 | {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, 460 | {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, 461 | {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, 462 | {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, 463 | {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, 464 | {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, 465 | {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, 466 | {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, 467 | {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, 468 | {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, 469 | {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, 470 | ] 471 | 472 | [[package]] 473 | name = "py" 474 | version = "1.11.0" 475 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 476 | optional = false 477 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 478 | files = [ 479 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 480 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 481 | ] 482 | 483 | [[package]] 484 | name = "pycodestyle" 485 | version = "2.11.1" 486 | description = "Python style guide checker" 487 | optional = false 488 | python-versions = ">=3.8" 489 | files = [ 490 | {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, 491 | {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, 492 | ] 493 | 494 | [[package]] 495 | name = "pyflakes" 496 | version = "3.2.0" 497 | description = "passive checker of Python programs" 498 | optional = false 499 | python-versions = ">=3.8" 500 | files = [ 501 | {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, 502 | {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, 503 | ] 504 | 505 | [[package]] 506 | name = "pyparsing" 507 | version = "3.0.7" 508 | description = "Python parsing module" 509 | optional = false 510 | python-versions = ">=3.6" 511 | files = [ 512 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 513 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 514 | ] 515 | 516 | [package.extras] 517 | diagrams = ["jinja2", "railroad-diagrams"] 518 | 519 | [[package]] 520 | name = "pytest" 521 | version = "6.2.5" 522 | description = "pytest: simple powerful testing with Python" 523 | optional = false 524 | python-versions = ">=3.6" 525 | files = [ 526 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 527 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 528 | ] 529 | 530 | [package.dependencies] 531 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 532 | attrs = ">=19.2.0" 533 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 534 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 535 | iniconfig = "*" 536 | packaging = "*" 537 | pluggy = ">=0.12,<2.0" 538 | py = ">=1.8.2" 539 | toml = "*" 540 | 541 | [package.extras] 542 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 543 | 544 | [[package]] 545 | name = "pytest-cov" 546 | version = "3.0.0" 547 | description = "Pytest plugin for measuring coverage." 548 | optional = false 549 | python-versions = ">=3.6" 550 | files = [ 551 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 552 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 553 | ] 554 | 555 | [package.dependencies] 556 | coverage = {version = ">=5.2.1", extras = ["toml"]} 557 | pytest = ">=4.6" 558 | 559 | [package.extras] 560 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 561 | 562 | [[package]] 563 | name = "pytest-django" 564 | version = "4.5.2" 565 | description = "A Django plugin for pytest." 566 | optional = false 567 | python-versions = ">=3.5" 568 | files = [ 569 | {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, 570 | {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, 571 | ] 572 | 573 | [package.dependencies] 574 | pytest = ">=5.4.0" 575 | 576 | [package.extras] 577 | docs = ["sphinx", "sphinx-rtd-theme"] 578 | testing = ["Django", "django-configurations (>=2.0)"] 579 | 580 | [[package]] 581 | name = "pytz" 582 | version = "2024.1" 583 | description = "World timezone definitions, modern and historical" 584 | optional = false 585 | python-versions = "*" 586 | files = [ 587 | {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, 588 | {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, 589 | ] 590 | 591 | [[package]] 592 | name = "sqlparse" 593 | version = "0.4.4" 594 | description = "A non-validating SQL parser." 595 | optional = false 596 | python-versions = ">=3.5" 597 | files = [ 598 | {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, 599 | {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, 600 | ] 601 | 602 | [package.extras] 603 | dev = ["build", "flake8"] 604 | doc = ["sphinx"] 605 | test = ["pytest", "pytest-cov"] 606 | 607 | [[package]] 608 | name = "toml" 609 | version = "0.10.2" 610 | description = "Python Library for Tom's Obvious, Minimal Language" 611 | optional = false 612 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 613 | files = [ 614 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 615 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 616 | ] 617 | 618 | [[package]] 619 | name = "tomli" 620 | version = "1.2.3" 621 | description = "A lil' TOML parser" 622 | optional = false 623 | python-versions = ">=3.6" 624 | files = [ 625 | {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, 626 | {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, 627 | ] 628 | 629 | [[package]] 630 | name = "typed-ast" 631 | version = "1.5.5" 632 | description = "a fork of Python 2 and 3 ast modules with type comment support" 633 | optional = false 634 | python-versions = ">=3.6" 635 | files = [ 636 | {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, 637 | {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, 638 | {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, 639 | {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, 640 | {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, 641 | {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, 642 | {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, 643 | {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, 644 | {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, 645 | {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, 646 | {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, 647 | {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, 648 | {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, 649 | {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, 650 | {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, 651 | {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, 652 | {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, 653 | {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, 654 | {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, 655 | {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, 656 | {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, 657 | {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, 658 | {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, 659 | {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, 660 | {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, 661 | {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, 662 | {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, 663 | {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, 664 | {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, 665 | {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, 666 | {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, 667 | {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, 668 | {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, 669 | {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, 670 | {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, 671 | {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, 672 | {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, 673 | {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, 674 | {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, 675 | {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, 676 | {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, 677 | ] 678 | 679 | [[package]] 680 | name = "typing-extensions" 681 | version = "4.1.1" 682 | description = "Backported and Experimental Type Hints for Python 3.6+" 683 | optional = false 684 | python-versions = ">=3.6" 685 | files = [ 686 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 687 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 688 | ] 689 | 690 | [[package]] 691 | name = "zipp" 692 | version = "3.6.0" 693 | description = "Backport of pathlib-compatible object wrapper for zip files" 694 | optional = false 695 | python-versions = ">=3.6" 696 | files = [ 697 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, 698 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, 699 | ] 700 | 701 | [package.extras] 702 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 703 | testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] 704 | 705 | [metadata] 706 | lock-version = "2.0" 707 | python-versions = ">=3.6.2,<4" 708 | content-hash = "567d29d785d08e807e54e3fba2e9b5a9de00a4b21a9c19cb6ce3fac638aae90e" 709 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "django-hashids" 3 | version = "0.7.0" 4 | readme = "README.md" 5 | description = "Non-intrusive hashids library for Django" 6 | homepage = "https://github.com/ericls/django-hashids" 7 | repository = "https://github.com/ericls/django-hashids" 8 | keywords = ["django", "hashids", "hashid"] 9 | authors = ["Shen Li "] 10 | license = "MIT" 11 | 12 | [tool.poetry.dependencies] 13 | python = ">=3.6.2,<4" 14 | hashids = ">=1.0.2" 15 | 16 | [tool.poetry.group.dev] 17 | optional = true 18 | 19 | [tool.poetry.group.dev.dependencies] 20 | python = ">=3.8.1,<4" 21 | django = "^3.0.7" 22 | pytest = "^6.2.5" 23 | black = "^22.0.3" 24 | pytest-django = "^4.5.2" 25 | pytest-cov = "^3.0.0" 26 | flake8 = { version = "^7.0.0", python = ">=3.8.1" } 27 | isort = "^4.3.21" 28 | 29 | [tool.poetry.group.testpg] 30 | optional = true 31 | 32 | [tool.poetry.group.testpg.dependencies] 33 | psycopg2-binary = { version = "^2.8.6", python = ">=3.9" } 34 | 35 | [tool.poetry.group.testmysql] 36 | optional = true 37 | 38 | [tool.poetry.group.testmysql.dependencies] 39 | mysqlclient = { version = "^2.2.3", python = ">=3.8" } 40 | 41 | [build-system] 42 | requires = ["poetry>=1.0.9"] 43 | build-backend = "poetry.core.masonry.api" 44 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/django-hashids/7f00d95a1372a4527ae98c821033521f5d2a70fd/tests/__init__.py -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SECRET_KEY = "1" 4 | DEBUG = True 5 | INSTALLED_APPS = [ 6 | "django.contrib.contenttypes", 7 | "django.contrib.auth", 8 | "tests.test_app", 9 | ] 10 | MIDDLEWARE = [] 11 | ROOT_URLCONF = "tests.urls" 12 | DJANGO_HASHIDS_SALT = "???!" 13 | DATABASES = { 14 | "default": { 15 | "ENGINE": "django.db.backends.sqlite3", 16 | "NAME": ":memory:", 17 | } 18 | } 19 | if os.environ.get("TEST_WITH_PG"): 20 | DATABASES = { 21 | "default": { 22 | "ENGINE": "django.db.backends.postgresql", 23 | "NAME": os.environ["POSTGRES_DB"], 24 | "USER": os.environ["POSTGRES_USER"], 25 | "PASSWORD": os.environ["POSTGRES_PASSWORD"], 26 | "HOST": os.environ["POSTGRES_HOST"], 27 | "PORT": "5432", 28 | } 29 | } 30 | elif os.environ.get("TEST_WITH_MYSQL"): 31 | DATABASES = { 32 | "default": { 33 | "ENGINE": "django.db.backends.mysql", 34 | "NAME": os.environ["MYSQL_DATABASE"], 35 | "USER": os.environ["MYSQL_USER"], 36 | "PASSWORD": os.environ["MYSQL_PASSWORD"], 37 | "HOST": os.environ["MYSQL_HOST"], 38 | "PORT": "3306", 39 | "TEST": { 40 | # the main database is also the test database 41 | "NAME": os.environ["MYSQL_DATABASE"], 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/django-hashids/7f00d95a1372a4527ae98c821033521f5d2a70fd/tests/test_app/__init__.py -------------------------------------------------------------------------------- /tests/test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from django.db.models import Model 4 | from hashids import Hashids 5 | 6 | from django_hashids import HashidsField 7 | 8 | 9 | class TestModel(Model): 10 | hashid = HashidsField(real_field_name="id") 11 | 12 | 13 | class TestModelWithDifferentConfig(Model): 14 | hashid = HashidsField(salt="AAA", min_length=5, alphabet="OPQRST1234567890") 15 | 16 | 17 | this_hashids_instance = Hashids(salt="FOO") 18 | 19 | 20 | class TestModelWithOwnInstance(Model): 21 | hashid = HashidsField(hashids_instance=this_hashids_instance) 22 | 23 | 24 | class TestUser(AbstractUser): 25 | hashid = HashidsField(real_field_name="id") 26 | 27 | 28 | class TestUserRelated(Model): 29 | hashid = HashidsField(real_field_name="id") 30 | 31 | user = models.ForeignKey("TestUser", related_name="related", on_delete=models.CASCADE) 32 | 33 | 34 | class FirstSubClass(TestModel): 35 | pass 36 | 37 | 38 | class SecondSubClass(FirstSubClass): 39 | pass 40 | 41 | 42 | class TestAbstractModel(models.Model): 43 | hashid = HashidsField(real_field_name="id") 44 | 45 | class Meta: 46 | abstract = True 47 | 48 | 49 | class ModelA(TestAbstractModel): 50 | pass 51 | 52 | 53 | class ModelB(ModelA): 54 | pass 55 | 56 | 57 | class ModelUsingPKAsRealFieldName(Model): 58 | hashid = HashidsField(real_field_name="pk") 59 | -------------------------------------------------------------------------------- /tests/test_django_hashids.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from django import setup 5 | from django.db.models import ExpressionWrapper, F, IntegerField 6 | from django.test import override_settings 7 | from hashids import Hashids 8 | 9 | from django_hashids.exceptions import ConfigError, RealFieldDoesNotExistError 10 | 11 | os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings" 12 | setup() 13 | 14 | pytestmark = pytest.mark.django_db 15 | 16 | 17 | def test_can_get_hashids(): 18 | from django.conf import settings 19 | from tests.test_app.models import TestModel 20 | 21 | instance = TestModel.objects.create() 22 | hashid = instance.hashid 23 | hashids_instance = Hashids(salt=settings.DJANGO_HASHIDS_SALT) 24 | assert hashids_instance.decode(hashid)[0] == instance.pk 25 | 26 | 27 | def test_can_get_field_from_model(): 28 | from tests.test_app.models import TestModel 29 | 30 | TestModel.hashid 31 | 32 | 33 | def test_can_use_per_field_config(): 34 | from tests.test_app.models import TestModelWithDifferentConfig 35 | 36 | instance = TestModelWithDifferentConfig.objects.create() 37 | hashid = instance.hashid 38 | hashids_instance = Hashids(salt="AAA", min_length=5, alphabet="OPQRST1234567890") 39 | assert hashids_instance.decode(hashid)[0] == instance.pk 40 | 41 | 42 | def test_can_use_per_field_instance(): 43 | from tests.test_app.models import TestModelWithOwnInstance, this_hashids_instance 44 | 45 | instance = TestModelWithOwnInstance.objects.create() 46 | assert this_hashids_instance.decode(instance.hashid)[0] == instance.pk 47 | 48 | 49 | def test_throws_when_setting_both_instance_and_config(): 50 | from django.db.models import Model 51 | from tests.test_app.models import this_hashids_instance 52 | from django_hashids import HashidsField 53 | 54 | with pytest.raises(ConfigError): 55 | 56 | class Foo(Model): 57 | class Meta: 58 | app_label = "tests.test_app" 59 | 60 | hash_id = HashidsField( 61 | salt="Anotherone", hashids_instance=this_hashids_instance 62 | ) 63 | 64 | 65 | def test_updates_when_changing_real_column_value(): 66 | from django.conf import settings 67 | from tests.test_app.models import TestModel 68 | 69 | instance = TestModel.objects.create() 70 | instance.id = 3 71 | # works before saving 72 | hashids_instance = Hashids(salt=settings.DJANGO_HASHIDS_SALT) 73 | assert hashids_instance.decode(instance.hashid)[0] == 3 74 | # works after saving 75 | instance.save() 76 | hashids_instance = Hashids(salt=settings.DJANGO_HASHIDS_SALT) 77 | assert hashids_instance.decode(instance.hashid)[0] == 3 78 | 79 | 80 | def test_ignores_changes_to_value(): 81 | from django.conf import settings 82 | from tests.test_app.models import TestModel 83 | 84 | instance = TestModel.objects.create() 85 | instance.id = 3 86 | instance.hashid = "FOO" 87 | 88 | hashids_instance = Hashids(salt=settings.DJANGO_HASHIDS_SALT) 89 | assert hashids_instance.decode(instance.hashid)[0] == 3 90 | # works after saving 91 | instance.save() 92 | 93 | instance.hashid = "FOO" 94 | hashids_instance = Hashids(salt=settings.DJANGO_HASHIDS_SALT) 95 | assert hashids_instance.decode(instance.hashid)[0] == 3 96 | 97 | 98 | def test_can_use_exact_lookup(): 99 | from tests.test_app.models import TestModel 100 | 101 | instance = TestModel.objects.create() 102 | got_instance = TestModel.objects.filter(hashid=instance.hashid).first() 103 | assert instance == got_instance 104 | # assert id field still works 105 | got_instance = TestModel.objects.filter(id=instance.id).first() 106 | assert instance == got_instance 107 | 108 | 109 | def test_can_use_in_lookup(): 110 | from tests.test_app.models import TestModel 111 | 112 | instance = TestModel.objects.create() 113 | instance2 = TestModel.objects.create() 114 | hashids = [instance.hashid, instance2.hashid] 115 | qs = TestModel.objects.filter(hashid__in=hashids) 116 | assert set([instance, instance2]) == set(qs) 117 | 118 | 119 | def test_can_use_lookup_when_value_does_not_exists(): 120 | # https://github.com/ericls/django-hashids/issues/4 121 | from tests.test_app.models import TestModel 122 | 123 | # exact lookup 124 | instance = TestModel.objects.create() 125 | hashid = instance.hashid + "A" 126 | qs = TestModel.objects.filter(hashid=hashid) 127 | assert list(qs) == [] 128 | 129 | # lookup 130 | instance = TestModel.objects.create() 131 | instance2 = TestModel.objects.create() 132 | hashids = [instance.hashid + "A", instance2.hashid + "A"] 133 | qs = TestModel.objects.filter(hashid__in=hashids) 134 | assert list(qs) == [] 135 | 136 | 137 | def test_can_use_lt_gt_lte_gte_lookup(): 138 | from tests.test_app.models import TestModel 139 | 140 | instance = TestModel.objects.create() 141 | instance2 = TestModel.objects.create() 142 | qs = TestModel.objects.filter(hashid__lt=instance2.hashid) 143 | assert set([instance]) == set(qs) 144 | qs = TestModel.objects.filter(hashid__lte=instance2.hashid) 145 | assert set([instance, instance2]) == set(qs) 146 | qs = TestModel.objects.filter(hashid__gt=instance.hashid) 147 | assert set([instance2]) == set(qs) 148 | qs = TestModel.objects.filter(hashid__gte=instance.hashid) 149 | assert set([instance, instance2]) == set(qs) 150 | 151 | 152 | def test_can_get_values(): 153 | from tests.test_app.models import TestModel 154 | 155 | instance = TestModel.objects.create() 156 | instance2 = TestModel.objects.create() 157 | 158 | hashids = TestModel.objects.values("hashid") 159 | assert set([instance, instance2]) == set( 160 | TestModel.objects.filter(hashid__in=hashids) 161 | ) 162 | hashids = list(TestModel.objects.values_list("hashid", flat=True)) 163 | assert set([instance, instance2]) == set( 164 | TestModel.objects.filter(hashid__in=hashids) 165 | ) 166 | # assert id field still works 167 | ids = list(TestModel.objects.values_list("id", flat=True)) 168 | assert set([instance, instance2]) == set(TestModel.objects.filter(id__in=ids)) 169 | 170 | 171 | def test_can_select_as_integer(): 172 | from tests.test_app.models import TestModel 173 | 174 | instance = TestModel.objects.create() 175 | instance2 = TestModel.objects.create() 176 | 177 | integer_ids = list( 178 | TestModel.objects.annotate( 179 | hid=ExpressionWrapper(F("hashid"), output_field=IntegerField()) 180 | ).values_list("hid", flat=True) 181 | ) 182 | assert set([instance.id, instance2.id]) == set(integer_ids) 183 | 184 | 185 | @override_settings(DJANGO_HASHIDS_MIN_LENGTH=10) 186 | def test_can_use_min_length_from_settings(): 187 | from tests.test_app.models import TestModel 188 | 189 | TestModel.hashid.hashids_instance = None 190 | TestModel.hashid.hashids_instance = TestModel.hashid.get_hashid_instance() 191 | 192 | instance = TestModel.objects.create() 193 | assert len(instance.hashid) >= 10 194 | 195 | 196 | @override_settings(DJANGO_HASHIDS_ALPHABET='!@#$%^&*(){}[]:"') 197 | def test_can_use_min_length_from_settings(): 198 | from tests.test_app.models import TestModel 199 | 200 | TestModel.hashid.hashids_instance = None 201 | TestModel.hashid.hashids_instance = TestModel.hashid.get_hashid_instance() 202 | 203 | instance = TestModel.objects.create() 204 | assert all(c in '!@#$%^&*(){}[]:"' for c in instance.hashid) 205 | 206 | 207 | def test_not_saved_instance(): 208 | from tests.test_app.models import TestModel 209 | 210 | instance = TestModel() 211 | assert instance.hashid == "" 212 | 213 | 214 | def test_create_user(): 215 | # https://github.com/ericls/django-hashids/issues/2 216 | from tests.test_app.models import TestUser 217 | 218 | u = TestUser.objects.create_user("username", password="password") 219 | assert TestUser.hashid.hashids_instance.decode(u.hashid)[0] == u.id 220 | 221 | 222 | def test_multiple_level_inheritance(): 223 | # https://github.com/ericls/django-hashids/issues/25 224 | from tests.test_app.models import SecondSubClass, FirstSubClass 225 | 226 | instance = SecondSubClass.objects.create() 227 | SecondSubClass.objects.filter(id=1).first() == SecondSubClass.objects.filter( 228 | hashid=instance.hashid 229 | ).first() 230 | 231 | instance = FirstSubClass.objects.create() 232 | FirstSubClass.objects.filter(id=1).first() == FirstSubClass.objects.filter( 233 | hashid=instance.hashid 234 | ).first() 235 | 236 | 237 | def test_multiple_level_inheritance_from_abstract_model(): 238 | # https://github.com/ericls/django-hashids/issues/25 239 | from tests.test_app.models import ModelB, ModelA 240 | 241 | instance = ModelB.objects.create() 242 | ModelB.objects.filter(id=1).first() == ModelB.objects.filter( 243 | hashid=instance.hashid 244 | ).first() 245 | 246 | instance = ModelA.objects.create() 247 | ModelA.objects.filter(id=1).first() == ModelA.objects.filter( 248 | hashid=instance.hashid 249 | ).first() 250 | 251 | 252 | def test_related_queries(): 253 | 254 | from tests.test_app.models import TestUser, TestUserRelated 255 | 256 | u = TestUser.objects.create() 257 | r = TestUserRelated.objects.create(user=u) 258 | 259 | assert TestUserRelated.objects.filter(user__hashid=u.hashid).first() == r 260 | assert TestUser.objects.filter(related__hashid=r.hashid).first() == u 261 | 262 | 263 | def test_using_pk_as_real_field_name(): 264 | # https://github.com/ericls/django-hashids/issues/31 265 | from tests.test_app.models import ModelUsingPKAsRealFieldName 266 | 267 | a = ModelUsingPKAsRealFieldName.objects.create() 268 | assert a.hashid 269 | assert ModelUsingPKAsRealFieldName.objects.get(hashid=a.hashid) == a 270 | assert ModelUsingPKAsRealFieldName.objects.get(hashid__lte=a.hashid) == a 271 | assert ( 272 | ModelUsingPKAsRealFieldName.objects.filter(hashid__lt=a.hashid).exists() 273 | is False 274 | ) 275 | 276 | 277 | def test_no_real_field_error_message(): 278 | from django.db.models import Model 279 | from django_hashids import HashidsField 280 | 281 | class Foo(Model): 282 | class Meta: 283 | app_label = "tests.test_app" 284 | 285 | hash_id = HashidsField(real_field_name="does_not_exist") 286 | 287 | with pytest.raises(RealFieldDoesNotExistError): 288 | Foo.objects.filter(hash_id="foo") 289 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | urlpatterns = [] 2 | --------------------------------------------------------------------------------