├── tests ├── __init__.py ├── test_django_scylla.py ├── urls.py ├── test_user_model.py └── settings.py ├── django_scylla ├── cql │ ├── __init__.py │ ├── queryset.py │ ├── expressions.py │ ├── where.py │ ├── query.py │ └── compiler.py ├── apps.py ├── validation.py ├── __init__.py ├── constraints.py ├── introspection.py ├── client.py ├── database.py ├── options.py ├── cursor.py ├── operations.py ├── models.py ├── creation.py ├── schema.py ├── base.py └── features.py ├── .gitattributes ├── .dockerignore ├── poetry.toml ├── pytest.ini ├── .github ├── FUNDING.yml └── workflows │ └── tox.yml ├── .isort.cfg ├── .editorconfig ├── demo ├── urls.py └── settings.py ├── manage.py ├── Dockerfile ├── .prettierrc ├── mypy.ini ├── CHANGELOG.md ├── docker-compose.yml ├── LICENSE.rst ├── .pre-commit-config.yaml ├── pyproject.toml ├── tox.ini ├── .flake8 ├── .gitignore ├── README.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_scylla/cql/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .tox 2 | .venv 3 | .github 4 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | create = true 3 | in-project = true 4 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = tests.settings 3 | 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [r4fek] 4 | -------------------------------------------------------------------------------- /tests/test_django_scylla.py: -------------------------------------------------------------------------------- 1 | def test_import() -> None: 2 | import django_scylla # noqa: F401 3 | 4 | assert "Empty test to ensure pytest passes." 5 | -------------------------------------------------------------------------------- /django_scylla/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DjangoScyllaAppConfig(AppConfig): 5 | name = "django_scylla" 6 | 7 | def ready(self): 8 | ... 9 | -------------------------------------------------------------------------------- /django_scylla/validation.py: -------------------------------------------------------------------------------- 1 | from django.db.backends.base.validation import BaseDatabaseValidation 2 | 3 | 4 | class DatabaseValidation(BaseDatabaseValidation): 5 | """Encapsulate backend-specific validation.""" 6 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | default_section=THIRDPARTY 3 | indent=' ' 4 | sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 5 | multi_line_output=3 6 | line_length=120 7 | include_trailing_comma=True 8 | use_parentheses=True 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yaml] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /demo/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path 3 | from django.views import debug 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = [ 8 | path("", debug.default_urlconf), 9 | path("admin/", admin.site.urls), 10 | ] 11 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path 3 | from django.views import debug 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = [ 8 | path("", debug.default_urlconf), 9 | path("admin/", admin.site.urls), 10 | ] 11 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /django_scylla/cql/queryset.py: -------------------------------------------------------------------------------- 1 | from django.db import NotSupportedError 2 | from django.db.models.query import QuerySet 3 | 4 | 5 | def patched_exclude(self, *args, **kwargs): 6 | raise NotSupportedError("Calling QuerySet.exclude() is not supported yet.") 7 | 8 | 9 | QuerySet.exclude = patched_exclude 10 | -------------------------------------------------------------------------------- /tests/test_user_model.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_my_user(): 9 | user = User.objects.create_user("john", "lennon@thebeatles.com", "johnpassword") 10 | assert len(User.objects.filter(username="john")) == 1 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | ENV PYTHONUNBUFFERED 1 3 | 4 | RUN apt-get update 5 | RUN apt-get install -y postgresql-client 6 | 7 | ENV POETRY_HOME=/opt/poetry 8 | ENV POETRY_VIRTUALENVS_IN_PROJECT=true 9 | ENV PATH="$POETRY_HOME/bin:$PATH" 10 | 11 | RUN curl -sSL https://install.python-poetry.org | python 12 | 13 | COPY . /app 14 | WORKDIR /app 15 | RUN poetry install 16 | -------------------------------------------------------------------------------- /django_scylla/__init__.py: -------------------------------------------------------------------------------- 1 | # monkey patch 2 | 3 | from django_scylla.cql.expressions import * # noqa: F401, F403 4 | from django_scylla.cql.query import * # noqa: F401, F403 5 | from django_scylla.cql.queryset import * # noqa: F401, F403 6 | from django_scylla.cql.where import * # noqa: F401, F403 7 | from django_scylla.models import * # noqa: F401, F403 8 | from django_scylla.options import * # noqa: F401, F403 9 | -------------------------------------------------------------------------------- /django_scylla/cql/expressions.py: -------------------------------------------------------------------------------- 1 | from django.db.models import expressions 2 | 3 | 4 | class Col(expressions.Col): 5 | def as_scylladb(self, compiler, connection): 6 | """Do not return alias, just column name""" 7 | return compiler.quote_name_unless_alias(self.target.column), [] 8 | 9 | 10 | # safe monkey patching: we just add as_scylladb methods here 11 | # so it won't affect other database backends 12 | expressions.Col = Col 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 4, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "proseWrap": "always", 11 | "endOfLine": "auto", 12 | "overrides": [ 13 | { 14 | "files": ["*.yml", "*.yaml"], 15 | "options": { 16 | "tabWidth": 2 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | check_untyped_defs=true 3 | disallow_incomplete_defs=true 4 | disallow_untyped_defs=true 5 | follow_imports=silent 6 | ignore_missing_imports=true 7 | no_implicit_optional=true 8 | strict_optional=true 9 | warn_redundant_casts=true 10 | warn_unreachable=true 11 | warn_unused_ignores=true 12 | 13 | # Disable mypy for migrations 14 | [mypy-*.migrations.*] 15 | ignore_errors=true 16 | 17 | # Disable mypy for settings 18 | [mypy-*.settings.*] 19 | ignore_errors=true 20 | 21 | ; # Disable mypy for tests 22 | ; [mypy-tests.*] 23 | ; ignore_errors=true 24 | -------------------------------------------------------------------------------- /django_scylla/constraints.py: -------------------------------------------------------------------------------- 1 | from django.db.models.constraints import BaseConstraint 2 | 3 | 4 | class PrimaryKeysConstraint(BaseConstraint): 5 | def __init__(self, fields=()): 6 | if not fields: 7 | raise ValueError("fields argument is missing") 8 | 9 | self.fields = tuple(fields) 10 | super().__init__(self.__class__.__name__) 11 | 12 | def constraint_sql(self, model, schema_editor): 13 | return schema_editor._create_primary_keys_sql(self.fields) 14 | 15 | def create_sql(self, model, schema_editor): 16 | return "" 17 | 18 | def remove_sql(self, model, schema_editor): 19 | return "" 20 | -------------------------------------------------------------------------------- /django_scylla/cql/where.py: -------------------------------------------------------------------------------- 1 | from django.db.models.sql import where 2 | 3 | 4 | class WhereNode(where.WhereNode): 5 | def as_scylladb(self, compiler, connection): 6 | res, params = super().as_sql(compiler, connection) 7 | # ScyllaDB does not support parentheses after WHERE 8 | if res and res[0] == "(" and res[-1] == ")": 9 | return res[1:-1], params 10 | return res, params 11 | 12 | 13 | class ExtraWhere(where.ExtraWhere): 14 | def as_sql(self, compiler=None, connection=None): 15 | sqls = ["%s" % sql for sql in self.sqls] 16 | return " AND ".join(sqls), list(self.params or ()) 17 | 18 | 19 | where.WhereNode = WhereNode 20 | -------------------------------------------------------------------------------- /django_scylla/introspection.py: -------------------------------------------------------------------------------- 1 | from django.db.backends.base.introspection import BaseDatabaseIntrospection, TableInfo 2 | 3 | 4 | class DatabaseIntrospection(BaseDatabaseIntrospection): 5 | """Encapsulate backend-specific introspection utilities.""" 6 | 7 | def get_table_list(self, cursor): 8 | """Return a list of table and Materialized Views names in the current database""" 9 | keyspace = cursor.keyspace 10 | 11 | return [ 12 | TableInfo(c[0], "t") 13 | for c in cursor.execute( 14 | "SELECT table_name FROM system_schema.tables WHERE keyspace_name=%s", 15 | params=(keyspace,), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /django_scylla/client.py: -------------------------------------------------------------------------------- 1 | import subprocess # nosec 2 | 3 | from django.db.backends.base.client import BaseDatabaseClient 4 | 5 | 6 | class DatabaseClient(BaseDatabaseClient): 7 | executable_name = "cqlsh" 8 | 9 | def runshell(self): 10 | settings_dict = self.connection.settings_dict 11 | args = [self.executable_name] 12 | if settings_dict["HOST"]: 13 | args.extend([settings_dict["HOST"].split(",")[0]]) 14 | if settings_dict["PORT"]: 15 | args.extend([str(settings_dict["PORT"])]) 16 | if settings_dict["USER"]: 17 | args += ["-u", settings_dict["USER"]] 18 | args += ["-k", settings_dict["NAME"]] 19 | 20 | subprocess.call(args) # nosec 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | - Add initial support for custom `Options` class (model `Meta` with scylla table options) 9 | - Fix disabling `INNER JOINS` in a smarter way (by disabling `select_related`) 10 | 11 | ## [0.0.9] - 2022-11-03 12 | ### Added 13 | - New `constraints.PrimaryKeysConstraint` model for filling `PRIMARY KEY` section of `CREATE TABLE..` statement. 14 | - Ability to set more than one primary key in model definition. 15 | - `CHANGELOG.md` 16 | 17 | [Unreleased]: https://github.com/r4fek/django-scylla/compare/compare/0.0.9...HEAD 18 | [0.0.9]: https://github.com/r4fek/django-scylla/compare/0.0.8...0.0.9 19 | -------------------------------------------------------------------------------- /django_scylla/database.py: -------------------------------------------------------------------------------- 1 | from logging import getLogger 2 | 3 | from cassandra.cluster import Cluster 4 | 5 | logger = getLogger(__name__) 6 | clients = {} 7 | 8 | 9 | def initialize(db, **kwargs): 10 | try: 11 | return clients[db] 12 | except KeyError: 13 | logger.debug("Initialize ScyllaDB connection") 14 | clients[db] = Cluster(**kwargs) 15 | return clients[db] 16 | 17 | 18 | class Error(Exception): # NOQA: StandardError undefined on PY3 19 | pass 20 | 21 | 22 | class InterfaceError(Error): 23 | pass 24 | 25 | 26 | class DatabaseError(Error): 27 | pass 28 | 29 | 30 | class DataError(DatabaseError): 31 | pass 32 | 33 | 34 | class OperationalError(DatabaseError): 35 | pass 36 | 37 | 38 | class IntegrityError(DatabaseError): 39 | pass 40 | 41 | 42 | class InternalError(DatabaseError): 43 | pass 44 | 45 | 46 | class ProgrammingError(DatabaseError): 47 | pass 48 | 49 | 50 | class NotSupportedError(DatabaseError): 51 | pass 52 | -------------------------------------------------------------------------------- /django_scylla/options.py: -------------------------------------------------------------------------------- 1 | import django.db.models.base 2 | import django.db.models.options 3 | from django.db.models.options import Options as DjangoOptions 4 | 5 | DEFAULT_SCYLLA_NAMES = ( 6 | "comment", 7 | "read_repair_chance", 8 | "dclocal_read_repair_chance", 9 | "speculative_retry", 10 | "gc_grace_seconds", 11 | "tombstone_gc", 12 | "bloom_filter_fp_chance", 13 | "default_time_to_live", 14 | "compaction", 15 | "compression", 16 | "caching", 17 | "cdc", 18 | ) 19 | 20 | 21 | class Options(DjangoOptions): 22 | def __init__(self, *args, **kwargs): 23 | """Add ScyllaDB table options from here: # https://docs.scylladb.com/stable/cql/ddl.html#other-table-options""" 24 | super().__init__(*args, **kwargs) 25 | for key in DEFAULT_SCYLLA_NAMES: 26 | setattr(self, key, None) 27 | 28 | 29 | # monkey patch Options 30 | django.db.models.base.Options = Options 31 | django.db.models.options.DEFAULT_NAMES = tuple( 32 | list(django.db.models.options.DEFAULT_NAMES) + list(DEFAULT_SCYLLA_NAMES) 33 | ) 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | demo: 6 | build: . 7 | command: poetry run python manage.py runserver 0.0.0.0:8000 8 | ports: 9 | - 8000:8000 10 | depends_on: 11 | - scylla 12 | volumes: 13 | - "./demo:/app/demo" 14 | - "./tests:/app/tests" 15 | - "./django_scylla:/app/django_scylla" 16 | healthcheck: 17 | test: curl -f "http://localhost:8000" 18 | interval: 30s 19 | timeout: 10s 20 | retries: 3 21 | start_period: 10s 22 | restart: unless-stopped 23 | 24 | demo-init: 25 | build: . 26 | depends_on: 27 | - demo 28 | restart: "no" 29 | entrypoint: [ "bash", "-c", "poetry run python manage.py migrate"] 30 | 31 | scylla: 32 | image: scylladb/scylla:5.1 33 | command: --smp 1 --memory 750M --overprovisioned 1 34 | ports: 35 | - "7000:7000" 36 | - "7001:7001" 37 | - "7199:7199" 38 | - "9042:9042" 39 | - "10000:10000" 40 | healthcheck: 41 | test: nodetool status | grep UN 42 | interval: 60s 43 | retries: 5 44 | start_period: 20s 45 | timeout: 10s 46 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright © 2022 Rafał Furmański 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | **The software is provided "as is", without warranty of any kind, express or 17 | implied, including but not limited to the warranties of merchantability, 18 | fitness for a particular purpose and noninfringement. In no event shall the 19 | authors or copyright holders be liable for any claim, damages or other 20 | liability, whether in an action of contract, tort or otherwise, arising from, 21 | out of or in connection with the software or the use or other dealings in the 22 | software.** 23 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # python import sorting - will amend files 3 | - repo: https://github.com/pre-commit/mirrors-isort 4 | rev: v5.10.1 5 | hooks: 6 | - id: isort 7 | 8 | # python code formatting - will amend files 9 | - repo: https://github.com/ambv/black 10 | rev: 22.10.0 11 | hooks: 12 | - id: black 13 | 14 | - repo: https://github.com/asottile/pyupgrade 15 | rev: v3.1.0 16 | hooks: 17 | - id: pyupgrade 18 | 19 | # Flake8 includes pyflakes, pycodestyle, mccabe, pydocstyle, bandit 20 | - repo: https://gitlab.com/pycqa/flake8 21 | rev: 5.0.4 22 | hooks: 23 | - id: flake8 24 | additional_dependencies: 25 | - flake8-bandit 26 | - flake8-blind-except 27 | - flake8-docstrings 28 | - flake8-logging-format 29 | - flake8-print 30 | 31 | # python static type checking 32 | - repo: https://github.com/pre-commit/mirrors-mypy 33 | rev: v0.982 34 | hooks: 35 | - id: mypy 36 | additional_dependencies: 37 | - types-geoip2 38 | args: 39 | - --disallow-untyped-defs 40 | - --disallow-incomplete-defs 41 | - --check-untyped-defs 42 | - --no-implicit-optional 43 | - --ignore-missing-imports 44 | - --follow-imports=silent 45 | exclude: ^tests 46 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "django-scylla" 3 | version = "0.0.10" 4 | description = "Django Scylla" 5 | authors = ["Rafał Furmański "] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/r4fek/django-scylla" 9 | repository = "https://github.com/r4fek/django-scylla" 10 | documentation = "https://django-scylla.readthedocs.io" 11 | classifiers = [ 12 | "Development Status :: 3 - Alpha", 13 | "Programming Language :: Python :: 3.8", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | ] 17 | packages = [{ include = "django_scylla" }] 18 | 19 | [tool.poetry.urls] 20 | Changelog = "https://github.com/r4fek/django-scylla/releases" 21 | 22 | [tool.poetry.dependencies] 23 | python = "^3.8" 24 | django = ">=3.1 <4.2" 25 | scylla-driver = "^3.25" 26 | 27 | [tool.poetry.dev-dependencies] 28 | black = {version = "*", allow-prereleases = true} 29 | coverage = "*" 30 | flake8 = "*" 31 | flake8-bandit = "*" 32 | flake8-blind-except = "*" 33 | flake8-docstrings = "*" 34 | flake8-logging-format = "*" 35 | flake8-print = "*" 36 | freezegun = "*" 37 | isort = "*" 38 | mypy = "*" 39 | pre-commit = "*" 40 | pytest = "*" 41 | pytest-cov = "*" 42 | pytest-django = "*" 43 | tox = "*" 44 | 45 | [build-system] 46 | requires = ["poetry>=1.1.12"] 47 | build-backend = "poetry.masonry.api" 48 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | isolated_build = True 3 | envlist = fmt, lint, checks, py{3.8,3.9,3.10}-django{31,32,40,41,main} 4 | 5 | [testenv] 6 | deps = 7 | coverage 8 | pytest 9 | pytest-cov 10 | pytest-django 11 | scylla-driver 12 | django31: Django>=3.1,<3.2 13 | django32: Django>=3.2,<3.3 14 | django40: Django>=4.0,<4.1 15 | django41: Django>=4.1,<4.2 16 | djangomain: https://github.com/django/django/archive/main.tar.gz 17 | 18 | commands = 19 | pytest --cov=django_scylla --verbose -s tests/ 20 | 21 | [testenv:checks] 22 | description = Django system checks and missing migrations 23 | deps = Django 24 | commands = 25 | python manage.py check --fail-level WARNING 26 | python manage.py makemigrations --dry-run --check --verbosity 3 27 | 28 | [testenv:fmt] 29 | description = Python source code formatting (isort, black) 30 | deps = 31 | isort 32 | black 33 | 34 | commands = 35 | isort --check-only django_scylla 36 | black --check django_scylla 37 | 38 | [testenv:lint] 39 | description = Python source code linting (flake8, bandit, pydocstyle) 40 | deps = 41 | flake8 42 | flake8-bandit 43 | flake8-blind-except 44 | flake8-docstrings 45 | flake8-logging-format 46 | flake8-print 47 | 48 | commands = 49 | flake8 django_scylla 50 | 51 | ; [testenv:mypy] 52 | ; description = Python source code type hints (mypy) 53 | ; deps = 54 | ; mypy 55 | ; types-requests 56 | ; types-python-dateutil 57 | ; types-simplejson 58 | 59 | ; commands = 60 | ; mypy django_scylla 61 | -------------------------------------------------------------------------------- /django_scylla/cursor.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from cassandra.cluster import ResultSet, Session 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class Cursor: 9 | def __init__(self, session: Session): 10 | logger.debug("CURSOR: Initialize Cursor") 11 | self.session = session 12 | self.result: ResultSet = None 13 | 14 | def __del__(self): 15 | logger.debug("CURSOR: shutdown session") 16 | self.session.shutdown() 17 | 18 | def close(self): 19 | ... 20 | 21 | def __iter__(self): 22 | return iter(self.result) 23 | 24 | @property 25 | def keyspace(self): 26 | return self.session.keyspace 27 | 28 | @property 29 | def rowcount(self): 30 | if self.result is None: 31 | raise RuntimeError 32 | 33 | # TODO: possibly not optimal 34 | return len(self.result.all()) 35 | 36 | def set_keyspace(self, name: str): 37 | return self.session.set_keyspace(name) 38 | 39 | def execute(self, query: str, parameters=None): 40 | if not query: 41 | return None 42 | logger.debug("QUERY %s, params %s", query, parameters) 43 | self.result = self.session.execute(query, parameters=parameters) 44 | return self.result 45 | 46 | def fetchmany(self, size=1): 47 | if size == 1: 48 | return self.fetchone() 49 | res = self.result.all()[:size] 50 | return res 51 | 52 | def fetchone(self): 53 | res = self.result.one() 54 | is_empty = len(res) == 1 and res[0] == 0 55 | return None if is_empty else res 56 | 57 | def fetchall(self): 58 | return self.result.all() 59 | 60 | @property 61 | def lastrowid(self): 62 | ... 63 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | name: Django Scylla 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | types: [opened, synchronize, reopened] 10 | 11 | jobs: 12 | format: 13 | name: Check formatting 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | toxenv: [fmt,lint] 18 | env: 19 | TOXENV: ${{ matrix.toxenv }} 20 | 21 | steps: 22 | - name: Check out the repository 23 | uses: actions/checkout@v3 24 | 25 | - name: Set up Python 3.10 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: '3.10' 29 | 30 | - name: Install and run tox 31 | run: | 32 | pip install tox 33 | tox 34 | test: 35 | name: Run tests 36 | runs-on: ubuntu-latest 37 | container: python:3.10 38 | strategy: 39 | matrix: 40 | python: ['3.10'] 41 | django: ['31','32','40','41','main'] 42 | 43 | env: 44 | TOXENV: py${{ matrix.python }}-django${{ matrix.django }} 45 | 46 | services: 47 | scylla: 48 | image: scylladb/scylla:latest 49 | env: 50 | SCYLLA_API_ADDR: "-a 0.0.0.0" 51 | SCYLLA_API_PORT: "-p 10000" 52 | SCYLLA_JMX_PORT: "-jp 7199" 53 | ports: 54 | - 7000:7000 55 | - 7001:7001 56 | - 7199:7199 57 | - 9042:9042 58 | - 10000:10000 59 | options: --health-cmd "cqlsh --debug" --health-interval 5s --health-retries 10 60 | 61 | steps: 62 | - name: Check out the repository 63 | uses: actions/checkout@v3 64 | 65 | # - name: Set up Python ${{ matrix.python }} 66 | # uses: actions/setup-python@v4 67 | # with: 68 | # python-version: ${{ matrix.python }} 69 | 70 | - name: Install and run tox 71 | run: | 72 | pip install tox 73 | tox 74 | -------------------------------------------------------------------------------- /django_scylla/cql/query.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.db.models import sql 4 | 5 | from django_scylla.cql.where import WhereNode 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Query(sql.query.Query): 11 | def __init__(self, *args, **kwargs): 12 | super().__init__(*args, **kwargs) 13 | self.select_related = False 14 | self.where = WhereNode() 15 | 16 | def setup_joins(self, *args, **kwargs): 17 | self.select_related = False 18 | return super().setup_joins(*args, **kwargs) 19 | 20 | def clear_where(self): 21 | self.where = WhereNode() 22 | 23 | def trim_start(self, names_with_path): 24 | self._lookup_joins = [] 25 | return super().trim_start(names_with_path) 26 | 27 | def join(self, join, *args, **kwargs): 28 | join.join_type = None 29 | self.select_related = False 30 | return super().join(join, *args, **kwargs) 31 | 32 | def add_ordering(self, *ordering): 33 | if len(ordering) > 1: 34 | ordering = [ordering[0]] 35 | return super().add_ordering(*ordering) 36 | 37 | def add_select_related(self, _): 38 | self.select_related = False 39 | 40 | def exists(self, using, limit=True): 41 | q = self.clone() 42 | if not q.distinct: 43 | if q.group_by is True: 44 | q.add_fields( 45 | (f.attname for f in self.model._meta.concrete_fields), False 46 | ) 47 | # Disable GROUP BY aliases to avoid orphaning references to the 48 | # SELECT clause which is about to be cleared. 49 | q.set_group_by(allow_aliases=False) 50 | q.clear_select_clause() 51 | q.clear_ordering(force=True) 52 | if limit: 53 | q.set_limits(high=1) 54 | q.add_extra({"a": "count(*)"}, None, None, None, None, None) 55 | q.set_extra_mask(None) 56 | return q 57 | 58 | 59 | sql.Query = Query 60 | -------------------------------------------------------------------------------- /django_scylla/operations.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | 3 | from django.db.backends.base.operations import BaseDatabaseOperations 4 | 5 | 6 | class DatabaseOperations(BaseDatabaseOperations): 7 | """ 8 | Encapsulate backend-specific differences, such as the way a backend 9 | performs ordering or calculates the ID of a recently-inserted row. 10 | """ 11 | 12 | compiler_module = "django_scylla.cql.compiler" 13 | 14 | def quote_name(self, name): 15 | """ 16 | Return a quoted version of the given table, index, or column name. Do 17 | not quote the given name if it's already been quoted. 18 | """ 19 | if name.startswith('"') and name.endswith('"'): 20 | return name 21 | return f'"{name}"' 22 | 23 | def prep_for_like_query(self, value): 24 | """Do no conversion, parent string-cast is SQL specific.""" 25 | return value 26 | 27 | def prep_for_iexact_query(self, value): 28 | """Do no conversion, parent string-cast is SQL specific.""" 29 | return value 30 | 31 | def adapt_datetimefield_value(self, value): 32 | """ 33 | Convert `datetime.datetime` object to the 64-bit signed integer 34 | representing a number of milliseconds since the standard base time. 35 | @see more: https://docs.scylladb.com/getting-started/types/#working-with-timestamps 36 | """ 37 | if value is None: 38 | return None 39 | ts = calendar.timegm(value.utctimetuple()) 40 | return str(int(ts * 1e3 + getattr(value, "microsecond", 0) / 1e3)) 41 | 42 | def bulk_insert_sql(self, fields, placeholder_rows): 43 | return " ".join("VALUES (%s)" % ", ".join(row) for row in placeholder_rows) 44 | 45 | def sql_flush(self, style, tables, *args, **kwargs): 46 | """ 47 | Truncate all existing tables in current keyspace. 48 | :returns: an empty list 49 | """ 50 | if tables: 51 | cql_list = [] 52 | 53 | for table in tables: 54 | cql_list.append(f"TRUNCATE {table}") 55 | return cql_list 56 | return [] 57 | 58 | def prepare_sql_script(self, sql): 59 | return [sql] 60 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | max-complexity = 8 4 | # http://flake8.pycqa.org/en/2.5.5/warnings.html#warning-error-codes 5 | ignore = 6 | ### pydocstyle - docstring conventions (PEP257) 7 | # blind except Exception: statement 8 | B902 9 | # too complex 10 | C901 11 | # Missing docstring in public module 12 | D100 13 | # Missing docstring in public class 14 | D101 15 | # Missing docstring in public method 16 | D102 17 | # Missing docstring in public function 18 | D103 19 | # Missing docstring in public package 20 | D104 21 | # Missing docstring in magic method 22 | D105 23 | # Missing docstring in public nested class 24 | D106 25 | # Missing docstring in __init__ 26 | D107 27 | # First word of the first line should be properly capitalized 28 | D403 29 | # No blank lines allowed between a section header and its content 30 | D412 31 | 32 | ### pycodestyle - style checker (PEP8) 33 | # line break before binary operator 34 | W503 35 | 36 | ### the following are ignored in CI using --extend-ignore option: 37 | # [pydocstyle] 1 blank line required between summary line and description 38 | D205 39 | # [pydocstyle] First line should end with a period 40 | D400 41 | # Consider possible security implications associated with the subprocess module. 42 | S404 43 | # subprocess call - check for execution of untrusted input. 44 | S603 45 | # [pydocstyle] First line should be in imperative mood 46 | ; D401 47 | # [bandit] Use of mark_safe() may expose cross-site scripting vulnerabilities and should be reviewed. 48 | ; S308 49 | # [bandit] Potential XSS on mark_safe function. 50 | ; S703 51 | DAR101 52 | DAR201 53 | # lowercase x imported as non lowercase 54 | N812 55 | 56 | per-file-ignores = 57 | ; D205 - 1 blank line required between summary line and description 58 | ; D400 - First line should end with a period 59 | ; D401 - First line should be in imperative mood 60 | ; S101 - use of assert 61 | ; S106 - hard-coded password 62 | ; E501 - line-length 63 | ; E731 - assigning a lambda to a variable 64 | *tests/*:D205,D400,D401,S101,S106,E501,E731 65 | */migrations/*:E501 66 | ; F403 - unable to detect undefined names 67 | ; F405 - may be undefined, or defined from star imports 68 | */settings.py:F403,F405 69 | */settings/*:F403,F405 70 | -------------------------------------------------------------------------------- /django_scylla/models.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import NON_FIELD_ERRORS 2 | from django.db import connection 3 | from django.db.models.base import Model 4 | 5 | 6 | def _perform_unique_checks(self, unique_checks): 7 | errors = {} 8 | 9 | for model_class, unique_check in unique_checks: 10 | # Try to look up an existing object with the same values as this 11 | # object's values for all the unique field. 12 | 13 | lookup_kwargs = {} 14 | for field_name in unique_check: 15 | f = self._meta.get_field(field_name) 16 | lookup_value = getattr(self, f.attname) 17 | # TODO: Handle multiple backends with different feature flags. 18 | if lookup_value is None or ( 19 | lookup_value == "" 20 | and connection.features.interprets_empty_strings_as_nulls 21 | ): 22 | # no value, skip the lookup 23 | continue 24 | if f.primary_key and not self._state.adding: 25 | # no need to check for unique primary key when editing 26 | continue 27 | lookup_kwargs[str(field_name)] = lookup_value 28 | 29 | # some fields were skipped, no reason to do the check 30 | if len(unique_check) != len(lookup_kwargs): 31 | continue 32 | 33 | qs = model_class._default_manager.filter(**lookup_kwargs) 34 | 35 | # Exclude the current object from the query if we are editing an 36 | # instance (as opposed to creating a new one) 37 | # Note that we need to use the pk as defined by model_class, not 38 | # self.pk. These can be different fields because model inheritance 39 | # allows single model to have effectively multiple primary keys. 40 | # Refs #17615. 41 | model_class_pk = self._get_pk_val(model_class._meta) 42 | if not self._state.adding and model_class_pk is not None: 43 | qs = [item for item in qs if item.pk != model_class_pk] 44 | if len(qs) > 0: 45 | if len(unique_check) == 1: 46 | key = unique_check[0] 47 | else: 48 | key = NON_FIELD_ERRORS 49 | errors.setdefault(key, []).append( 50 | self.unique_error_message(model_class, unique_check) 51 | ) 52 | 53 | return errors 54 | 55 | 56 | def _check_single_primary_key(): 57 | return [] 58 | 59 | 60 | Model._perform_unique_checks = _perform_unique_checks 61 | Model._check_single_primary_key = _check_single_primary_key 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | *$py.class 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | pip-wheel-metadata/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 94 | __pypackages__/ 95 | 96 | # Celery stuff 97 | celerybeat-schedule 98 | celerybeat.pid 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | .mypy_cache/ 131 | /.coverage 132 | /.coverage.* 133 | /.nox/ 134 | /.python-version 135 | /.pytype/ 136 | /dist/ 137 | /docs/_build/ 138 | /src/*.egg-info/ 139 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from cassandra import ConsistencyLevel 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = True 7 | USE_TZ = True 8 | 9 | INSTALLED_APPS = ( 10 | "django_scylla", 11 | "django.contrib.admin", 12 | "django.contrib.auth", 13 | "django.contrib.contenttypes", 14 | "django.contrib.sessions", 15 | "django.contrib.messages", 16 | "django.contrib.staticfiles", 17 | ) 18 | 19 | DATABASES = { 20 | "default": { 21 | "ENGINE": "django_scylla", 22 | "HOST": "scylla", 23 | "PORT": 9042, 24 | "NAME": "testdb", 25 | "OPTIONS": { 26 | "replication": { 27 | "class": "SimpleStrategy", 28 | "replication_factor": 1 29 | }, 30 | "connection": { 31 | "consistency_level": ConsistencyLevel.ONE 32 | }, 33 | "execution_profile": { 34 | "request_timeout": 5, 35 | } 36 | }, 37 | }, 38 | } 39 | 40 | MIDDLEWARE = [ 41 | # default django middleware 42 | "django.contrib.sessions.middleware.SessionMiddleware", 43 | "django.middleware.common.CommonMiddleware", 44 | "django.middleware.csrf.CsrfViewMiddleware", 45 | "django.contrib.auth.middleware.AuthenticationMiddleware", 46 | "django.contrib.messages.middleware.MessageMiddleware", 47 | ] 48 | 49 | PROJECT_DIR = path.abspath(path.join(path.dirname(__file__))) 50 | 51 | TEMPLATES = [ 52 | { 53 | "BACKEND": "django.template.backends.django.DjangoTemplates", 54 | "DIRS": [path.join(PROJECT_DIR, "templates")], 55 | "APP_DIRS": True, 56 | "OPTIONS": { 57 | "context_processors": [ 58 | "django.contrib.messages.context_processors.messages", 59 | "django.contrib.auth.context_processors.auth", 60 | "django.template.context_processors.request", 61 | ] 62 | }, 63 | } 64 | ] 65 | 66 | STATIC_URL = "/static/" 67 | 68 | SECRET_KEY = "secret" # noqa: S105 69 | 70 | LOGGING = { 71 | "version": 1, 72 | "disable_existing_loggers": False, 73 | "formatters": {"simple": {"format": "%(levelname)s %(message)s"}}, 74 | "handlers": { 75 | "console": { 76 | "level": "DEBUG", 77 | "class": "logging.StreamHandler", 78 | "formatter": "simple", 79 | } 80 | }, 81 | "loggers": { 82 | "": {"handlers": ["console"], "propagate": True, "level": "DEBUG"}, 83 | # 'django': { 84 | # 'handlers': ['console'], 85 | # 'propagate': True, 86 | # 'level': 'WARNING', 87 | # }, 88 | }, 89 | } 90 | 91 | ROOT_URLCONF = "tests.urls" 92 | 93 | if not DEBUG: 94 | raise Exception("This settings file can only be used with DEBUG=True") 95 | -------------------------------------------------------------------------------- /demo/settings.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from cassandra import ConsistencyLevel 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = True 7 | USE_TZ = True 8 | 9 | ALLOWED_HOSTS = [ "*" ] 10 | 11 | INSTALLED_APPS = ( 12 | "django_scylla", 13 | "django.contrib.admin", 14 | "django.contrib.auth", 15 | "django.contrib.contenttypes", 16 | "django.contrib.sessions", 17 | "django.contrib.messages", 18 | "django.contrib.staticfiles", 19 | ) 20 | 21 | DATABASES = { 22 | "default": { 23 | "ENGINE": "django_scylla", 24 | "HOST": "scylla", 25 | "PORT": 9042, 26 | "NAME": "demo", 27 | "OPTIONS": { 28 | "replication": { 29 | "class": "SimpleStrategy", 30 | "replication_factor": 1 31 | }, 32 | "connection": { 33 | "consistency_level": ConsistencyLevel.ONE 34 | }, 35 | "execution_profile": { 36 | "request_timeout": 5, 37 | } 38 | }, 39 | }, 40 | } 41 | 42 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 43 | 44 | MIDDLEWARE = [ 45 | # default django middleware 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "django.middleware.common.CommonMiddleware", 48 | "django.middleware.csrf.CsrfViewMiddleware", 49 | "django.contrib.auth.middleware.AuthenticationMiddleware", 50 | "django.contrib.messages.middleware.MessageMiddleware", 51 | ] 52 | 53 | PROJECT_DIR = path.abspath(path.join(path.dirname(__file__))) 54 | 55 | TEMPLATES = [ 56 | { 57 | "BACKEND": "django.template.backends.django.DjangoTemplates", 58 | "DIRS": [path.join(PROJECT_DIR, "templates")], 59 | "APP_DIRS": True, 60 | "OPTIONS": { 61 | "context_processors": [ 62 | "django.contrib.messages.context_processors.messages", 63 | "django.contrib.auth.context_processors.auth", 64 | "django.template.context_processors.request", 65 | ] 66 | }, 67 | } 68 | ] 69 | 70 | STATIC_URL = "/static/" 71 | 72 | SECRET_KEY = "secret" # noqa: S105 73 | 74 | LOGGING = { 75 | "version": 1, 76 | "disable_existing_loggers": False, 77 | "formatters": {"simple": {"format": "%(levelname)s %(message)s"}}, 78 | "handlers": { 79 | "console": { 80 | "level": "DEBUG", 81 | "class": "logging.StreamHandler", 82 | "formatter": "simple", 83 | } 84 | }, 85 | "loggers": { 86 | "": {"handlers": ["console"], "propagate": True, "level": "DEBUG"}, 87 | # 'django': { 88 | # 'handlers': ['console'], 89 | # 'propagate': True, 90 | # 'level': 'WARNING', 91 | # }, 92 | }, 93 | } 94 | 95 | ROOT_URLCONF = "demo.urls" 96 | if not DEBUG: 97 | raise Exception("This settings file can only be used with DEBUG=True") 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Scylla - the Cassandra & ScyllaDB backend for Django 2 | 3 | Django-scylla makes possible to connect your Django app to Cassandra or ScyllaDB and **use native Django ORM** as with any other relational database backend. 4 | 5 | 6 | [![Latest version](https://img.shields.io/pypi/v/django-scylla.svg "Latest version")](https://pypi.python.org/pypi/django-scylla/) 7 | ![workflow](https://github.com/r4fek/django-scylla/actions/workflows/tox.yml/badge.svg) 8 | 9 | Discord: https://discord.gg/pxunMGmDNc 10 | 11 | ## Sponsors ## 12 | Help support ongoing development and maintenance by [sponsoring Django Scylla](https://github.com/sponsors/r4fek). 13 | 14 | ## Installation ## 15 | 16 | Recommended installation: 17 | 18 | pip install django-scylla 19 | 20 | ## Basic Usage ## 21 | 22 | 1. Add `django_scylla` to `INSTALLED_APPS` in your `settings.py` file: 23 | 24 | INSTALLED_APPS = ('django_scylla',) + INSTALLED_APPS 25 | 26 | 2. Change `DATABASES` setting: 27 | 28 | DATABASES = { 29 | 'default': { 30 | 'ENGINE': 'django_scylla', 31 | 'NAME': 'db', 32 | 'TEST_NAME': 'test_db', 33 | 'HOST': 'db1.example.com,db2.example.com,db3.example.com', 34 | 'OPTIONS': { 35 | 'replication': { 36 | 'strategy_class': ..., 37 | 'replication_factor': ... 38 | }, 39 | 'execution_profile': { 40 | 'load_balancing_policy': ..., 41 | 'retry_policy': ..., 42 | 'consistency_level': ..., 43 | 'serial_consistency_level': ..., 44 | 'request_timeout': ..., 45 | 'speculative_execution_policy': ... 46 | } 47 | 'connection': { 48 | # Full list of connection options can be found here: https://docs.datastax.com/en/developer/python-driver/3.26/api/cassandra/cluster/ 49 | 'cql_version': ..., 50 | 'protocol_version': ... 51 | 'compression': ..., 52 | 'consistency': ..., 53 | 'lazy_connect': ..., 54 | 'retry_connect': ..., 55 | } 56 | } 57 | } 58 | } 59 | 60 | 3. Define some model: 61 | 62 | # myapp/models.py 63 | 64 | from django.db import models 65 | 66 | 67 | class Person(models.Model): 68 | first_name = models.CharField(max_length=30) 69 | last_name = models.CharField(max_length=30) 70 | 71 | 72 | 4. Connect to ScyllaDB and create a keyspace. 73 | 5. Run `./manage.py makemigrations && ./manage.py migrate` 74 | 6. Done! 75 | 76 | ## License ## 77 | Copyright (c) 2021-2022, [Rafał Furmański](https://linkedin.com/in/furmanski). 78 | 79 | All rights reserved. Licensed under MIT License. 80 | -------------------------------------------------------------------------------- /django_scylla/creation.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from django.db.backends.base.creation import BaseDatabaseCreation 4 | 5 | 6 | class DatabaseCreation(BaseDatabaseCreation): 7 | """ 8 | Encapsulate backend-specific differences pertaining to creation and 9 | destruction of the test database. 10 | """ 11 | 12 | def _execute_create_test_db(self, cursor, parameters, keepdb=False): 13 | cursor.execute( 14 | "CREATE KEYSPACE %(dbname)s WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}" 15 | % parameters, 16 | ) 17 | 18 | def _destroy_test_db(self, test_database_name, verbosity): 19 | with self._nodb_cursor() as cursor: 20 | cursor.execute( 21 | "DROP KEYSPACE %s" % self.connection.ops.quote_name(test_database_name) 22 | ) 23 | 24 | def _create_test_db(self, verbosity, autoclobber, keepdb=False): 25 | """Create the test db tables.""" 26 | test_database_name = self._get_test_db_name() 27 | test_db_params = { 28 | "dbname": self.connection.ops.quote_name(test_database_name), 29 | "suffix": self.sql_table_creation_suffix(), 30 | } 31 | # Create the test database and connect to it. 32 | with self._nodb_cursor() as cursor: 33 | try: 34 | self._execute_create_test_db(cursor, test_db_params, keepdb) 35 | except Exception as e: 36 | # if we want to keep the db, then no need to do any of the below, 37 | # just return and skip it all. 38 | if keepdb: 39 | return test_database_name 40 | 41 | self.log("Got an error creating the test database: %s" % e) 42 | if not autoclobber: 43 | confirm = input( 44 | "Type 'yes' if you would like to try deleting the test " 45 | "database '%s', or 'no' to cancel: " % test_database_name 46 | ) 47 | if autoclobber or confirm == "yes": 48 | try: 49 | if verbosity >= 1: 50 | self.log( 51 | "Destroying old test database for alias %s..." 52 | % ( 53 | self._get_database_display_str( 54 | verbosity, test_database_name 55 | ), 56 | ) 57 | ) 58 | cursor.execute("DROP KEYSPACE %(dbname)s" % test_db_params) 59 | self._execute_create_test_db(cursor, test_db_params, keepdb) 60 | except Exception as e: 61 | self.log("Got an error recreating the test database: %s" % e) 62 | sys.exit(2) 63 | else: 64 | self.log("Tests cancelled.") 65 | sys.exit(1) 66 | 67 | return test_database_name 68 | -------------------------------------------------------------------------------- /django_scylla/schema.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.db.backends.base.schema import BaseDatabaseSchemaEditor 4 | 5 | from django_scylla.constraints import PrimaryKeysConstraint 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): 11 | """ 12 | This class and its subclasses are responsible for emitting schema-changing 13 | statements to the databases - model creation/removal/alteration, field 14 | renaming, index fiddling, and so on. 15 | """ 16 | 17 | sql_create_column = "ALTER TABLE %(table)s ADD %(column)s %(definition)s" 18 | sql_create_table = "CREATE TABLE IF NOT EXISTS %(table)s (%(definition)s)" 19 | sql_create_index = "CREATE INDEX IF NOT EXISTS %(name)s ON %(table)s (%(columns)s)" 20 | sql_delete_column = "ALTER TABLE %(table)s DROP %(column)s" 21 | sql_create_primary_keys = "PRIMARY KEY %(columns)s" 22 | 23 | def skip_default(self, field): 24 | return False 25 | 26 | def skip_default_on_alter(self, field): 27 | return True 28 | 29 | def column_sql(self, model, field, include_default=False): 30 | """ 31 | Take a field and return its column definition. 32 | The field must already have had set_attributes_from_name() called. 33 | """ 34 | # Get the column's type and use that as the basis of the SQL 35 | db_params = field.db_parameters(connection=self.connection) 36 | sql = db_params["type"] 37 | params = [] 38 | # Check for fields that aren't actually columns (e.g. M2M) 39 | if sql is None: 40 | return None, None 41 | 42 | return sql, params 43 | 44 | def table_sql(self, model): 45 | """Add primary key definitions""" 46 | # check if PrimaryKeysConstraint is present in model._meta 47 | for constraint in model._meta.constraints: 48 | if isinstance(constraint, PrimaryKeysConstraint): 49 | return super().table_sql(model) 50 | 51 | # No manually added PrimaryKeysConstraint in meta. Creating then! 52 | pk_fields = [f.column for f in model._meta.fields if f.primary_key] 53 | model._meta.constraints.append(PrimaryKeysConstraint(fields=pk_fields)) 54 | sql, params = super().table_sql(model) 55 | logger.debug("table_sql %s, params %s", sql, params) 56 | return sql, params 57 | 58 | def _create_primary_keys_sql(self, fields): 59 | if len(fields) == 1: 60 | columns = f'("{fields[0]}")' 61 | else: 62 | columns = str(fields).replace("'", '"') 63 | return self.sql_create_primary_keys % {"columns": columns} 64 | 65 | def add_constraint(self, model, constraint): 66 | logger.debug("add_constraint %s", constraint) 67 | ... # TODO: fix it 68 | 69 | def alter_unique_together(self, model, old_unique_together, new_unique_together): 70 | ... 71 | 72 | def alter_index_together(self, model, old_index_together, new_index_together): 73 | return None 74 | 75 | def _create_unique_sql(self, *args, **kwargs): 76 | # TODO: fix it 77 | return "" 78 | 79 | def _create_index_name(self, table_name, column_names, suffix=""): 80 | return f"{table_name}_by_{'_'.join(column_names)}" 81 | 82 | def _alter_column_null_sql(self, model, old_field, new_field): 83 | return "" 84 | -------------------------------------------------------------------------------- /django_scylla/base.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from cassandra.auth import PlainTextAuthProvider 4 | from cassandra.cluster import EXEC_PROFILE_DEFAULT, ExecutionProfile, ProtocolVersion 5 | from cassandra.policies import RoundRobinPolicy, TokenAwarePolicy 6 | from cassandra.query import tuple_factory 7 | from django.db.backends.base.base import BaseDatabaseWrapper 8 | 9 | from . import database as Database 10 | from .client import DatabaseClient 11 | from .creation import DatabaseCreation 12 | from .cursor import Cursor 13 | from .features import DatabaseFeatures 14 | from .introspection import DatabaseIntrospection 15 | from .operations import DatabaseOperations 16 | from .schema import DatabaseSchemaEditor 17 | from .validation import DatabaseValidation 18 | 19 | 20 | class DatabaseWrapper(BaseDatabaseWrapper): 21 | """Represent a database connection.""" 22 | 23 | vendor = "scylladb" 24 | display_name = "ScyllaDB" 25 | DEFAULT_PROTOCOL_VERSION = ProtocolVersion.V4 26 | 27 | # Mapping of Field objects to their column types. 28 | data_types = { 29 | "AutoField": "bigint", 30 | "BigAutoField": "bigint", 31 | "BinaryField": "blob", 32 | "BooleanField": "boolean", 33 | "CharField": "text", 34 | "DateField": "date", 35 | "DateTimeField": "timestamp", 36 | "DecimalField": "decimal", 37 | "DurationField": "duration", 38 | "FileField": "text", 39 | "FilePathField": "text", 40 | "FloatField": "float", 41 | "ForeignKeyField": "bigint", 42 | "IntegerField": "bigint", 43 | "BigIntegerField": "bigint", 44 | "IPAddressField": "inet", 45 | "GenericIPAddressField": "inet", 46 | "JSONField": "text", 47 | "ManyToOneRel": "bigint", 48 | "OneToOneField": "bigint", 49 | "PositiveBigIntegerField": "bigint", 50 | "PositiveIntegerField": "bigint", 51 | "PositiveSmallIntegerField": "int", 52 | "RelatedField": "bigint", 53 | "SlugField": "text", 54 | "SmallAutoField": "int", 55 | "SmallIntegerField": "int", 56 | "TextField": "text", 57 | "TimeField": "time", 58 | "UUIDField": "uuid", 59 | } 60 | 61 | operators = { 62 | "exact": "= %s", 63 | "iexact": "= %s", 64 | "contains": "CONTAINS %s", 65 | "gt": "> %s", 66 | "gte": ">= %s", 67 | "lt": "< %s", 68 | "lte": "<= %s", 69 | } 70 | 71 | Database = Database 72 | SchemaEditorClass = DatabaseSchemaEditor 73 | # Classes instantiated in __init__(). 74 | client_class = DatabaseClient 75 | creation_class = DatabaseCreation 76 | features_class = DatabaseFeatures 77 | introspection_class = DatabaseIntrospection 78 | ops_class = DatabaseOperations 79 | validation_class = DatabaseValidation 80 | 81 | queries_limit = 9000 82 | 83 | def get_connection_params(self): 84 | """Return a dict of parameters suitable for get_new_connection.""" 85 | out_params = {} 86 | options = self.settings_dict.get("OPTIONS", {}) 87 | 88 | connection_options = options.get("connection", {}) 89 | replication_options = options.get("replication", {}) 90 | execution_profile_options = options.get("execution_profile", {}) 91 | 92 | if not replication_options.get("class"): 93 | raise ValueError( 94 | "Missing configuration value: OPTIONS.replication.class\n" 95 | "See: https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/architecture/archDataDistributeReplication.html" # noqa 96 | ) 97 | 98 | if not replication_options.get("replication_factor"): 99 | raise ValueError( 100 | "Missing configuration value: OPTIONS.replication.replication_factor\n" 101 | "See: https://docs.datastax.com/en/cql-oss/3.3/cql/cql_using/useUpdateKeyspaceRF.html" 102 | ) 103 | 104 | ep_keys = ( 105 | "load_balancing_policy", 106 | "retry_policy", 107 | "consistency_level", 108 | "serial_consistency_level", 109 | "request_timeout", 110 | "speculative_execution_policy", 111 | ) 112 | 113 | if not connection_options.get("contact_points"): 114 | out_params["contact_points"] = self.settings_dict["HOST"].split(",") 115 | if not connection_options.get("port") and self.settings_dict.get("PORT"): 116 | out_params["port"] = int(self.settings_dict["PORT"]) 117 | if connection_options.get("auth_provider") is None and ( 118 | self.settings_dict.get("USER") and self.settings_dict.get("PASSWORD") 119 | ): 120 | out_params["auth_provider"] = PlainTextAuthProvider( 121 | username=self.settings_dict["USER"], 122 | password=self.settings_dict["PASSWORD"], 123 | ) 124 | if connection_options.get("protocol_version") is None: 125 | out_params["protocol_version"] = self.DEFAULT_PROTOCOL_VERSION 126 | 127 | ep_options = { 128 | k: execution_profile_options.pop(k, None) 129 | for k in ep_keys 130 | if execution_profile_options.get(k) 131 | } 132 | ep_options["row_factory"] = tuple_factory 133 | if "load_balancing_policy" not in ep_options: 134 | ep_options["load_balancing_policy"] = TokenAwarePolicy(RoundRobinPolicy()) 135 | 136 | out_params["execution_profiles"] = { 137 | EXEC_PROFILE_DEFAULT: ExecutionProfile(**ep_options) 138 | } 139 | return out_params 140 | 141 | def get_new_connection(self, conn_params): 142 | """Open a connection to the database.""" 143 | db = self.settings_dict["NAME"] 144 | cluster = Database.initialize(db, **conn_params) 145 | 146 | cursor = Cursor(cluster.connect()) 147 | 148 | # Ensure that the keyspace exists before using it, if possible. 149 | if db is not None: 150 | replication_options = json.dumps( 151 | self.settings_dict["OPTIONS"]["replication"] 152 | ).replace('"', "'") 153 | 154 | cursor.execute( 155 | ( 156 | "CREATE KEYSPACE IF NOT EXISTS {db} " 157 | "WITH REPLICATION = {replication}".format( 158 | db=db, replication=replication_options 159 | ) 160 | ) 161 | ) 162 | 163 | return cursor 164 | 165 | def init_connection_state(self): 166 | """Initialize the database connection settings.""" 167 | ... 168 | 169 | def create_cursor(self, name=None): 170 | """Create a cursor. Assume that a connection is established.""" 171 | name = name or self.settings_dict["NAME"] 172 | if name is not None: 173 | self.connection.set_keyspace(name) 174 | return self.connection 175 | 176 | def _set_autocommit(self, autocommit): 177 | ... 178 | 179 | def _commit(self): 180 | ... 181 | 182 | def _rollback(self): 183 | ... 184 | -------------------------------------------------------------------------------- /django_scylla/cql/compiler.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | from django.core.exceptions import EmptyResultSet 4 | from django.db import NotSupportedError 5 | from django.db.models import AutoField 6 | from django.db.models.sql import compiler 7 | 8 | 9 | def unique_rowid(): 10 | # TODO: guarantee that this is globally unique 11 | return int(time() * 1e6) 12 | 13 | 14 | class SQLCompiler(compiler.SQLCompiler): 15 | def get_extra_select(self, order_by, select): 16 | return [] 17 | 18 | def get_default_columns(self, *_, **__): 19 | """Do not pull in related models (e.g. via select_related or joins)""" 20 | return super().get_default_columns(None, None, None) 21 | 22 | def as_sql(self, with_limits=True, with_col_aliases=False): 23 | """ 24 | Create the SQL for this query. Return the SQL string and list of 25 | parameters. 26 | 27 | If 'with_limits' is False, any limit/offset information is not included 28 | in the query. 29 | """ 30 | refcounts_before = self.query.alias_refcount.copy() 31 | try: 32 | extra_select, order_by, group_by = self.pre_sql_setup() 33 | for_update_part = None 34 | # Is a LIMIT/OFFSET clause needed? 35 | with_limit_offset = with_limits and ( 36 | self.query.high_mark is not None or self.query.low_mark 37 | ) 38 | combinator = self.query.combinator 39 | features = self.connection.features 40 | if combinator: 41 | if not getattr(features, "supports_select_{}".format(combinator)): 42 | raise NotSupportedError( 43 | "{} is not supported on this database backend.".format( 44 | combinator 45 | ) 46 | ) 47 | result, params = self.get_combinator_sql( 48 | combinator, self.query.combinator_all 49 | ) 50 | else: 51 | distinct_fields, distinct_params = self.get_distinct() 52 | # This must come after 'select', 'ordering', and 'distinct' 53 | # (see docstring of get_from_clause() for details). 54 | from_, f_params = self.get_from_clause() 55 | try: 56 | where, w_params = ( 57 | self.compile(self.where) if self.where is not None else ("", []) 58 | ) 59 | except EmptyResultSet: 60 | if getattr(self, "elide_empty", True): 61 | raise 62 | # Use a predicate that's always False. 63 | where, w_params = "0 = 1", [] 64 | having, h_params = ( 65 | self.compile(self.having) if self.having is not None else ("", []) 66 | ) 67 | result = ["SELECT"] 68 | params = [] 69 | 70 | if self.query.distinct: 71 | distinct_result, distinct_params = self.connection.ops.distinct_sql( 72 | distinct_fields, 73 | distinct_params, 74 | ) 75 | result += distinct_result 76 | params += distinct_params 77 | 78 | out_cols = [] 79 | for _, (s_sql, s_params), alias in self.select: 80 | params.extend(s_params) 81 | if s_sql[0] == "(" and s_sql[-1] == ")": 82 | s_sql = s_sql[1:-1] 83 | out_cols.append(s_sql) 84 | 85 | result += [", ".join(out_cols), "FROM", *from_] 86 | params.extend(f_params) 87 | 88 | if where: 89 | result.append("WHERE %s" % where) 90 | params.extend(w_params) 91 | 92 | grouping = [] 93 | for g_sql, g_params in group_by: 94 | grouping.append(g_sql) 95 | params.extend(g_params) 96 | if grouping: 97 | if distinct_fields: 98 | raise NotImplementedError( 99 | "annotate() + distinct(fields) is not implemented." 100 | ) 101 | order_by = order_by or self.connection.ops.force_no_ordering() 102 | result.append("GROUP BY %s" % ", ".join(grouping)) 103 | if self._meta_ordering: 104 | order_by = None 105 | if having: 106 | result.append("HAVING %s" % having) 107 | params.extend(h_params) 108 | 109 | if hasattr(self.query, "explain_info") and self.query.explain_info: 110 | result.insert( 111 | 0, 112 | self.connection.ops.explain_query_prefix( 113 | self.query.explain_info.format, 114 | **self.query.explain_info.options, 115 | ), 116 | ) 117 | 118 | if order_by: 119 | ordering = [] 120 | for _, (o_sql, o_params, _) in order_by: 121 | ordering.append(o_sql) 122 | params.extend(o_params) 123 | result.append("ORDER BY %s" % ", ".join(ordering)) 124 | 125 | if with_limit_offset: 126 | result.append( 127 | self.connection.ops.limit_offset_sql( 128 | self.query.low_mark, self.query.high_mark 129 | ) 130 | ) 131 | 132 | if for_update_part and not features.for_update_after_from: 133 | result.append(for_update_part) 134 | 135 | if self.query.subquery and extra_select: 136 | raise NotSupportedError("Subqueries are not supported.") 137 | 138 | result.append("ALLOW FILTERING") 139 | return " ".join(result), tuple(params) 140 | finally: 141 | # Finally do cleanup - get rid of the joins we created above. 142 | self.query.reset_refcounts(refcounts_before) 143 | 144 | def get_order_by(self): 145 | # TODO: fix this! 146 | return [] 147 | 148 | 149 | class SQLInsertCompiler(compiler.SQLInsertCompiler): 150 | def __init__(self, *args, **kwargs): 151 | super().__init__(*args, **kwargs) 152 | pk_fields = [ 153 | f 154 | for f in self.query.model._meta.get_fields() 155 | if getattr(f, "primary_key", False) and f not in self.query.fields 156 | ] 157 | self.query.fields = pk_fields + list(self.query.fields) 158 | 159 | def prepare_value(self, field, value): 160 | if value is None and isinstance(field, AutoField): 161 | value = unique_rowid() 162 | return super().prepare_value(field, value) 163 | 164 | 165 | class SQLUpdateCompiler(compiler.SQLUpdateCompiler): 166 | def as_sql(self, *args, **kwargs): 167 | result, params = super().as_sql(*args, **kwargs) 168 | return result, params 169 | 170 | def execute_sql(self, result_type): 171 | super().execute_sql(result_type) 172 | return 1 173 | 174 | 175 | class SQLDeleteCompiler(compiler.SQLDeleteCompiler): 176 | ... 177 | -------------------------------------------------------------------------------- /django_scylla/features.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | 3 | from django.db.backends.base.features import BaseDatabaseFeatures 4 | 5 | 6 | class DatabaseFeatures(BaseDatabaseFeatures): 7 | allows_group_by_lob = False 8 | update_can_self_select = False 9 | 10 | can_use_chunked_reads = True 11 | can_return_columns_from_insert = False 12 | can_return_rows_from_bulk_insert = False 13 | has_bulk_insert = False 14 | has_select_for_update = False 15 | uses_savepoints = False 16 | can_release_savepoints = False 17 | 18 | supports_subqueries_in_group_by = False 19 | 20 | # If True, don't use integer foreign keys referring to, e.g., positive 21 | # integer primary keys. 22 | related_fields_match_type = True 23 | 24 | # Does the backend ignore unnecessary ORDER BY clauses in subqueries? 25 | ignores_unnecessary_order_by_in_subqueries = True 26 | 27 | # Is there a true datatype for uuid? 28 | has_native_uuid_field = True 29 | 30 | # Is there a true datatype for timedeltas? 31 | has_native_duration_field = True 32 | 33 | # Does the database driver supports same type temporal data subtraction 34 | # by returning the type used to store duration field? 35 | supports_temporal_subtraction = False 36 | 37 | # Does the __regex lookup support backreferencing and grouping? 38 | supports_regex_backreferencing = False 39 | 40 | # Can date/datetime lookups be performed using a string? 41 | supports_date_lookup_using_string = True 42 | 43 | # Can datetimes with timezones be used? 44 | supports_timezones = True 45 | 46 | # Does the database have a copy of the zoneinfo database? 47 | has_zoneinfo_database = True 48 | 49 | # When performing a GROUP BY, is an ORDER BY NULL required 50 | # to remove any ordering? 51 | requires_explicit_null_ordering_when_grouping = False 52 | 53 | # Does the backend order NULL values as largest or smallest? 54 | nulls_order_largest = False 55 | 56 | # Does the backend support NULLS FIRST and NULLS LAST in ORDER BY? 57 | supports_order_by_nulls_modifier = False 58 | 59 | # Does the backend orders NULLS FIRST by default? 60 | order_by_nulls_first = False 61 | 62 | # The database's limit on the number of query parameters. 63 | max_query_params = None 64 | 65 | # Can an object have an autoincrement primary key of 0? 66 | allows_auto_pk_0 = True 67 | 68 | # Do we need to NULL a ForeignKey out, or can the constraint check be 69 | # deferred 70 | can_defer_constraint_checks = False 71 | 72 | # date_interval_sql can properly handle mixed Date/DateTime fields and timedeltas 73 | supports_mixed_date_datetime_comparisons = True 74 | 75 | # Does the backend support tablespaces? Default to False because it isn't 76 | # in the SQL standard. 77 | supports_tablespaces = False 78 | 79 | # Does the backend reset sequences between tests? 80 | supports_sequence_reset = False 81 | 82 | # Can the backend introspect the default value of a column? 83 | can_introspect_default = False 84 | 85 | # Confirm support for introspected foreign keys 86 | # Every database can do this reliably, except MySQL, 87 | # which can't do it for MyISAM tables 88 | can_introspect_foreign_keys = False 89 | 90 | # Can the backend introspect the column order (ASC/DESC) for indexes? 91 | supports_index_column_ordering = False 92 | 93 | # Does the backend support introspection of materialized views? 94 | can_introspect_materialized_views = True 95 | 96 | # Support for the DISTINCT ON clause 97 | can_distinct_on_fields = False 98 | 99 | # Does the backend prevent running SQL queries in broken transactions? 100 | atomic_transactions = False 101 | 102 | # Can we roll back DDL in a transaction? 103 | can_rollback_ddl = False 104 | 105 | # Does it support operations requiring references rename in a transaction? 106 | supports_atomic_references_rename = True 107 | 108 | # Can we issue more than one ALTER COLUMN clause in an ALTER TABLE? 109 | supports_combined_alters = False 110 | 111 | # Does it support foreign keys? 112 | supports_foreign_keys = False 113 | 114 | # Can it create foreign key constraints inline when adding columns? 115 | can_create_inline_fk = False 116 | 117 | # Does it automatically index foreign keys? 118 | indexes_foreign_keys = False 119 | 120 | # Does it support CHECK constraints? 121 | supports_column_check_constraints = False 122 | supports_table_check_constraints = False 123 | # Does the backend support introspection of CHECK constraints? 124 | can_introspect_check_constraints = False 125 | 126 | # Does the backend support 'pyformat' style ("... %(name)s ...", {'name': value}) 127 | # parameter passing? Note this can be provided by the backend even if not 128 | # supported by the Python driver 129 | supports_paramstyle_pyformat = True 130 | 131 | # Does the backend require literal defaults, rather than parameterized ones? 132 | requires_literal_defaults = False 133 | 134 | # Does the backend require a connection reset after each material schema change? 135 | connection_persists_old_columns = False 136 | 137 | # Does 'a' LIKE 'A' match? 138 | has_case_insensitive_like = True 139 | 140 | # Suffix for backends that don't support "SELECT xxx;" queries. 141 | bare_select_suffix = "" 142 | 143 | # If NULL is implied on columns without needing to be explicitly specified 144 | implied_column_null = True 145 | 146 | # Does the backend support "select for update" queries with limit (and offset)? 147 | supports_select_for_update_with_limit = False 148 | 149 | # Does the backend ignore null expressions in GREATEST and LEAST queries unless 150 | # every expression is null? 151 | greatest_least_ignores_nulls = False 152 | 153 | # Can the backend clone databases for parallel test execution? 154 | # Defaults to False to allow third-party backends to opt-in. 155 | can_clone_databases = False 156 | 157 | # Does the backend consider table names with different casing to 158 | # be equal? 159 | ignores_table_name_case = False 160 | 161 | # Place FOR UPDATE right after FROM clause. Used on MSSQL. 162 | for_update_after_from = False 163 | 164 | # Combinatorial flags 165 | supports_select_union = False 166 | supports_select_intersection = False 167 | supports_select_difference = False 168 | supports_slicing_ordering_in_compound = False 169 | supports_parentheses_in_compound = False 170 | 171 | # Does the database support SQL 2003 FILTER (WHERE ...) in aggregate 172 | # expressions? 173 | supports_aggregate_filter_clause = False 174 | 175 | # Does the backend support indexing a TextField? 176 | supports_index_on_text_field = False 177 | 178 | # Does the backend support window expressions (expression OVER (...))? 179 | supports_over_clause = False 180 | supports_frame_range_fixed_distance = False 181 | only_supports_unbounded_with_preceding_and_following = False 182 | 183 | # Does the backend support CAST with precision? 184 | supports_cast_with_precision = True 185 | 186 | # How many second decimals does the database return when casting a value to 187 | # a type with time? 188 | time_cast_precision = 6 189 | 190 | # SQL to create a procedure for use by the Django test suite. The 191 | # functionality of the procedure isn't important. 192 | create_test_procedure_without_params_sql = None 193 | create_test_procedure_with_int_param_sql = None 194 | 195 | # Does the backend support keyword parameters for cursor.callproc()? 196 | supports_callproc_kwargs = False 197 | 198 | # What formats does the backend EXPLAIN syntax support? 199 | supported_explain_formats = set() 200 | 201 | # Does DatabaseOperations.explain_query_prefix() raise ValueError if 202 | # unknown kwargs are passed to QuerySet.explain()? 203 | validates_explain_options = True 204 | 205 | # Does the backend support the default parameter in lead() and lag()? 206 | supports_default_in_lead_lag = True 207 | 208 | # Does the backend support ignoring constraint or uniqueness errors during 209 | # INSERT? 210 | supports_ignore_conflicts = True 211 | 212 | # Does this backend require casting the results of CASE expressions used 213 | # in UPDATE statements to ensure the expression has the correct type? 214 | requires_casted_case_in_updates = False 215 | 216 | # Does the backend support partial indexes (CREATE INDEX ... WHERE ...)? 217 | supports_partial_indexes = False 218 | supports_functions_in_partial_indexes = False 219 | # Does the backend support covering indexes (CREATE INDEX ... INCLUDE ...)? 220 | supports_covering_indexes = False 221 | # Does the backend support indexes on expressions? 222 | supports_expression_indexes = False 223 | # Does the backend treat COLLATE as an indexed expression? 224 | collate_as_index_expression = False 225 | 226 | # Does the database allow more than one constraint or index on the same 227 | # field(s)? 228 | allows_multiple_constraints_on_same_fields = False 229 | 230 | # Does the backend support boolean expressions in SELECT and GROUP BY 231 | # clauses? 232 | supports_boolean_expr_in_select_clause = True 233 | 234 | # Does the backend support JSONField? 235 | supports_json_field = True 236 | # Can the backend introspect a JSONField? 237 | can_introspect_json_field = True 238 | # Does the backend support primitives in JSONField? 239 | supports_primitives_in_json_field = True 240 | # Is there a true datatype for JSON? 241 | has_native_json_field = False 242 | # Does the backend use PostgreSQL-style JSON operators like '->'? 243 | has_json_operators = False 244 | # Does the backend support __contains and __contained_by lookups for 245 | # a JSONField? 246 | supports_json_field_contains = True 247 | # Does value__d__contains={'f': 'g'} (without a list around the dict) match 248 | # {'d': [{'f': 'g'}]}? 249 | json_key_contains_list_matching_requires_list = False 250 | # Does the backend support JSONObject() database function? 251 | has_json_object_function = True 252 | 253 | # Does the backend support column collations? 254 | supports_collation_on_charfield = True 255 | supports_collation_on_textfield = True 256 | # Does the backend support non-deterministic collations? 257 | supports_non_deterministic_collations = True 258 | 259 | # Collation names for use by the Django test suite. 260 | test_collations = { 261 | "ci": None, # Case-insensitive. 262 | "cs": None, # Case-sensitive. 263 | "non_default": None, # Non-default. 264 | "swedish_ci": None, # Swedish case-insensitive. 265 | } 266 | 267 | # A set of dotted paths to tests in Django's test suite that are expected 268 | # to fail on this database. 269 | django_test_expected_failures = set() 270 | # A map of reasons to sets of dotted paths to tests in Django's test suite 271 | # that should be skipped for this database. 272 | django_test_skips = {} 273 | 274 | @cached_property 275 | def supports_transactions(self): 276 | return False 277 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "asgiref" 3 | version = "3.6.0" 4 | description = "ASGI specs, helper code, and adapters" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.7" 8 | 9 | [package.extras] 10 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] 11 | 12 | [[package]] 13 | name = "backports.zoneinfo" 14 | version = "0.2.1" 15 | description = "Backport of the standard library zoneinfo module" 16 | category = "main" 17 | optional = false 18 | python-versions = ">=3.6" 19 | 20 | [package.extras] 21 | tzdata = ["tzdata"] 22 | 23 | [[package]] 24 | name = "bandit" 25 | version = "1.7.5" 26 | description = "Security oriented static analyser for python code." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=3.7" 30 | 31 | [package.dependencies] 32 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} 33 | GitPython = ">=1.0.1" 34 | PyYAML = ">=5.3.1" 35 | rich = "*" 36 | stevedore = ">=1.20.0" 37 | 38 | [package.extras] 39 | test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] 40 | toml = ["tomli (>=1.1.0)"] 41 | yaml = ["pyyaml"] 42 | 43 | [[package]] 44 | name = "black" 45 | version = "23.3.0" 46 | description = "The uncompromising code formatter." 47 | category = "dev" 48 | optional = false 49 | python-versions = ">=3.7" 50 | 51 | [package.dependencies] 52 | click = ">=8.0.0" 53 | mypy-extensions = ">=0.4.3" 54 | packaging = ">=22.0" 55 | pathspec = ">=0.9.0" 56 | platformdirs = ">=2" 57 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 58 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 59 | 60 | [package.extras] 61 | colorama = ["colorama (>=0.4.3)"] 62 | d = ["aiohttp (>=3.7.4)"] 63 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 64 | uvloop = ["uvloop (>=0.15.2)"] 65 | 66 | [[package]] 67 | name = "cachetools" 68 | version = "5.3.0" 69 | description = "Extensible memoizing collections and decorators" 70 | category = "dev" 71 | optional = false 72 | python-versions = "~=3.7" 73 | 74 | [[package]] 75 | name = "cfgv" 76 | version = "3.3.1" 77 | description = "Validate configuration and produce human readable error messages." 78 | category = "dev" 79 | optional = false 80 | python-versions = ">=3.6.1" 81 | 82 | [[package]] 83 | name = "chardet" 84 | version = "5.1.0" 85 | description = "Universal encoding detector for Python 3" 86 | category = "dev" 87 | optional = false 88 | python-versions = ">=3.7" 89 | 90 | [[package]] 91 | name = "click" 92 | version = "8.1.3" 93 | description = "Composable command line interface toolkit" 94 | category = "main" 95 | optional = false 96 | python-versions = ">=3.7" 97 | 98 | [package.dependencies] 99 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 100 | 101 | [[package]] 102 | name = "colorama" 103 | version = "0.4.6" 104 | description = "Cross-platform colored terminal text." 105 | category = "main" 106 | optional = false 107 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 108 | 109 | [[package]] 110 | name = "coverage" 111 | version = "7.2.3" 112 | description = "Code coverage measurement for Python" 113 | category = "dev" 114 | optional = false 115 | python-versions = ">=3.7" 116 | 117 | [package.dependencies] 118 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 119 | 120 | [package.extras] 121 | toml = ["tomli"] 122 | 123 | [[package]] 124 | name = "distlib" 125 | version = "0.3.6" 126 | description = "Distribution utilities" 127 | category = "dev" 128 | optional = false 129 | python-versions = "*" 130 | 131 | [[package]] 132 | name = "django" 133 | version = "4.1.8" 134 | description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." 135 | category = "main" 136 | optional = false 137 | python-versions = ">=3.8" 138 | 139 | [package.dependencies] 140 | asgiref = ">=3.5.2,<4" 141 | "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} 142 | sqlparse = ">=0.2.2" 143 | tzdata = {version = "*", markers = "sys_platform == \"win32\""} 144 | 145 | [package.extras] 146 | argon2 = ["argon2-cffi (>=19.1.0)"] 147 | bcrypt = ["bcrypt"] 148 | 149 | [[package]] 150 | name = "exceptiongroup" 151 | version = "1.1.1" 152 | description = "Backport of PEP 654 (exception groups)" 153 | category = "dev" 154 | optional = false 155 | python-versions = ">=3.7" 156 | 157 | [package.extras] 158 | test = ["pytest (>=6)"] 159 | 160 | [[package]] 161 | name = "filelock" 162 | version = "3.12.0" 163 | description = "A platform independent file lock." 164 | category = "dev" 165 | optional = false 166 | python-versions = ">=3.7" 167 | 168 | [package.extras] 169 | docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] 170 | testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] 171 | 172 | [[package]] 173 | name = "flake8" 174 | version = "5.0.4" 175 | description = "the modular source code checker: pep8 pyflakes and co" 176 | category = "dev" 177 | optional = false 178 | python-versions = ">=3.6.1" 179 | 180 | [package.dependencies] 181 | mccabe = ">=0.7.0,<0.8.0" 182 | pycodestyle = ">=2.9.0,<2.10.0" 183 | pyflakes = ">=2.5.0,<2.6.0" 184 | 185 | [[package]] 186 | name = "flake8-bandit" 187 | version = "4.1.1" 188 | description = "Automated security testing with bandit and flake8." 189 | category = "dev" 190 | optional = false 191 | python-versions = ">=3.6" 192 | 193 | [package.dependencies] 194 | bandit = ">=1.7.3" 195 | flake8 = ">=5.0.0" 196 | 197 | [[package]] 198 | name = "flake8-blind-except" 199 | version = "0.2.1" 200 | description = "A flake8 extension that checks for blind except: statements" 201 | category = "dev" 202 | optional = false 203 | python-versions = "*" 204 | 205 | [[package]] 206 | name = "flake8-docstrings" 207 | version = "1.7.0" 208 | description = "Extension for flake8 which uses pydocstyle to check docstrings" 209 | category = "dev" 210 | optional = false 211 | python-versions = ">=3.7" 212 | 213 | [package.dependencies] 214 | flake8 = ">=3" 215 | pydocstyle = ">=2.1" 216 | 217 | [[package]] 218 | name = "flake8-logging-format" 219 | version = "0.9.0" 220 | description = "" 221 | category = "dev" 222 | optional = false 223 | python-versions = "*" 224 | 225 | [package.extras] 226 | lint = ["flake8"] 227 | test = ["pyhamcrest", "pytest", "pytest-cov"] 228 | 229 | [[package]] 230 | name = "flake8-print" 231 | version = "5.0.0" 232 | description = "print statement checker plugin for flake8" 233 | category = "dev" 234 | optional = false 235 | python-versions = ">=3.7" 236 | 237 | [package.dependencies] 238 | flake8 = ">=3.0" 239 | pycodestyle = "*" 240 | 241 | [[package]] 242 | name = "freezegun" 243 | version = "1.2.2" 244 | description = "Let your Python tests travel through time" 245 | category = "dev" 246 | optional = false 247 | python-versions = ">=3.6" 248 | 249 | [package.dependencies] 250 | python-dateutil = ">=2.7" 251 | 252 | [[package]] 253 | name = "geomet" 254 | version = "0.2.1.post1" 255 | description = "GeoJSON <-> WKT/WKB conversion utilities" 256 | category = "main" 257 | optional = false 258 | python-versions = ">2.6, !=3.3.*, <4" 259 | 260 | [package.dependencies] 261 | click = "*" 262 | six = "*" 263 | 264 | [[package]] 265 | name = "gitdb" 266 | version = "4.0.10" 267 | description = "Git Object Database" 268 | category = "dev" 269 | optional = false 270 | python-versions = ">=3.7" 271 | 272 | [package.dependencies] 273 | smmap = ">=3.0.1,<6" 274 | 275 | [[package]] 276 | name = "gitpython" 277 | version = "3.1.31" 278 | description = "GitPython is a Python library used to interact with Git repositories" 279 | category = "dev" 280 | optional = false 281 | python-versions = ">=3.7" 282 | 283 | [package.dependencies] 284 | gitdb = ">=4.0.1,<5" 285 | 286 | [[package]] 287 | name = "identify" 288 | version = "2.5.22" 289 | description = "File identification library for Python" 290 | category = "dev" 291 | optional = false 292 | python-versions = ">=3.7" 293 | 294 | [package.extras] 295 | license = ["ukkonen"] 296 | 297 | [[package]] 298 | name = "iniconfig" 299 | version = "2.0.0" 300 | description = "brain-dead simple config-ini parsing" 301 | category = "dev" 302 | optional = false 303 | python-versions = ">=3.7" 304 | 305 | [[package]] 306 | name = "isort" 307 | version = "5.12.0" 308 | description = "A Python utility / library to sort Python imports." 309 | category = "dev" 310 | optional = false 311 | python-versions = ">=3.8.0" 312 | 313 | [package.extras] 314 | colors = ["colorama (>=0.4.3)"] 315 | pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] 316 | plugins = ["setuptools"] 317 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 318 | 319 | [[package]] 320 | name = "markdown-it-py" 321 | version = "2.2.0" 322 | description = "Python port of markdown-it. Markdown parsing, done right!" 323 | category = "dev" 324 | optional = false 325 | python-versions = ">=3.7" 326 | 327 | [package.dependencies] 328 | mdurl = ">=0.1,<1.0" 329 | 330 | [package.extras] 331 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 332 | code_style = ["pre-commit (>=3.0,<4.0)"] 333 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 334 | linkify = ["linkify-it-py (>=1,<3)"] 335 | plugins = ["mdit-py-plugins"] 336 | profiling = ["gprof2dot"] 337 | rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme", "sphinx-copybutton", "sphinx-design"] 338 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 339 | 340 | [[package]] 341 | name = "mccabe" 342 | version = "0.7.0" 343 | description = "McCabe checker, plugin for flake8" 344 | category = "dev" 345 | optional = false 346 | python-versions = ">=3.6" 347 | 348 | [[package]] 349 | name = "mdurl" 350 | version = "0.1.2" 351 | description = "Markdown URL utilities" 352 | category = "dev" 353 | optional = false 354 | python-versions = ">=3.7" 355 | 356 | [[package]] 357 | name = "mypy" 358 | version = "1.2.0" 359 | description = "Optional static typing for Python" 360 | category = "dev" 361 | optional = false 362 | python-versions = ">=3.7" 363 | 364 | [package.dependencies] 365 | mypy-extensions = ">=1.0.0" 366 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 367 | typing-extensions = ">=3.10" 368 | 369 | [package.extras] 370 | dmypy = ["psutil (>=4.0)"] 371 | install-types = ["pip"] 372 | python2 = ["typed-ast (>=1.4.0,<2)"] 373 | reports = ["lxml"] 374 | 375 | [[package]] 376 | name = "mypy-extensions" 377 | version = "1.0.0" 378 | description = "Type system extensions for programs checked with the mypy type checker." 379 | category = "dev" 380 | optional = false 381 | python-versions = ">=3.5" 382 | 383 | [[package]] 384 | name = "nodeenv" 385 | version = "1.7.0" 386 | description = "Node.js virtual environment builder" 387 | category = "dev" 388 | optional = false 389 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 390 | 391 | [[package]] 392 | name = "packaging" 393 | version = "23.1" 394 | description = "Core utilities for Python packages" 395 | category = "dev" 396 | optional = false 397 | python-versions = ">=3.7" 398 | 399 | [[package]] 400 | name = "pathspec" 401 | version = "0.11.1" 402 | description = "Utility library for gitignore style pattern matching of file paths." 403 | category = "dev" 404 | optional = false 405 | python-versions = ">=3.7" 406 | 407 | [[package]] 408 | name = "pbr" 409 | version = "5.11.1" 410 | description = "Python Build Reasonableness" 411 | category = "dev" 412 | optional = false 413 | python-versions = ">=2.6" 414 | 415 | [[package]] 416 | name = "platformdirs" 417 | version = "3.2.0" 418 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 419 | category = "dev" 420 | optional = false 421 | python-versions = ">=3.7" 422 | 423 | [package.extras] 424 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] 425 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 426 | 427 | [[package]] 428 | name = "pluggy" 429 | version = "1.0.0" 430 | description = "plugin and hook calling mechanisms for python" 431 | category = "dev" 432 | optional = false 433 | python-versions = ">=3.6" 434 | 435 | [package.extras] 436 | dev = ["pre-commit", "tox"] 437 | testing = ["pytest", "pytest-benchmark"] 438 | 439 | [[package]] 440 | name = "pre-commit" 441 | version = "3.2.2" 442 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 443 | category = "dev" 444 | optional = false 445 | python-versions = ">=3.8" 446 | 447 | [package.dependencies] 448 | cfgv = ">=2.0.0" 449 | identify = ">=1.0.0" 450 | nodeenv = ">=0.11.1" 451 | pyyaml = ">=5.1" 452 | virtualenv = ">=20.10.0" 453 | 454 | [[package]] 455 | name = "pycodestyle" 456 | version = "2.9.1" 457 | description = "Python style guide checker" 458 | category = "dev" 459 | optional = false 460 | python-versions = ">=3.6" 461 | 462 | [[package]] 463 | name = "pydocstyle" 464 | version = "6.3.0" 465 | description = "Python docstring style checker" 466 | category = "dev" 467 | optional = false 468 | python-versions = ">=3.6" 469 | 470 | [package.dependencies] 471 | snowballstemmer = ">=2.2.0" 472 | 473 | [package.extras] 474 | toml = ["tomli (>=1.2.3)"] 475 | 476 | [[package]] 477 | name = "pyflakes" 478 | version = "2.5.0" 479 | description = "passive checker of Python programs" 480 | category = "dev" 481 | optional = false 482 | python-versions = ">=3.6" 483 | 484 | [[package]] 485 | name = "pygments" 486 | version = "2.15.1" 487 | description = "Pygments is a syntax highlighting package written in Python." 488 | category = "dev" 489 | optional = false 490 | python-versions = ">=3.7" 491 | 492 | [package.extras] 493 | plugins = ["importlib-metadata"] 494 | 495 | [[package]] 496 | name = "pyproject-api" 497 | version = "1.5.1" 498 | description = "API to interact with the python pyproject.toml based projects" 499 | category = "dev" 500 | optional = false 501 | python-versions = ">=3.7" 502 | 503 | [package.dependencies] 504 | packaging = ">=23" 505 | tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} 506 | 507 | [package.extras] 508 | docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] 509 | testing = ["covdefaults (>=2.2.2)", "importlib-metadata (>=6)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "virtualenv (>=20.17.1)", "wheel (>=0.38.4)"] 510 | 511 | [[package]] 512 | name = "pytest" 513 | version = "7.3.1" 514 | description = "pytest: simple powerful testing with Python" 515 | category = "dev" 516 | optional = false 517 | python-versions = ">=3.7" 518 | 519 | [package.dependencies] 520 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 521 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 522 | iniconfig = "*" 523 | packaging = "*" 524 | pluggy = ">=0.12,<2.0" 525 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 526 | 527 | [package.extras] 528 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 529 | 530 | [[package]] 531 | name = "pytest-cov" 532 | version = "4.0.0" 533 | description = "Pytest plugin for measuring coverage." 534 | category = "dev" 535 | optional = false 536 | python-versions = ">=3.6" 537 | 538 | [package.dependencies] 539 | coverage = {version = ">=5.2.1", extras = ["toml"]} 540 | pytest = ">=4.6" 541 | 542 | [package.extras] 543 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 544 | 545 | [[package]] 546 | name = "pytest-django" 547 | version = "4.5.2" 548 | description = "A Django plugin for pytest." 549 | category = "dev" 550 | optional = false 551 | python-versions = ">=3.5" 552 | 553 | [package.dependencies] 554 | pytest = ">=5.4.0" 555 | 556 | [package.extras] 557 | docs = ["sphinx", "sphinx-rtd-theme"] 558 | testing = ["django", "django-configurations (>=2.0)"] 559 | 560 | [[package]] 561 | name = "python-dateutil" 562 | version = "2.8.2" 563 | description = "Extensions to the standard Python datetime module" 564 | category = "dev" 565 | optional = false 566 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 567 | 568 | [package.dependencies] 569 | six = ">=1.5" 570 | 571 | [[package]] 572 | name = "pyyaml" 573 | version = "6.0" 574 | description = "YAML parser and emitter for Python" 575 | category = "main" 576 | optional = false 577 | python-versions = ">=3.6" 578 | 579 | [[package]] 580 | name = "rich" 581 | version = "13.3.4" 582 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 583 | category = "dev" 584 | optional = false 585 | python-versions = ">=3.7.0" 586 | 587 | [package.dependencies] 588 | markdown-it-py = ">=2.2.0,<3.0.0" 589 | pygments = ">=2.13.0,<3.0.0" 590 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} 591 | 592 | [package.extras] 593 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 594 | 595 | [[package]] 596 | name = "scylla-driver" 597 | version = "3.26.0" 598 | description = "Scylla Driver for Apache Cassandra" 599 | category = "main" 600 | optional = false 601 | python-versions = "*" 602 | 603 | [package.dependencies] 604 | geomet = ">=0.1,<0.3" 605 | pyyaml = ">5.0" 606 | six = ">=1.9" 607 | 608 | [package.extras] 609 | graph = ["gremlinpython (==3.4.6)"] 610 | 611 | [[package]] 612 | name = "six" 613 | version = "1.16.0" 614 | description = "Python 2 and 3 compatibility utilities" 615 | category = "main" 616 | optional = false 617 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 618 | 619 | [[package]] 620 | name = "smmap" 621 | version = "5.0.0" 622 | description = "A pure Python implementation of a sliding window memory map manager" 623 | category = "dev" 624 | optional = false 625 | python-versions = ">=3.6" 626 | 627 | [[package]] 628 | name = "snowballstemmer" 629 | version = "2.2.0" 630 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 631 | category = "dev" 632 | optional = false 633 | python-versions = "*" 634 | 635 | [[package]] 636 | name = "sqlparse" 637 | version = "0.4.4" 638 | description = "A non-validating SQL parser." 639 | category = "main" 640 | optional = false 641 | python-versions = ">=3.5" 642 | 643 | [package.extras] 644 | dev = ["build", "flake8"] 645 | doc = ["sphinx"] 646 | test = ["pytest", "pytest-cov"] 647 | 648 | [[package]] 649 | name = "stevedore" 650 | version = "5.0.0" 651 | description = "Manage dynamic plugins for Python applications" 652 | category = "dev" 653 | optional = false 654 | python-versions = ">=3.8" 655 | 656 | [package.dependencies] 657 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 658 | 659 | [[package]] 660 | name = "tomli" 661 | version = "2.0.1" 662 | description = "A lil' TOML parser" 663 | category = "dev" 664 | optional = false 665 | python-versions = ">=3.7" 666 | 667 | [[package]] 668 | name = "tox" 669 | version = "4.4.12" 670 | description = "tox is a generic virtualenv management and test command line tool" 671 | category = "dev" 672 | optional = false 673 | python-versions = ">=3.7" 674 | 675 | [package.dependencies] 676 | cachetools = ">=5.3" 677 | chardet = ">=5.1" 678 | colorama = ">=0.4.6" 679 | filelock = ">=3.11" 680 | packaging = ">=23" 681 | platformdirs = ">=3.2" 682 | pluggy = ">=1" 683 | pyproject-api = ">=1.5.1" 684 | tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} 685 | virtualenv = ">=20.21" 686 | 687 | [package.extras] 688 | docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-argparse-cli (>=1.11)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)", "sphinx-copybutton (>=0.5.1)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] 689 | testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "devpi-process (>=0.3)", "diff-cover (>=7.5)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.14)", "psutil (>=5.9.4)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-xdist (>=3.2.1)", "re-assert (>=1.1)", "time-machine (>=2.9)", "wheel (>=0.40)"] 690 | 691 | [[package]] 692 | name = "typing-extensions" 693 | version = "4.5.0" 694 | description = "Backported and Experimental Type Hints for Python 3.7+" 695 | category = "dev" 696 | optional = false 697 | python-versions = ">=3.7" 698 | 699 | [[package]] 700 | name = "tzdata" 701 | version = "2023.3" 702 | description = "Provider of IANA time zone data" 703 | category = "main" 704 | optional = false 705 | python-versions = ">=2" 706 | 707 | [[package]] 708 | name = "virtualenv" 709 | version = "20.22.0" 710 | description = "Virtual Python Environment builder" 711 | category = "dev" 712 | optional = false 713 | python-versions = ">=3.7" 714 | 715 | [package.dependencies] 716 | distlib = ">=0.3.6,<1" 717 | filelock = ">=3.11,<4" 718 | platformdirs = ">=3.2,<4" 719 | 720 | [package.extras] 721 | docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] 722 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] 723 | 724 | [metadata] 725 | lock-version = "1.1" 726 | python-versions = "^3.8" 727 | content-hash = "e36a158da1df78312e38d5778b69dd7eecf65fe414eee63aab19c43a921acac8" 728 | 729 | [metadata.files] 730 | asgiref = [ 731 | {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, 732 | {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, 733 | ] 734 | "backports.zoneinfo" = [ 735 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, 736 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, 737 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, 738 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, 739 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, 740 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, 741 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, 742 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, 743 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, 744 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, 745 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, 746 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, 747 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, 748 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, 749 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, 750 | {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, 751 | ] 752 | bandit = [ 753 | {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, 754 | {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, 755 | ] 756 | black = [ 757 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, 758 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, 759 | {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, 760 | {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, 761 | {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, 762 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, 763 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, 764 | {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, 765 | {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, 766 | {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, 767 | {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, 768 | {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, 769 | {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, 770 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, 771 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, 772 | {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, 773 | {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, 774 | {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, 775 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, 776 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, 777 | {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, 778 | {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, 779 | {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, 780 | {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, 781 | {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, 782 | ] 783 | cachetools = [ 784 | {file = "cachetools-5.3.0-py3-none-any.whl", hash = "sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"}, 785 | {file = "cachetools-5.3.0.tar.gz", hash = "sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14"}, 786 | ] 787 | cfgv = [ 788 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 789 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 790 | ] 791 | chardet = [ 792 | {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, 793 | {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, 794 | ] 795 | click = [ 796 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 797 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 798 | ] 799 | colorama = [ 800 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 801 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 802 | ] 803 | coverage = [ 804 | {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, 805 | {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, 806 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, 807 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, 808 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, 809 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, 810 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, 811 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, 812 | {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, 813 | {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, 814 | {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, 815 | {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, 816 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, 817 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, 818 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, 819 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, 820 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, 821 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, 822 | {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, 823 | {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, 824 | {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, 825 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, 826 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, 827 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, 828 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, 829 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, 830 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, 831 | {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, 832 | {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, 833 | {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, 834 | {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, 835 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, 836 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, 837 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, 838 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, 839 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, 840 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, 841 | {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, 842 | {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, 843 | {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, 844 | {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, 845 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, 846 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, 847 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, 848 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, 849 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, 850 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, 851 | {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, 852 | {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, 853 | {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, 854 | {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, 855 | ] 856 | distlib = [ 857 | {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, 858 | {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, 859 | ] 860 | django = [ 861 | {file = "Django-4.1.8-py3-none-any.whl", hash = "sha256:dba9dd0bf8b748aa9c98d6c679f8b1c0bfa43124844bea9425ad9ba0cd5e65c3"}, 862 | {file = "Django-4.1.8.tar.gz", hash = "sha256:43253f4b4a2c3a1694cd52d3517847271934c84dbd060ca0e82a9f90e569dab3"}, 863 | ] 864 | exceptiongroup = [ 865 | {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, 866 | {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, 867 | ] 868 | filelock = [ 869 | {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, 870 | {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, 871 | ] 872 | flake8 = [ 873 | {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, 874 | {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, 875 | ] 876 | flake8-bandit = [ 877 | {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"}, 878 | {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, 879 | ] 880 | flake8-blind-except = [ 881 | {file = "flake8-blind-except-0.2.1.tar.gz", hash = "sha256:f25a575a9dcb3eeb3c760bf9c22db60b8b5a23120224ed1faa9a43f75dd7dd16"}, 882 | ] 883 | flake8-docstrings = [ 884 | {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, 885 | {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, 886 | ] 887 | flake8-logging-format = [ 888 | {file = "flake8-logging-format-0.9.0.tar.gz", hash = "sha256:e830cc49091e4b8ab9ea3da69a3da074bd631ce9a7db300e5c89fb48ba4a6986"}, 889 | ] 890 | flake8-print = [ 891 | {file = "flake8-print-5.0.0.tar.gz", hash = "sha256:76915a2a389cc1c0879636c219eb909c38501d3a43cc8dae542081c9ba48bdf9"}, 892 | {file = "flake8_print-5.0.0-py3-none-any.whl", hash = "sha256:84a1a6ea10d7056b804221ac5e62b1cee1aefc897ce16f2e5c42d3046068f5d8"}, 893 | ] 894 | freezegun = [ 895 | {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, 896 | {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, 897 | ] 898 | geomet = [ 899 | {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, 900 | {file = "geomet-0.2.1.post1-py3.6.egg", hash = "sha256:87ae0fc42e532b9e98969c0bbf895a5e0b2bb4f6f775cf51a74e6482f1f35c2b"}, 901 | {file = "geomet-0.2.1.post1.tar.gz", hash = "sha256:91d754f7c298cbfcabd3befdb69c641c27fe75e808b27aa55028605761d17e95"}, 902 | ] 903 | gitdb = [ 904 | {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, 905 | {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, 906 | ] 907 | gitpython = [ 908 | {file = "GitPython-3.1.31-py3-none-any.whl", hash = "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"}, 909 | {file = "GitPython-3.1.31.tar.gz", hash = "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573"}, 910 | ] 911 | identify = [ 912 | {file = "identify-2.5.22-py2.py3-none-any.whl", hash = "sha256:f0faad595a4687053669c112004178149f6c326db71ee999ae4636685753ad2f"}, 913 | {file = "identify-2.5.22.tar.gz", hash = "sha256:f7a93d6cf98e29bd07663c60728e7a4057615068d7a639d132dc883b2d54d31e"}, 914 | ] 915 | iniconfig = [ 916 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 917 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 918 | ] 919 | isort = [ 920 | {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, 921 | {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, 922 | ] 923 | markdown-it-py = [ 924 | {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, 925 | {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, 926 | ] 927 | mccabe = [ 928 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 929 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 930 | ] 931 | mdurl = [ 932 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 933 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 934 | ] 935 | mypy = [ 936 | {file = "mypy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d"}, 937 | {file = "mypy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"}, 938 | {file = "mypy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e"}, 939 | {file = "mypy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a"}, 940 | {file = "mypy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128"}, 941 | {file = "mypy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41"}, 942 | {file = "mypy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb"}, 943 | {file = "mypy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937"}, 944 | {file = "mypy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9"}, 945 | {file = "mypy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602"}, 946 | {file = "mypy-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140"}, 947 | {file = "mypy-1.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336"}, 948 | {file = "mypy-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e"}, 949 | {file = "mypy-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119"}, 950 | {file = "mypy-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a"}, 951 | {file = "mypy-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950"}, 952 | {file = "mypy-1.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6"}, 953 | {file = "mypy-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5"}, 954 | {file = "mypy-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8"}, 955 | {file = "mypy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed"}, 956 | {file = "mypy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f"}, 957 | {file = "mypy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521"}, 958 | {file = "mypy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238"}, 959 | {file = "mypy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48"}, 960 | {file = "mypy-1.2.0-py3-none-any.whl", hash = "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394"}, 961 | {file = "mypy-1.2.0.tar.gz", hash = "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1"}, 962 | ] 963 | mypy-extensions = [ 964 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 965 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 966 | ] 967 | nodeenv = [ 968 | {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, 969 | {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, 970 | ] 971 | packaging = [ 972 | {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, 973 | {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, 974 | ] 975 | pathspec = [ 976 | {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, 977 | {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, 978 | ] 979 | pbr = [ 980 | {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, 981 | {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, 982 | ] 983 | platformdirs = [ 984 | {file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"}, 985 | {file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"}, 986 | ] 987 | pluggy = [ 988 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 989 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 990 | ] 991 | pre-commit = [ 992 | {file = "pre_commit-3.2.2-py2.py3-none-any.whl", hash = "sha256:0b4210aea813fe81144e87c5a291f09ea66f199f367fa1df41b55e1d26e1e2b4"}, 993 | {file = "pre_commit-3.2.2.tar.gz", hash = "sha256:5b808fcbda4afbccf6d6633a56663fed35b6c2bc08096fd3d47ce197ac351d9d"}, 994 | ] 995 | pycodestyle = [ 996 | {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, 997 | {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, 998 | ] 999 | pydocstyle = [ 1000 | {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, 1001 | {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, 1002 | ] 1003 | pyflakes = [ 1004 | {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, 1005 | {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, 1006 | ] 1007 | pygments = [ 1008 | {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, 1009 | {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, 1010 | ] 1011 | pyproject-api = [ 1012 | {file = "pyproject_api-1.5.1-py3-none-any.whl", hash = "sha256:4698a3777c2e0f6b624f8a4599131e2a25376d90fe8d146d7ac74c67c6f97c43"}, 1013 | {file = "pyproject_api-1.5.1.tar.gz", hash = "sha256:435f46547a9ff22cf4208ee274fca3e2869aeb062a4834adfc99a4dd64af3cf9"}, 1014 | ] 1015 | pytest = [ 1016 | {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, 1017 | {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, 1018 | ] 1019 | pytest-cov = [ 1020 | {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, 1021 | {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, 1022 | ] 1023 | pytest-django = [ 1024 | {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, 1025 | {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, 1026 | ] 1027 | python-dateutil = [ 1028 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 1029 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 1030 | ] 1031 | pyyaml = [ 1032 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1033 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1034 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1035 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1036 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 1037 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1038 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1039 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1040 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1041 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1042 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 1043 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1044 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1045 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1046 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1047 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1048 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 1049 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1050 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1051 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1052 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1053 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1054 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 1055 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1056 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1057 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1058 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1059 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1060 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1061 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 1062 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1063 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1064 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1065 | ] 1066 | rich = [ 1067 | {file = "rich-13.3.4-py3-none-any.whl", hash = "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a"}, 1068 | {file = "rich-13.3.4.tar.gz", hash = "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"}, 1069 | ] 1070 | scylla-driver = [ 1071 | {file = "scylla-driver-3.26.0.tar.gz", hash = "sha256:1f45c7a4cbda26853d7b55a5c7ec0c1f6cbd300ac20a12fb5085110b2de5f616"}, 1072 | {file = "scylla_driver-3.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccbc07bfd0771fdf02da3d1c06b7e818e42d3f5df4f602a2667fe1ae153b09b"}, 1073 | {file = "scylla_driver-3.26.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5311149d980112030d89b5106e84006c6f5e73d3178c227ddd7f216bb078053e"}, 1074 | {file = "scylla_driver-3.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd5fe13e520cff68eb79cf1ac426f3640454e3cc3575bf0ac4cb845d98204b82"}, 1075 | {file = "scylla_driver-3.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e883903f1724f4984dedb38d42646c893f7d86b0fffd49ac513d38d145e8add"}, 1076 | {file = "scylla_driver-3.26.0-cp310-cp310-win32.whl", hash = "sha256:b11e0591c8ea94ab12b95633ef7fb7e497a1956d7b3745a7217aeb0758106cc0"}, 1077 | {file = "scylla_driver-3.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:61cb00582aa77cc259353c5906b4fd2d53b4ec82abc9ca93b72ba297768ba71a"}, 1078 | {file = "scylla_driver-3.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d8a906fb5958ca12aa1f7802cb5fa06a00492b2bdd33d53c919106f711b0a34"}, 1079 | {file = "scylla_driver-3.26.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93b74a1e6392184e078d5003e65367aee459a25604620e3ae3bd60b5df17dd78"}, 1080 | {file = "scylla_driver-3.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c192e0620386e4438902354837c4615524ee816a3e2c1e8ee472ed9e8e20fe43"}, 1081 | {file = "scylla_driver-3.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d633da18d021452992c21ffaeebad2f7e085f827fdf851baf233c7c4ae82ff1"}, 1082 | {file = "scylla_driver-3.26.0-cp311-cp311-win32.whl", hash = "sha256:7998954f69b04e2a195f205f6277dd6441d7c43d180532f682449af92c338d47"}, 1083 | {file = "scylla_driver-3.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:562313ccd817aa345769d38fe284d82cf366ccabb752236bd105c7f3819890fe"}, 1084 | {file = "scylla_driver-3.26.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0aa3136af2b2371cc6cf0b3ccbf725d6f6a6f3b556b02cdcf48344f42d6c34a6"}, 1085 | {file = "scylla_driver-3.26.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4635e61bc5ca64f9adbf8dabf10cec7da2035c90aa041b3ec00c65aed2f5426a"}, 1086 | {file = "scylla_driver-3.26.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2c5d57578aa5381736a81a7d5dcac8e82bfd0441aba13adcecba6eeccc70fc"}, 1087 | {file = "scylla_driver-3.26.0-cp37-cp37m-win32.whl", hash = "sha256:2ec5f5fbb36652603d4db67ce1e02b967756c54d176be56e8a7f9d37211bcdf2"}, 1088 | {file = "scylla_driver-3.26.0-cp37-cp37m-win_amd64.whl", hash = "sha256:065fcad633ade0312cbde3918fff9150b9022e357c6b2d0ba11469c627b37e67"}, 1089 | {file = "scylla_driver-3.26.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bd2b23d6c87a40a8fccc7883281ba97591f26986fe54cac72ccde16ef709bc8"}, 1090 | {file = "scylla_driver-3.26.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceae17e7074dae999577e7ab61060f9cbdbadb6910590e98e8b343c7eb744700"}, 1091 | {file = "scylla_driver-3.26.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6746aa5014f5901dd904f306cfc1c0324c9780ce56ed6c56026d0533b7897d97"}, 1092 | {file = "scylla_driver-3.26.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0be45c2af6e4025a2d08897f3a12755d75924bbc562509f298ae614461a60cc3"}, 1093 | {file = "scylla_driver-3.26.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f79d969d4d617751e70acdec2e29b0cf0d92a8866d85ea23e4d8d609177382ca"}, 1094 | {file = "scylla_driver-3.26.0-cp38-cp38-win32.whl", hash = "sha256:e30515c05ec17e3fe14e22dfec198597d45d2a31922824c73e0677ac1a1a06ec"}, 1095 | {file = "scylla_driver-3.26.0-cp38-cp38-win_amd64.whl", hash = "sha256:599c2c02ae187c632470a9af94f457e247e9456fcbb4ad8e85099a3a1ec51701"}, 1096 | {file = "scylla_driver-3.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6165891e1b84b44e7e89876f0b56f6404d6f3f8aa15bf80effa4aa53ada5f57c"}, 1097 | {file = "scylla_driver-3.26.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e133940782da814546cecf5225fb092d3d94200417a6d3dfeac29156e8f2cbf"}, 1098 | {file = "scylla_driver-3.26.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:614a2530cbcc8b4a5c21a8654b0236a0266b6fcf9e345f3a0017a6d243fe8dad"}, 1099 | {file = "scylla_driver-3.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd8f7ee5c0348f3ee825ba6f9fdff2aadc1b5955aeecdb5b6de2db39e6fdb7a"}, 1100 | {file = "scylla_driver-3.26.0-cp39-cp39-win32.whl", hash = "sha256:e4563f3c2f7f0bbf062610f5356617bafc2a66cfd1459ca7187d1b3917ab5b0e"}, 1101 | {file = "scylla_driver-3.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:965d739f8dd3f1dd46acdcd4a01f47bdb2933d345e80acdb81b0884538d12d3a"}, 1102 | {file = "scylla_driver-3.26.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0adc2dbff9f09f3e152f6b9052e0a248e08548e079918e20ebb14c57e485c72c"}, 1103 | {file = "scylla_driver-3.26.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30e38ccab91bc8808b6d7fcc0a1d8f5682bd73673e17dd22804051e9414e3f94"}, 1104 | {file = "scylla_driver-3.26.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d0f22830c1f86d361d1fa583e7472b3dc643c269bfe0733b1f4ef5ffb7b182"}, 1105 | {file = "scylla_driver-3.26.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c48b260298861d234b9eef8f4c8756d555b3d991145527e6c2e7b42ec02d9aad"}, 1106 | {file = "scylla_driver-3.26.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaaa57f09a4234fe9d755bfeb370b9361fa3f2ad52cd7181ff2ab61521e3e361"}, 1107 | {file = "scylla_driver-3.26.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56ee189a29b781ee609e373f36b310fcfe84ba142ee3ea3813ac420e4479e443"}, 1108 | {file = "scylla_driver-3.26.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e2b5c653c9ff20cf6ba68f776a51d2e3b93c5c383db9f5bf6bf2895c438618"}, 1109 | {file = "scylla_driver-3.26.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ce0333b4a1a1621533fcbf8a4108f3a0c8b0b68bc2a9662e7e43432ad1fc64c2"}, 1110 | {file = "scylla_driver-3.26.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5ed544d7fadf0f3d73f42844b1fd9e9600caee04e999f21d31e583847b5d9d5e"}, 1111 | {file = "scylla_driver-3.26.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c8f70eaa34476ed2abe7fb055dbb3b56b26d2d418a4b66c949d48ceb59fa21e"}, 1112 | {file = "scylla_driver-3.26.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a79423daad489487e480489bdb943fda9a3c484003318f9d5017905785fc50"}, 1113 | {file = "scylla_driver-3.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:453220816e8b1868c8b6ac9e260c713650c0c9dc4c24b1f71feea13300fd9dd0"}, 1114 | ] 1115 | six = [ 1116 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1117 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1118 | ] 1119 | smmap = [ 1120 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 1121 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 1122 | ] 1123 | snowballstemmer = [ 1124 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 1125 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 1126 | ] 1127 | sqlparse = [ 1128 | {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, 1129 | {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, 1130 | ] 1131 | stevedore = [ 1132 | {file = "stevedore-5.0.0-py3-none-any.whl", hash = "sha256:bd5a71ff5e5e5f5ea983880e4a1dd1bb47f8feebbb3d95b592398e2f02194771"}, 1133 | {file = "stevedore-5.0.0.tar.gz", hash = "sha256:2c428d2338976279e8eb2196f7a94910960d9f7ba2f41f3988511e95ca447021"}, 1134 | ] 1135 | tomli = [ 1136 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1137 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1138 | ] 1139 | tox = [ 1140 | {file = "tox-4.4.12-py3-none-any.whl", hash = "sha256:d4be558809d86fad13f4553976b0500352630a8fbfa39ea4b1ce3bd945ba680b"}, 1141 | {file = "tox-4.4.12.tar.gz", hash = "sha256:740f5209d0dec19451b951ee5b1cce4a207acdc7357af84dbc8ec35bcf2c454e"}, 1142 | ] 1143 | typing-extensions = [ 1144 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, 1145 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, 1146 | ] 1147 | tzdata = [ 1148 | {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, 1149 | {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, 1150 | ] 1151 | virtualenv = [ 1152 | {file = "virtualenv-20.22.0-py3-none-any.whl", hash = "sha256:48fd3b907b5149c5aab7c23d9790bea4cac6bc6b150af8635febc4cfeab1275a"}, 1153 | {file = "virtualenv-20.22.0.tar.gz", hash = "sha256:278753c47aaef1a0f14e6db8a4c5e1e040e90aea654d0fc1dc7e0d8a42616cc3"}, 1154 | ] 1155 | --------------------------------------------------------------------------------