diff --git a/.github/workflows/code.yaml b/.github/workflows/code.yaml index 1aa6e8b..b1e8882 100644 --- a/.github/workflows/code.yaml +++ b/.github/workflows/code.yaml @@ -14,8 +14,8 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.6, 3.7, 3.8] - django-version: ["~=2.0.0", "~=2.1.0", "~=2.2.0", "~=3.0.0"] + python-version: [3.6, 3.7, 3.8, 3.9] + django-version: ["~=2.0.0", "~=2.1.0", "~=2.2.0", "~=3.0.0", "~=3.1.0"] services: postgres: image: postgres:latest diff --git a/README.rst b/README.rst index 4e1182f..657cc3d 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,39 @@ Django project. It is a fork of `django-tenants`_ with some conceptual changes: .. _django-tenants: https://github.com/tomturner/django-tenants +Which package to use? +--------------------- + +There are currently multiple packages to handle multi-tenancy via PostgreSQL schemas. +This table should help you make an informed decision on which one to choose. + +.. list-table:: + :widths: 50 50 + :header-rows: 1 + + * - Package + - Features + * - `django-tenant-schemas`_ + - Original project. + Now active and maintained by `@goodtune`_. + * - `django-tenants`_ + - Active and maintained by `@tomturner`_. + Built on top of `django-tenant-schemas`_. + Uses a ``Domain`` model for allowing multiple domains per tenant. + Allows for parallel migrations with custom migration executor. + Other multiple improvements. + * - `django-pgschemas`_ + - Active and maintained by `@lorinkoz`_. + Built on top of `django-tenants`_. + Different philosphy for tenants. + Other improvements listed above. + +.. _django-tenants-schemas: https://github.com/bernardopires/django-tenant-schemas +.. _@goodtune: https://github.com/goodtune +.. _django-tenants: https://github.com/tomturner/django-tenants +.. _@tomturner: https://github.com/tomturner +.. _django-pgschemas: https://github.com/lorinkoz/django-pgschemas +.. _@lorinkoz: https://github.com/lorinkoz Documentation ------------- diff --git a/django_pgschemas/__init__.py b/django_pgschemas/__init__.py index 7070478..a0cf6d4 100644 --- a/django_pgschemas/__init__.py +++ b/django_pgschemas/__init__.py @@ -1 +1,3 @@ +from .schema import schema_handler + default_app_config = "django_pgschemas.apps.DjangoPGSchemasConfig" diff --git a/django_pgschemas/apps.py b/django_pgschemas/apps.py index 3cf4e8b..756f0c1 100644 --- a/django_pgschemas/apps.py +++ b/django_pgschemas/apps.py @@ -17,10 +17,6 @@ def _check_tenant_dict(self): def _check_public_schema(self): if not isinstance(settings.TENANTS.get("public"), dict): raise ImproperlyConfigured("TENANTS must contain a 'public' dict.") - if "TENANT_MODEL" not in settings.TENANTS["public"]: - raise ImproperlyConfigured("TENANTS['public'] must contain a 'TENANT_MODEL' key.") - if "DOMAIN_MODEL" not in settings.TENANTS["public"]: - raise ImproperlyConfigured("TENANTS['public'] must contain a 'DOMAIN_MODEL' key.") if "URLCONF" in settings.TENANTS["public"]: raise ImproperlyConfigured("TENANTS['public'] cannot contain a 'URLCONF' key.") if "WS_URLCONF" in settings.TENANTS["public"]: @@ -33,6 +29,10 @@ def _check_public_schema(self): def _check_default_schemas(self): if not isinstance(settings.TENANTS.get("default"), dict): raise ImproperlyConfigured("TENANTS must contain a 'default' dict.") + if "TENANT_MODEL" not in settings.TENANTS["default"]: + raise ImproperlyConfigured("TENANTS['default'] must contain a 'TENANT_MODEL' key.") + if "DOMAIN_MODEL" not in settings.TENANTS["default"]: + raise ImproperlyConfigured("TENANTS['default'] must contain a 'DOMAIN_MODEL' key.") if "URLCONF" not in settings.TENANTS["default"]: raise ImproperlyConfigured("TENANTS['default'] must contain a 'URLCONF' key.") if "DOMAINS" in settings.TENANTS["default"]: @@ -43,7 +43,7 @@ def _check_default_schemas(self): "CLONE_REFERENCE" in settings.TENANTS["default"] and settings.TENANTS["default"]["CLONE_REFERENCE"] in settings.TENANTS ): - raise ImproperlyConfigured("TENANTS['default']['CLONE_REFERENCE'] must be a unique schema name") + raise ImproperlyConfigured("TENANTS['default']['CLONE_REFERENCE'] must be a unique schema name.") def _check_overall_schemas(self): for schema in settings.TENANTS: diff --git a/django_pgschemas/cache.py b/django_pgschemas/contrib/cache.py similarity index 76% rename from django_pgschemas/cache.py rename to django_pgschemas/contrib/cache.py index 243fb5f..62c259a 100644 --- a/django_pgschemas/cache.py +++ b/django_pgschemas/contrib/cache.py @@ -1,4 +1,4 @@ -from django.db import connection +from ..schema import schema_handler def make_key(key, key_prefix, version): @@ -8,7 +8,7 @@ def make_key(key, key_prefix, version): Constructs the key used by all other methods. Prepends the tenant `schema_name` and `key_prefix'. """ - return "%s:%s:%s:%s" % (connection.schema.schema_name, key_prefix, version, key) + return "%s:%s:%s:%s" % (schema_handler.active.schema_name, key_prefix, version, key) def reverse_key(key): diff --git a/django_pgschemas/contrib/channels/auth.py b/django_pgschemas/contrib/channels/auth.py index ae64035..afcb192 100644 --- a/django_pgschemas/contrib/channels/auth.py +++ b/django_pgschemas/contrib/channels/auth.py @@ -1,11 +1,10 @@ +from channels.auth import AuthMiddleware, CookieMiddleware, SessionMiddleware, _get_user_session_key, login, logout +from channels.db import database_sync_to_async from django.conf import settings from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, load_backend from django.contrib.auth.models import AnonymousUser from django.utils.crypto import constant_time_compare -from channels.auth import login, logout, CookieMiddleware, SessionMiddleware, AuthMiddleware, _get_user_session_key -from channels.db import database_sync_to_async - @database_sync_to_async def get_user(scope): diff --git a/django_pgschemas/contrib/channels/router.py b/django_pgschemas/contrib/channels/router.py index e932f2a..5cd2407 100644 --- a/django_pgschemas/contrib/channels/router.py +++ b/django_pgschemas/contrib/channels/router.py @@ -1,12 +1,11 @@ +from channels.routing import ProtocolTypeRouter, URLRouter from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.encoding import force_text from django.utils.module_loading import import_string -from channels.routing import ProtocolTypeRouter, URLRouter - from ...schema import SchemaDescriptor -from ...utils import remove_www, get_tenant_model, get_domain_model +from ...utils import get_domain_model, get_tenant_model, remove_www from .auth import TenantAuthMiddlewareStack diff --git a/django_pgschemas/contrib/files/storage.py b/django_pgschemas/contrib/files/storage.py index 2ba02c5..3eac433 100644 --- a/django_pgschemas/contrib/files/storage.py +++ b/django_pgschemas/contrib/files/storage.py @@ -2,7 +2,8 @@ from django.conf import settings from django.core.files.storage import FileSystemStorage -from django.db import connection + +from ...schema import schema_handler class TenantFileSystemStorage(FileSystemStorage): @@ -12,13 +13,13 @@ class TenantFileSystemStorage(FileSystemStorage): """ def get_schema_path_identifier(self): - if not connection.schema: + if not schema_handler.active: return "" - path_identifier = connection.schema.schema_name - if hasattr(connection.schema, "schema_pathname"): - path_identifier = connection.schema.schema_pathname() + path_identifier = schema_handler.active.schema_name + if hasattr(schema_handler.active, "schema_pathname"): + path_identifier = schema_handler.active.schema_pathname() elif hasattr(settings, "PGSCHEMAS_PATHNAME_FUNCTION"): - path_identifier = settings.PGSCHEMAS_PATHNAME_FUNCTION(connection.schema) + path_identifier = settings.PGSCHEMAS_PATHNAME_FUNCTION(schema_handler.active) return path_identifier @property # To avoid caching of tenant @@ -44,7 +45,7 @@ def base_url(self): appended. """ url_folder = self.get_schema_path_identifier() - if url_folder and connection.schema and connection.schema.folder: + if url_folder and schema_handler.active and schema_handler.active.folder: # Since we're already prepending all URLs with schema, there is no # need to make the differentiation here url_folder = "" diff --git a/django_pgschemas/log.py b/django_pgschemas/log.py index 430ae3a..7ba5a41 100644 --- a/django_pgschemas/log.py +++ b/django_pgschemas/log.py @@ -1,6 +1,6 @@ import logging -from django.db import connection +from .schema import schema_handler class SchemaContextFilter(logging.Filter): @@ -9,6 +9,6 @@ class SchemaContextFilter(logging.Filter): """ def filter(self, record): - record.schema_name = connection.schema.schema_name - record.domain_url = connection.schema.domain_url + record.schema_name = schema_handler.active.schema_name + record.domain_url = schema_handler.active.domain_url return True diff --git a/django_pgschemas/management/commands/__init__.py b/django_pgschemas/management/commands/__init__.py index 44e6fab..08dad27 100644 --- a/django_pgschemas/management/commands/__init__.py +++ b/django_pgschemas/management/commands/__init__.py @@ -1,11 +1,10 @@ from django.conf import settings from django.core.management.base import BaseCommand, CommandError -from django.db.models import CharField, Q -from django.db.models import Value as V +from django.db.models import CharField, Q, Value as V from django.db.models.functions import Concat from django.db.utils import ProgrammingError -from ...schema import SchemaDescriptor +from ...schema import schema_handler from ...utils import create_schema, dynamic_models_exist, get_clone_reference, get_tenant_model from ._executors import parallel, sequential @@ -67,11 +66,13 @@ def add_arguments(self, parser): help="Schema(s) to exclude when executing the current command", ) parser.add_argument( - "--executor", - dest="executor", - default="sequential", - choices=EXECUTORS, - help="Executor to be used for running command on schemas", + "--sdb", nargs="?", dest="schema_database", default="default", help="Database to operate with the schema(s)" + ) + parser.add_argument( + "--parallel", + dest="parallel", + action="store_true", + help="Run command in parallel mode", ) parser.add_argument( "--no-create-schemas", @@ -81,6 +82,7 @@ def add_arguments(self, parser): ) def get_schemas_from_options(self, **options): + database = options.get("database") or options.get("schema_database") skip_schema_creation = options.get("skip_schema_creation", False) try: schemas = self._get_schemas_from_options(**options) @@ -97,16 +99,17 @@ def get_schemas_from_options(self, **options): raise CommandError("This command can only run in %s" % self.specific_schemas) if not skip_schema_creation: for schema in schemas: - create_schema(schema, check_if_exists=True, sync_schema=False, verbosity=0) + create_schema(schema, database, check_if_exists=True, sync_schema=False, verbosity=0) return schemas def get_executor_from_options(self, **options): - return EXECUTORS[options.get("executor")] + return EXECUTORS["parallel"] if options.get("parallel") else EXECUTORS["sequential"] def get_scope_display(self): return "|".join(self.specific_schemas or []) or self.scope def _get_schemas_from_options(self, **options): + database = options.get("database") or options.get("schema_database") schemas = options.get("schemas") or [] excluded_schemas = options.get("excluded_schemas") or [] include_all_schemas = options.get("all_schemas") or False @@ -140,9 +143,19 @@ def _get_schemas_from_options(self, **options): raise CommandError("No schema provided") TenantModel = get_tenant_model() - static_schemas = [x for x in settings.TENANTS.keys() if x != "default"] if allow_static else [] + static_schemas = ( + [ + x + for x in settings.TENANTS.keys() + if x != "default" and database in (settings.TENANTS[x].get("DATABASES") or ["default"]) + ] + if allow_static + else [] + ) dynamic_schemas = ( - TenantModel.objects.values_list("schema_name", flat=True) if dynamic_ready and allow_dynamic else [] + [x.schema_name for x in TenantModel.objects.all() if x.get_database() == database] + if dynamic_ready and allow_dynamic + else [] ) if clone_reference and allow_static: static_schemas.append(clone_reference) @@ -173,8 +186,10 @@ def _get_schemas_from_options(self, **options): schemas_to_return.add(schema) elif schema == clone_reference: schemas_to_return.add(schema) - elif dynamic_ready and TenantModel.objects.filter(schema_name=schema).exists() and allow_dynamic: - schemas_to_return.add(schema) + elif dynamic_ready and allow_dynamic: + tenant = TenantModel.objects.filter(schema_name=schema).first() + if tenant and tenant.get_database() == database: + schemas_to_return.add(schema) schemas = list(set(schemas) - schemas_to_return) @@ -188,13 +203,18 @@ def _get_schemas_from_options(self, **options): and any([x for x in data["DOMAINS"] if x.startswith(schema)]) ] if dynamic_ready and allow_dynamic: - local += ( - TenantModel.objects.annotate( - route=Concat("domains__domain", V("/"), "domains__folder", output_field=CharField()) - ) - .filter(Q(schema_name=schema) | Q(domains__domain__istartswith=schema) | Q(route=schema)) - .distinct() - .values_list("schema_name", flat=True) + local += list( + { + x.schema_name + for x in ( + TenantModel.objects.annotate( + route=Concat("domains__domain", V("/"), "domains__folder", output_field=CharField()) + ) + .filter(Q(schema_name=schema) | Q(domains__domain__istartswith=schema) | Q(route=schema)) + .distinct() + ) + if x.get_database() == database + } ) if not local: raise CommandError("No schema found for '%s'" % schema) @@ -216,13 +236,18 @@ def _get_schemas_from_options(self, **options): if schema_name not in ["public", "default", clone_reference] and any([x for x in data["DOMAINS"] if x.startswith(schema)]) ] - local += ( - TenantModel.objects.annotate( - route=Concat("domains__domain", V("/"), "domains__folder", output_field=CharField()) - ) - .filter(Q(schema_name=schema) | Q(domains__domain__istartswith=schema) | Q(route=schema)) - .distinct() - .values_list("schema_name", flat=True) + local += list( + { + x.schema_name + for x in ( + TenantModel.objects.annotate( + route=Concat("domains__domain", V("/"), "domains__folder", output_field=CharField()) + ) + .filter(Q(schema_name=schema) | Q(domains__domain__istartswith=schema) | Q(route=schema)) + .distinct() + ) + if x.get_database() == database + } ) if not local: raise CommandError("No schema found for '%s' (excluded)" % schema) @@ -248,18 +273,8 @@ def handle(self, *args, **options): executor(schemas, self, "_raw_handle_tenant", args, options, pass_schema_in_kwargs=True) def _raw_handle_tenant(self, *args, **kwargs): - schema_name = kwargs.pop("schema_name") - if schema_name in settings.TENANTS: - domains = settings.TENANTS[schema_name].get("DOMAINS", []) - tenant = SchemaDescriptor.create(schema_name=schema_name, domain_url=domains[0] if domains else None) - self.handle_tenant(tenant, *args, **kwargs) - elif schema_name == get_clone_reference(): - tenant = SchemaDescriptor.create(schema_name=schema_name) - self.handle_tenant(tenant, *args, **kwargs) - else: - TenantModel = get_tenant_model() - tenant = TenantModel.objects.get(schema_name=schema_name) - self.handle_tenant(tenant, *args, **kwargs) + kwargs.pop("schema_name") + self.handle_tenant(schema_handler.active, *args, **kwargs) def handle_tenant(self, tenant, *args, **options): pass diff --git a/django_pgschemas/management/commands/_executors.py b/django_pgschemas/management/commands/_executors.py index e179edc..0ce24d6 100644 --- a/django_pgschemas/management/commands/_executors.py +++ b/django_pgschemas/management/commands/_executors.py @@ -3,8 +3,11 @@ from django.conf import settings from django.core.management import call_command -from django.core.management.base import BaseCommand, OutputWrapper, CommandError -from django.db import connection, transaction, connections +from django.core.management.base import BaseCommand, OutputWrapper +from django.db import connection, connections, transaction + +from ...schema import SchemaDescriptor, schema_handler +from ...utils import get_clone_reference, get_tenant_model def run_on_schema( @@ -52,7 +55,17 @@ def __call__(self, message): if fork_db: connections.close_all() - connection.set_schema_to(schema_name) + + if schema_name in settings.TENANTS: + domains = settings.TENANTS[schema_name].get("DOMAINS", []) + schema = SchemaDescriptor.create(schema_name=schema_name, domain_url=domains[0] if domains else None) + elif schema_name == get_clone_reference(): + schema = SchemaDescriptor.create(schema_name=schema_name) + else: + TenantModel = get_tenant_model() + schema = TenantModel.objects.get(schema_name=schema_name) + + schema_handler.set_schema(schema) if pass_schema_in_kwargs: kwargs.update({"schema_name": schema_name}) diff --git a/django_pgschemas/management/commands/cloneschema.py b/django_pgschemas/management/commands/cloneschema.py index b478895..06c2614 100644 --- a/django_pgschemas/management/commands/cloneschema.py +++ b/django_pgschemas/management/commands/cloneschema.py @@ -1,11 +1,10 @@ import os - from distutils.util import strtobool from django.core.checks import Tags, run_checks from django.core.management.base import BaseCommand, CommandError -from ...utils import get_tenant_model, get_domain_model, clone_schema +from ...utils import clone_schema, get_domain_model, get_tenant_model class Command(BaseCommand): @@ -20,6 +19,9 @@ def add_arguments(self, parser): super().add_arguments(parser) parser.add_argument("source", help="The name of the schema you want to clone") parser.add_argument("destination", help="The name of the schema you want to create as clone") + parser.add_argument( + "--sdb", nargs="?", dest="schema_database", default="default", help="Database to operate with the schema(s)" + ) parser.add_argument( "--noinput", "--no-input", @@ -105,7 +107,7 @@ def handle(self, *args, **options): if TenantModel.objects.filter(schema_name=options["source"]).exists(): tenant, domain = self.get_dynamic_tenant(**options) try: - clone_schema(options["source"], options["destination"], dry_run) + clone_schema(options["source"], options["destination"], options["schema_database"], dry_run) if tenant and domain: if options["verbosity"] >= 1: self.stdout.write("Schema cloned.") diff --git a/django_pgschemas/management/commands/createrefschema.py b/django_pgschemas/management/commands/createrefschema.py index 45a3973..221883d 100644 --- a/django_pgschemas/management/commands/createrefschema.py +++ b/django_pgschemas/management/commands/createrefschema.py @@ -1,7 +1,8 @@ +from django.conf import settings from django.core.checks import Tags, run_checks from django.core.management.base import BaseCommand, CommandError -from ...utils import get_clone_reference, create_schema, drop_schema +from ...utils import create_schema, drop_schema, get_clone_reference class Command(BaseCommand): @@ -20,18 +21,22 @@ def handle(self, *args, **options): clone_reference = get_clone_reference() if not clone_reference: raise CommandError("There is no reference schema configured.") - if options.get("recreate", False): - drop_schema(clone_reference, check_if_exists=True, verbosity=options["verbosity"]) + for database in settings.DATABASES: + if options.get("recreate", False): + drop_schema(clone_reference, database=database, check_if_exists=True, verbosity=options["verbosity"]) + if options["verbosity"] >= 1: + self.stdout.write(f"[{database}] Destroyed existing reference schema.") + created = create_schema( + clone_reference, database=database, check_if_exists=True, verbosity=options["verbosity"] + ) if options["verbosity"] >= 1: - self.stdout.write("Destroyed existing reference schema.") - created = create_schema(clone_reference, check_if_exists=True, verbosity=options["verbosity"]) - if options["verbosity"] >= 1: - if created: - self.stdout.write("Reference schema successfully created!") - else: - self.stdout.write("Reference schema already exists.") - self.stdout.write( - self.style.WARNING( - "Run this command again with --recreate if you want to recreate the reference schema." + if created: + self.stdout.write(f"[{database}] Reference schema successfully created!") + else: + self.stdout.write(f"[{database}] Reference schema already exists.") + self.stdout.write( + self.style.WARNING( + f"[{database}] Run this command again with --recreate if you want to " + "recreate the reference schema." + ) ) - ) diff --git a/django_pgschemas/management/commands/migrateschema.py b/django_pgschemas/management/commands/migrateschema.py index df8d32d..305bb19 100644 --- a/django_pgschemas/management/commands/migrateschema.py +++ b/django_pgschemas/management/commands/migrateschema.py @@ -12,6 +12,7 @@ class NonInteractiveRunSchemaCommand(RunSchemaCommand): class MigrateSchemaCommand(WrappedSchemaOption, BaseCommand): + requires_system_checks = False allow_interactive = False def _run_checks(self, **kwargs): diff --git a/django_pgschemas/management/commands/runschema.py b/django_pgschemas/management/commands/runschema.py index 3e5b5e6..ca48e25 100644 --- a/django_pgschemas/management/commands/runschema.py +++ b/django_pgschemas/management/commands/runschema.py @@ -75,7 +75,8 @@ def handle(self, *args, **options): options.pop("static_schemas") options.pop("dynamic_schemas") options.pop("tenant_schemas") - options.pop("executor") + options.pop("schema_database") + options.pop("parallel") options.pop("skip_schema_creation") if self.allow_interactive: options.pop("interactive") diff --git a/django_pgschemas/middleware.py b/django_pgschemas/middleware.py index f47b0af..0dd61b7 100644 --- a/django_pgschemas/middleware.py +++ b/django_pgschemas/middleware.py @@ -1,13 +1,12 @@ import re from django.conf import settings -from django.db import connection from django.http import Http404 -from django.urls import set_urlconf, clear_url_caches +from django.urls import clear_url_caches, set_urlconf -from .schema import SchemaDescriptor +from .schema import SchemaDescriptor, schema_handler from .urlresolvers import get_urlconf_from_schema -from .utils import remove_www, get_domain_model +from .utils import get_domain_model, remove_www class TenantMiddleware: @@ -24,7 +23,7 @@ def __init__(self, get_response): def __call__(self, request): hostname = remove_www(request.get_host().split(":")[0]) - connection.set_schema_to_public() + schema_handler.set_schema_to_public() tenant = None @@ -74,5 +73,5 @@ def __call__(self, request): urlconf = get_urlconf_from_schema(tenant) request.urlconf = urlconf set_urlconf(urlconf) - connection.set_schema(tenant) + schema_handler.set_schema(tenant) return self.get_response(request) diff --git a/django_pgschemas/models.py b/django_pgschemas/models.py index 5a9c037..d380334 100644 --- a/django_pgschemas/models.py +++ b/django_pgschemas/models.py @@ -3,8 +3,8 @@ from .postgresql_backend.base import check_schema_name from .schema import SchemaDescriptor -from .signals import schema_post_sync, schema_needs_sync, schema_pre_drop -from .utils import schema_exists, create_or_clone_schema, drop_schema, get_domain_model +from .signals import schema_needs_sync, schema_post_sync, schema_pre_drop +from .utils import create_or_clone_schema, drop_schema, get_domain_model, schema_exists class TenantMixin(SchemaDescriptor, models.Model): @@ -50,7 +50,7 @@ def save(self, verbosity=1, *args, **kwargs): elif is_new: # Although we are not using the schema functions directly, the signal might be registered by a listener schema_needs_sync.send(sender=TenantMixin, tenant=self.serializable_fields()) - elif not is_new and self.auto_create_schema and not schema_exists(self.schema_name): + elif not is_new and self.auto_create_schema and not schema_exists(self.schema_name, self.get_database()): # Create schemas for existing models, deleting only the schema on failure try: self.create_schema(verbosity=verbosity) @@ -81,13 +81,15 @@ def create_schema(self, sync_schema=True, verbosity=1): """ Creates or clones the schema ``schema_name`` for this tenant. """ - return create_or_clone_schema(self.schema_name, sync_schema, verbosity) + return create_or_clone_schema( + self.schema_name, database=self.get_database(), sync_schema=sync_schema, verbosity=verbosity + ) def drop_schema(self): """ Drops the schema. """ - return drop_schema(self.schema_name) + return drop_schema(self.schema_name, database=self.get_database()) def get_primary_domain(self): try: @@ -103,7 +105,7 @@ class DomainMixin(models.Model): """ tenant = models.ForeignKey( - settings.TENANTS["public"]["TENANT_MODEL"], db_index=True, related_name="domains", on_delete=models.CASCADE + settings.TENANTS["default"]["TENANT_MODEL"], db_index=True, related_name="domains", on_delete=models.CASCADE ) domain = models.CharField(max_length=253, db_index=True) diff --git a/django_pgschemas/postgresql_backend/_constraints.py b/django_pgschemas/postgresql_backend/_constraints.py index ff5f340..5b24b84 100644 --- a/django_pgschemas/postgresql_backend/_constraints.py +++ b/django_pgschemas/postgresql_backend/_constraints.py @@ -1,5 +1,7 @@ from django.db.models.indexes import Index +from ..schema import schema_handler + def get_constraints(self, cursor, table_name): """ @@ -38,7 +40,7 @@ def get_constraints(self, cursor, table_name): JOIN pg_namespace AS ns ON cl.relnamespace = ns.oid WHERE ns.nspname = %s AND cl.relname = %s """, - [self.connection.schema.schema_name, table_name], + [schema_handler.active.schema_name, table_name], ) for constraint, columns, kind, used_cols, options in cursor.fetchall(): constraints[constraint] = { @@ -89,7 +91,7 @@ def get_constraints(self, cursor, table_name): ) s2 GROUP BY indexname, indisunique, indisprimary, amname, exprdef, attoptions; """, - [table_name, self.connection.schema.schema_name], + [table_name, schema_handler.active.schema_name], ) for index, columns, unique, primary, orders, type_, definition, options in cursor.fetchall(): if index not in constraints: diff --git a/django_pgschemas/postgresql_backend/base.py b/django_pgschemas/postgresql_backend/base.py index 3447cd6..28025a5 100644 --- a/django_pgschemas/postgresql_backend/base.py +++ b/django_pgschemas/postgresql_backend/base.py @@ -1,12 +1,12 @@ from importlib import import_module -import psycopg2 +import psycopg2 from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.db.utils import DatabaseError -from ..schema import SchemaDescriptor -from ..utils import get_limit_set_calls, check_schema_name +from ..schema import schema_handler +from ..utils import check_schema_name, get_limit_set_calls from .introspection import DatabaseSchemaIntrospection ORIGINAL_BACKEND = getattr(settings, "PGSCHEMAS_ORIGINAL_BACKEND", "django.db.backends.postgresql") @@ -21,43 +21,19 @@ class DatabaseWrapper(original_backend.DatabaseWrapper): Adds the capability to manipulate the search_path using set_schema """ - include_public_schema = True - def __init__(self, *args, **kwargs): - self.schema = None - self.search_path_set = None super().__init__(*args, **kwargs) # Use a patched version of the DatabaseIntrospection that only returns the table list for the # currently selected schema. self.introspection = DatabaseSchemaIntrospection(self) - self.set_schema_to_public() + schema_handler.set_schema_to_public() def close(self): - self.search_path_set = False + if schema_handler.active: + schema_handler.active.ready = False super().close() - def set_schema(self, schema_descriptor, include_public=True): - """ - Main API method to set current database schema, - but it does not actually modify the db connection. - """ - assert isinstance( - schema_descriptor, SchemaDescriptor - ), "'set_schema' must be called with a SchemaDescriptor descendant" - self.schema = schema_descriptor - self.include_public_schema = include_public - self.search_path_set = False - - def set_schema_to(self, schema_name, domain_url=None, folder=None, include_public=True): - self.set_schema(SchemaDescriptor.create(schema_name, domain_url, folder), include_public) - - def set_schema_to_public(self): - """ - Instructs to stay in the common 'public' schema. - """ - self.set_schema_to("public", include_public=False) - def _cursor(self, name=None): """ Here it happens. We hope every Django db operation using PostgreSQL @@ -71,21 +47,19 @@ def _cursor(self, name=None): # optionally limit the number of executions - under load, the execution # of `set search_path` can be quite time consuming - if (not get_limit_set_calls()) or not self.search_path_set: + if (not get_limit_set_calls()) or not schema_handler.active.ready: # Actual search_path modification for the cursor. Database will # search schemas from left to right when looking for the object # (table, index, sequence, etc.). - if not self.schema: + if not schema_handler.active: raise ImproperlyConfigured("Database schema not set. Did you forget to call set_schema()?") - check_schema_name(self.schema.schema_name) + check_schema_name(schema_handler.active.schema_name) search_paths = [] - if self.schema.schema_name == "public": + if schema_handler.active.schema_name == "public": search_paths = ["public"] - elif self.include_public_schema: - search_paths = [self.schema.schema_name, "public"] else: - search_paths = [self.schema.schema_name] + search_paths = [schema_handler.active.schema_name, "public"] search_paths.extend(EXTRA_SEARCH_PATHS) if name: @@ -102,9 +76,9 @@ def _cursor(self, name=None): try: cursor_for_search_path.execute("SET search_path = {0}".format(",".join(search_paths))) except (DatabaseError, psycopg2.InternalError): - self.search_path_set = False + schema_handler.active.ready = False else: - self.search_path_set = True + schema_handler.active.ready = True if name: cursor_for_search_path.close() return cursor diff --git a/django_pgschemas/postgresql_backend/introspection.py b/django_pgschemas/postgresql_backend/introspection.py index 03e2a77..89691ba 100644 --- a/django_pgschemas/postgresql_backend/introspection.py +++ b/django_pgschemas/postgresql_backend/introspection.py @@ -1,7 +1,8 @@ -from django.db.backends.base.introspection import TableInfo, FieldInfo +from django.db.backends.base.introspection import FieldInfo, TableInfo from django.db.backends.postgresql.introspection import DatabaseIntrospection from django.utils.encoding import force_text +from ..schema import schema_handler from . import _constraints @@ -33,7 +34,7 @@ def get_table_list(self, cursor): WHERE c.relkind IN ('r', 'v', '') AND n.nspname = '%s' AND pg_catalog.pg_table_is_visible(c.oid)""" - % self.connection.schema.schema_name + % schema_handler.active.schema_name ) return [ @@ -51,7 +52,7 @@ def get_table_description(self, cursor, table_name): SELECT column_name, is_nullable, column_default FROM information_schema.columns WHERE table_schema = %s and table_name = %s""", - [self.connection.schema.schema_name, table_name], + [schema_handler.active.schema_name, table_name], ) field_map = {line[0]: line[1:] for line in cursor.fetchall()} cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) @@ -69,7 +70,7 @@ def get_table_description(self, cursor, table_name): def get_indexes(self, cursor, table_name): # This query retrieves each index on the given table, including the # first associated field name - cursor.execute(self._get_indexes_query, [table_name, self.connection.schema.schema_name]) + cursor.execute(self._get_indexes_query, [table_name, schema_handler.active.schema_name]) indexes = {} for row in cursor.fetchall(): # row[1] (idx.indkey) is stored in the DB as an array. It comes out as @@ -103,7 +104,7 @@ def get_relations(self, cursor, table_name): LEFT JOIN pg_attribute a2 ON c2.oid = a2.attrelid AND a2.attnum = con.confkey[1] WHERE c1.relname = %s and n.nspname = %s AND con.contype = 'f'""", - [table_name, self.connection.schema.schema_name], + [table_name, schema_handler.active.schema_name], ) relations = {} for row in cursor.fetchall(): @@ -128,7 +129,7 @@ def get_key_columns(self, cursor, table_name): AND ccu.constraint_name = tc.constraint_name WHERE kcu.table_name = %s AND tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = %s """, - [table_name, self.connection.schema.schema_name], + [table_name, schema_handler.active.schema_name], ) key_columns.extend(cursor.fetchall()) return key_columns diff --git a/django_pgschemas/routers.py b/django_pgschemas/routers.py index 4b44dc3..966a5f8 100644 --- a/django_pgschemas/routers.py +++ b/django_pgschemas/routers.py @@ -1,13 +1,14 @@ from django.apps import apps from django.conf import settings -from django.db import connection -from .utils import get_tenant_database_alias +from .schema import schema_handler +from .utils import get_clone_reference class SyncRouter(object): """ A router to control which applications will be synced depending on the schema we're syncing. + It also controls database for read/write in a tenant sharding configuration. """ def app_in_list(self, app_label, app_list): @@ -15,16 +16,30 @@ def app_in_list(self, app_label, app_list): app_config_full_name = "{}.{}".format(app_config.__module__, app_config.__class__.__name__) return (app_config.name in app_list) or (app_config_full_name in app_list) + def db_for_read(self, model, **hints): + if not schema_handler.active or schema_handler.active.schema_name in ["public", get_clone_reference()]: + return None + return schema_handler.active.get_database() + + def db_for_write(self, model, **hints): + if not schema_handler.active or schema_handler.active.schema_name in ["public", get_clone_reference()]: + return None + return schema_handler.active.get_database() + def allow_migrate(self, db, app_label, model_name=None, **hints): - if db != get_tenant_database_alias() or not hasattr(connection, "schema"): + if not schema_handler.active: return False app_list = [] - if connection.schema.schema_name == "public": + databases = [] + if schema_handler.active.schema_name == "public": app_list = settings.TENANTS["public"]["APPS"] - elif connection.schema.schema_name in settings.TENANTS: - app_list = settings.TENANTS[connection.schema.schema_name]["APPS"] + databases = settings.TENANTS["public"].get("DATABASES") or ["default"] + elif schema_handler.active.schema_name in settings.TENANTS: + app_list = settings.TENANTS[schema_handler.active.schema_name]["APPS"] + databases = settings.TENANTS[schema_handler.active.schema_name].get("DATABASES") or ["default"] else: app_list = settings.TENANTS["default"]["APPS"] - if not app_list: + databases = settings.TENANTS["default"].get("DATABASES") or ["default"] + if not app_list or not databases: return None - return self.app_in_list(app_label, app_list) + return db in databases and self.app_in_list(app_label, app_list) diff --git a/django_pgschemas/schema.py b/django_pgschemas/schema.py index 89cd01a..a755609 100644 --- a/django_pgschemas/schema.py +++ b/django_pgschemas/schema.py @@ -1,4 +1,42 @@ -from django.db import connection +from asgiref.local import Local + +_active = Local() + + +class ActiveSchemaHandler: + def get_active_schema(self): + return getattr(_active, "value", None) + + def set_active_schema(self, schema): + _active.value = schema + + active = property(get_active_schema, set_active_schema) + + def set_schema(self, schema_descriptor): + """ + Main API method to set current schema. + """ + from django.contrib.contenttypes.models import ContentType + + assert isinstance( + schema_descriptor, SchemaDescriptor + ), "'set_schema' must be called with a SchemaDescriptor descendant" + + schema_descriptor.ready = False # Defines whether search path has been set + ContentType.objects.clear_cache() # Attempting to catch change of database + self.set_active_schema(schema_descriptor) + + def set_schema_to(self, schema_name, domain_url=None, folder=None): + self.set_schema(SchemaDescriptor.create(schema_name, domain_url, folder)) + + def set_schema_to_public(self): + """ + Instructs to stay in the 'public' schema. + """ + self.set_schema_to("public") + + +schema_handler = ActiveSchemaHandler() class SchemaDescriptor(object): @@ -23,8 +61,8 @@ def activate(self): Usage: some_schema_descriptor.activate() """ - self.previous_schema = connection.schema - connection.set_schema(self) + self.previous_schema = schema_handler.active + schema_handler.set_schema(self) def deactivate(self): """ @@ -34,7 +72,7 @@ def deactivate(self): some_schema_descriptor.deactivate() """ previous_schema = getattr(self, "previous_schema", None) - connection.set_schema(previous_schema) if previous_schema else connection.set_schema_to_public() + schema_handler.set_schema(previous_schema) if previous_schema else schema_handler.set_schema_to_public() @staticmethod def deactivate_all(): @@ -44,7 +82,7 @@ def deactivate_all(): Usage: SchemaDescriptor.deactivate_all() """ - connection.set_schema_to_public() + schema_handler.set_schema_to_public() def __enter__(self): self.activate() @@ -59,3 +97,9 @@ def get_primary_domain(self): if self.domain_url: return "/".join([self.domain_url, self.folder]) if self.folder else self.domain_url return None + + def get_database(self): + """ + Returns the database to use for this schema. + """ + return "default" diff --git a/django_pgschemas/signals.py b/django_pgschemas/signals.py index 2fc8957..7d382f2 100644 --- a/django_pgschemas/signals.py +++ b/django_pgschemas/signals.py @@ -17,6 +17,6 @@ def tenant_delete_callback(sender, instance, **kwargs): if not isinstance(instance, get_tenant_model()): return - if instance.auto_drop_schema and schema_exists(instance.schema_name): + if instance.auto_drop_schema and schema_exists(instance.schema_name, instance.get_database()): schema_pre_drop.send(sender=get_tenant_model(), tenant=instance.serializable_fields()) instance.drop_schema() diff --git a/django_pgschemas/test/cases.py b/django_pgschemas/test/cases.py index af794a3..9fa1326 100644 --- a/django_pgschemas/test/cases.py +++ b/django_pgschemas/test/cases.py @@ -1,9 +1,8 @@ from django.conf import settings from django.core.management import call_command -from django.db import connection from django.test import TestCase -from ..schema import SchemaDescriptor +from ..schema import SchemaDescriptor, schema_handler from ..utils import get_clone_reference, get_domain_model, get_tenant_model ALLOWED_TEST_DOMAIN = ".test.com" @@ -51,7 +50,7 @@ def setUpClass(cls): else cls.schema_name + ALLOWED_TEST_DOMAIN ) cls.tenant = SchemaDescriptor.create(schema_name=cls.schema_name, domain_url=domain) - connection.set_schema(cls.tenant) + schema_handler.set_schema(cls.tenant) cls.cls_atomics = cls._enter_atomics() try: cls.setUpTestData() @@ -62,7 +61,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): super().tearDownClass() - connection.set_schema_to_public() + schema_handler.set_schema_to_public() cls.remove_allowed_test_domain() @@ -102,7 +101,7 @@ def setUpClass(cls): cls.domain = get_domain_model()(tenant=cls.tenant, domain=tenant_domain) cls.setup_domain(cls.domain) cls.domain.save() - connection.set_schema(cls.tenant) + schema_handler.set_schema(cls.tenant) cls.cls_atomics = cls._enter_atomics() try: cls.setUpTestData() @@ -113,7 +112,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): super().tearDownClass() - connection.set_schema_to_public() + schema_handler.set_schema_to_public() cls.domain.delete() cls.tenant.delete(force_drop=True) cls.remove_allowed_test_domain() @@ -177,14 +176,14 @@ def setUpClass(cls): else: cls.setup_test_tenant_and_domain() - connection.set_schema(cls.tenant) + schema_handler.set_schema(cls.tenant) @classmethod def tearDownClass(cls): TenantModel = get_tenant_model() test_schema_name = cls.get_test_schema_name() TenantModel.objects.filter(schema_name=test_schema_name).delete() - connection.set_schema_to_public() + schema_handler.set_schema_to_public() def _fixture_teardown(self): if self.flush_data(): diff --git a/django_pgschemas/test/client.py b/django_pgschemas/test/client.py index b36c1bb..532374e 100644 --- a/django_pgschemas/test/client.py +++ b/django_pgschemas/test/client.py @@ -1,4 +1,4 @@ -from django.test import RequestFactory, Client +from django.test import Client, RequestFactory from ..middleware import TenantMiddleware diff --git a/django_pgschemas/urlresolvers.py b/django_pgschemas/urlresolvers.py index d12f2a3..54ba8c7 100644 --- a/django_pgschemas/urlresolvers.py +++ b/django_pgschemas/urlresolvers.py @@ -2,11 +2,10 @@ import sys from django.conf import settings -from django.db import connection from django.urls import URLResolver +from .schema import SchemaDescriptor, schema_handler from .utils import get_domain_model -from .schema import SchemaDescriptor class TenantPrefixPattern: @@ -17,7 +16,7 @@ def tenant_prefix(self): DomainModel = get_domain_model() try: domain = DomainModel.objects.exclude(folder="").get( - tenant__schema_name=connection.schema.schema_name, domain=connection.schema.domain_url + tenant__schema_name=schema_handler.active.schema_name, domain=schema_handler.active.domain_url ) return "{}/".format(domain.folder) except DomainModel.DoesNotExist: @@ -57,6 +56,7 @@ def get_dynamic_tenant_prefixed_urlconf(urlconf, dynamic_path): Generates a new URLConf module with all patterns prefixed with tenant. """ from types import ModuleType + from django.utils.module_loading import import_string class LazyURLConfModule(ModuleType): diff --git a/django_pgschemas/utils.py b/django_pgschemas/utils.py index 5b843a8..bef6c41 100644 --- a/django_pgschemas/utils.py +++ b/django_pgschemas/utils.py @@ -4,21 +4,17 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.core.management import call_command -from django.db import connection, transaction, ProgrammingError, DEFAULT_DB_ALIAS +from django.db import DEFAULT_DB_ALIAS, ProgrammingError, connection, transaction def get_tenant_model(require_ready=True): "Returns the tenant model." - return apps.get_model(settings.TENANTS["public"]["TENANT_MODEL"], require_ready=require_ready) + return apps.get_model(settings.TENANTS["default"]["TENANT_MODEL"], require_ready=require_ready) def get_domain_model(require_ready=True): "Returns the domain model." - return apps.get_model(settings.TENANTS["public"]["DOMAIN_MODEL"], require_ready=require_ready) - - -def get_tenant_database_alias(): - return getattr(settings, "PGSCHEMAS_TENANT_DB_ALIAS", DEFAULT_DB_ALIAS) + return apps.get_model(settings.TENANTS["default"]["DOMAIN_MODEL"], require_ready=require_ready) def get_limit_set_calls(): @@ -83,7 +79,7 @@ def wrapper(*args, **kwargs): return wrapper -def schema_exists(schema_name): +def schema_exists(schema_name, database): "Checks if a schema exists in database." sql = """ SELECT EXISTS( @@ -92,7 +88,7 @@ def schema_exists(schema_name): WHERE LOWER(nspname) = LOWER(%s) ) """ - cursor = connection.cursor() + cursor = connections[database].cursor() cursor.execute(sql, (schema_name,)) row = cursor.fetchone() if row: @@ -120,32 +116,32 @@ def dynamic_models_exist(): @run_in_public_schema -def create_schema(schema_name, check_if_exists=False, sync_schema=True, verbosity=1): +def create_schema(schema_name, database, check_if_exists=False, sync_schema=True, verbosity=1): """ Creates the schema ``schema_name``. Optionally checks if the schema already exists before creating it. Returns ``True`` if the schema was created, ``False`` otherwise. """ check_schema_name(schema_name) - if check_if_exists and schema_exists(schema_name): + if check_if_exists and schema_exists(schema_name, database): return False - cursor = connection.cursor() + cursor = connections[database].cursor() cursor.execute("CREATE SCHEMA %s" % schema_name) cursor.close() if sync_schema: - call_command("migrateschema", schemas=[schema_name], verbosity=verbosity) + call_command("migrateschema", schemas=[schema_name], database=database, verbosity=verbosity) return True @run_in_public_schema -def drop_schema(schema_name, check_if_exists=True, verbosity=1): +def drop_schema(schema_name, database, check_if_exists=True, verbosity=1): """ Drops the schema. Optionally checks if the schema already exists before dropping it. """ - if check_if_exists and not schema_exists(schema_name): + if check_if_exists and not schema_exists(schema_name, database): return False - cursor = connection.cursor() + cursor = connections[database].cursor() cursor.execute("DROP SCHEMA %s CASCADE" % schema_name) cursor.close() return True @@ -359,31 +355,31 @@ class DryRunException(Exception): pass -def _create_clone_schema_function(): +def _create_clone_schema_function(database): """ Creates a postgres function `clone_schema` that copies a schema and its contents. Will replace any existing `clone_schema` functions owned by the `postgres` superuser. """ - cursor = connection.cursor() + cursor = connections[database].cursor() cursor.execute(CLONE_SCHEMA_FUNCTION) cursor.close() @run_in_public_schema -def clone_schema(base_schema_name, new_schema_name, dry_run=False): +def clone_schema(base_schema_name, new_schema_name, database, dry_run=False): """ Creates a new schema ``new_schema_name`` as a clone of an existing schema ``base_schema_name``. """ check_schema_name(new_schema_name) - cursor = connection.cursor() + cursor = connections[database].cursor() # check if the clone_schema function already exists in the db try: cursor.execute("SELECT 'clone_schema'::regproc") except ProgrammingError: # pragma: no cover - _create_clone_schema_function() + _create_clone_schema_function(database) transaction.commit() try: @@ -397,17 +393,19 @@ def clone_schema(base_schema_name, new_schema_name, dry_run=False): cursor.close() -def create_or_clone_schema(schema_name, sync_schema=True, verbosity=1): +def create_or_clone_schema(schema_name, database, sync_schema=True, verbosity=1): """ Creates the schema ``schema_name``. Optionally checks if the schema already exists before creating it. Returns ``True`` if the schema was created, ``False`` otherwise. """ check_schema_name(schema_name) - if schema_exists(schema_name): + if schema_exists(schema_name, database): return False clone_reference = get_clone_reference() - if clone_reference and schema_exists(clone_reference) and not django_is_in_test_mode(): # pragma: no cover - clone_schema(clone_reference, schema_name) + if ( + clone_reference and schema_exists(clone_reference, database) and not django_is_in_test_mode() + ): # pragma: no cover + clone_schema(clone_reference, schema_name, database) return True - return create_schema(schema_name, sync_schema=sync_schema, verbosity=verbosity) + return create_schema(schema_name, database, sync_schema=sync_schema, verbosity=verbosity) diff --git a/docs/advanced.rst b/docs/advanced.rst index fa2abaf..b770fd8 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -4,7 +4,7 @@ Advanced configuration Fast dynamic tenant creation ---------------------------- -Every time a instance of ``settings.TENANTS["public"]["TENANT_MODEL"]`` is +Every time a instance of ``settings.TENANTS["default"]["TENANT_MODEL"]`` is created, by default, the corresponding schema is created and synchronized automatically. Depending on the number of migrations you already have in place, or the amount of time these could take, or whether you need to pre-populate the @@ -95,8 +95,6 @@ Something like this would be the proper configuration for the present case: "shared_app", # ... ], - "TENANT_MODEL": "shared_app.Client", - "DOMAIN_MODEL": "shared_app.Domain", }, "main": { "APPS": [ @@ -110,6 +108,8 @@ Something like this would be the proper configuration for the present case: "URLCONF": "main_app.urls", }, "default": { + "TENANT_MODEL": "shared_app.Client", + "DOMAIN_MODEL": "shared_app.Domain", "APPS": [ "django.contrib.auth", "django.contrib.sessions", @@ -143,7 +143,7 @@ concise synopsis of the ``runschema`` command is as follows:: usage: manage.py runschema [-s SCHEMAS [SCHEMAS ...]] [-x EXCLUDED_SCHEMAS [EXCLUDED_SCHEMAS ...]] [-as] [-ss] [-ds] [-ts] - [--executor {sequential,parallel}] + [--parallel] [--no-create-schemas] [--noinput] command_name @@ -178,8 +178,7 @@ concise synopsis of the ``runschema`` command is as follows:: Schema(s) to exclude when executing the current command - --executor {sequential,parallel} - Executor to be used for running command on schemas + --parallel Run command in parallel mode --no-create-schemas Skip automatic creation of non-existing schemas @@ -202,16 +201,9 @@ explicitly or via wildcard params, it will be asked interactively. One notable exception to this is when the option ``--noinput`` is passed, in which case the command will fail. -The executor argument accepts two options: - -:sequential: - Will run the command synchronously, one schema at a time. This is the - default executor. - -:parallel: - Will run the command asynchronously, spawning multiple threads controlled - by the setting ``PGSCHEMAS_PARALLEL_MAX_PROCESSES``. It defaults to - ``None``, in which case the number of CPUs will be used. +If ``--parallel`` is passed, the command will be run asynchronously, spawning +multiple threads controlled by the setting ``PGSCHEMAS_PARALLEL_MAX_PROCESSES``. +It defaults to ``None``, in which case the number of CPUs will be used. By default, schemas that do not exist will be created (but not synchronized), except if ``--no-create-schemas`` is passed. @@ -252,19 +244,4 @@ The base commands are: ``django_pgschemas.schema.SchemaDescriptor``. Make sure you do the appropriate type checking before accessing the tenant members, as not every tenant will be an instance of - ``settings.TENANTS["public"]["TENANT_MODEL"]``. - -Caching -------- - -In order to generate tenant aware cache keys, you can use -``django_pgschemas.cache.make_key`` as your ``KEY_FUNCTION``: - -.. code-block:: python - - CACHES = { - "default": { - # ... - "KEY_FUNCTION": "django_pgschemas.cache.make_key", - } - } + ``settings.TENANTS["default"]["TENANT_MODEL"]``. diff --git a/docs/basic.rst b/docs/basic.rst index 6cabd99..dd02fc9 100644 --- a/docs/basic.rst +++ b/docs/basic.rst @@ -65,11 +65,11 @@ Add the minimal tenant configuration. "shared_app", # ... ], - "TENANT_MODEL": "shared_app.Client", - "DOMAIN_MODEL": "shared_app.Domain", }, # ... "default": { + "TENANT_MODEL": "shared_app.Client", + "DOMAIN_MODEL": "shared_app.Domain", "APPS": [ "django.contrib.auth", "django.contrib.sessions", @@ -138,8 +138,8 @@ More static tenants can be added and routed. } Dynamic tenants need to be created through instances of -``TENANTS["public"]["TENANT_MODEL"]`` and routed through instances of -``TENANTS["public"]["DOMAIN_MODEL"]``. +``TENANTS["default"]["TENANT_MODEL"]`` and routed through instances of +``TENANTS["default"]["DOMAIN_MODEL"]``. .. code-block:: python diff --git a/docs/conf.py b/docs/conf.py index 9637d89..8586890 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,6 +18,7 @@ import os import sys + import django sys.path.insert(0, os.path.abspath("../dpgs_sandbox/")) diff --git a/docs/contrib.rst b/docs/contrib.rst index 8aed370..29c13e1 100644 --- a/docs/contrib.rst +++ b/docs/contrib.rst @@ -15,6 +15,23 @@ We're striving to maintain/increase our code coverage, but please, make sure you integration is properly tested. Proper tests will always beat meaningless 100% coverage. +Caching +------- + +In order to generate tenant aware cache keys, we provide +``django_pgschemas.contrib.cache.make_key`` which can be used as +``KEY_FUNCTION``: + +.. code-block:: python + + CACHES = { + "default": { + # ... + "KEY_FUNCTION": "django_pgschemas.contrib.cache.make_key", + } + } + + Tenant aware file system storage -------------------------------- diff --git a/docs/settings.rst b/docs/settings.rst index 8442316..69e950f 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -21,8 +21,6 @@ A sample tenant configuration is: "shared_app", # ... ], - "TENANT_MODEL": "shared_app.Client", - "DOMAIN_MODEL": "shared_app.Domain", }, "www": { "APPS": [ @@ -45,6 +43,8 @@ A sample tenant configuration is: "URLCONF": "blog_app.urls", }, "default": { + "TENANT_MODEL": "shared_app.Client", + "DOMAIN_MODEL": "shared_app.Domain", "APPS": [ "django.contrib.auth", "django.contrib.sessions", @@ -93,13 +93,6 @@ control the max number of processes the parallel executor can spawn. By default, ``None`` means that the number of CPUs will be used. -``PGSCHEMAS_TENANT_DB_ALIAS`` ------------------------------ - -Default: ``"default"`` - -The database alias where the tenant configuration is going to take place. - ``PGSCHEMAS_PATHNAME_FUNCTION`` ------------------------------- diff --git a/dpgs_sandbox/app_blog/urls.py b/dpgs_sandbox/app_blog/urls.py index b686bc2..8023e4c 100644 --- a/dpgs_sandbox/app_blog/urls.py +++ b/dpgs_sandbox/app_blog/urls.py @@ -1,7 +1,6 @@ from django.urls import path from django.views.generic import TemplateView - urlpatterns = [ path("", TemplateView.as_view(), name="blog-home"), path("entries/", TemplateView.as_view(), name="entries"), diff --git a/dpgs_sandbox/app_main/urls.py b/dpgs_sandbox/app_main/urls.py index af340b7..4c5dbd6 100644 --- a/dpgs_sandbox/app_main/urls.py +++ b/dpgs_sandbox/app_main/urls.py @@ -1,7 +1,6 @@ from django.urls import path from django.views.generic import TemplateView - urlpatterns = [ path("", TemplateView.as_view(), name="main-home"), path("register/", TemplateView.as_view(), name="register"), diff --git a/dpgs_sandbox/app_main/ws_urls.py b/dpgs_sandbox/app_main/ws_urls.py index bddd60c..cc9c8f9 100644 --- a/dpgs_sandbox/app_main/ws_urls.py +++ b/dpgs_sandbox/app_main/ws_urls.py @@ -1,7 +1,5 @@ -from django.urls import path - from channels.generic.websocket import JsonWebsocketConsumer - +from django.urls import path urlpatterns = [ path("", JsonWebsocketConsumer, name="main-ws"), diff --git a/dpgs_sandbox/app_tenants/urls.py b/dpgs_sandbox/app_tenants/urls.py index 1092a13..e41fb56 100644 --- a/dpgs_sandbox/app_tenants/urls.py +++ b/dpgs_sandbox/app_tenants/urls.py @@ -2,7 +2,6 @@ from django.http import HttpResponse from django.urls import path - urlpatterns = [ path("", lambda request: HttpResponse(), name="tenant-home"), path("profile/", lambda request: HttpResponse(), name="profile"), diff --git a/dpgs_sandbox/app_tenants/ws_urls.py b/dpgs_sandbox/app_tenants/ws_urls.py index 0ee5e3b..e39cb2c 100644 --- a/dpgs_sandbox/app_tenants/ws_urls.py +++ b/dpgs_sandbox/app_tenants/ws_urls.py @@ -1,4 +1,3 @@ from django.urls import path - urlpatterns = [] diff --git a/dpgs_sandbox/settings.py b/dpgs_sandbox/settings.py index d3fc838..41f4a17 100644 --- a/dpgs_sandbox/settings.py +++ b/dpgs_sandbox/settings.py @@ -30,8 +30,6 @@ TENANTS = { "public": { "APPS": ["shared_public", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.staticfiles"], - "TENANT_MODEL": "shared_public.Tenant", - "DOMAIN_MODEL": "shared_public.Domain", }, "www": { "APPS": ["shared_common", "app_main", "django.contrib.sessions"], @@ -46,6 +44,8 @@ "DOMAINS": ["blog.test.com"], }, "default": { + "TENANT_MODEL": "shared_public.Tenant", + "DOMAIN_MODEL": "shared_public.Domain", "APPS": ["shared_common", "app_tenants", "django.contrib.sessions"], "URLCONF": "app_tenants.urls", "WS_URLCONF": "app_tenants.ws_urls", @@ -111,7 +111,7 @@ CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", - "KEY_FUNCTION": "django_pgschemas.cache.make_key", + "KEY_FUNCTION": "django_pgschemas.contrib.cache.make_key", } } diff --git a/dpgs_sandbox/tests/test_apps.py b/dpgs_sandbox/tests/test_apps.py index 50de256..263dc43 100644 --- a/dpgs_sandbox/tests/test_apps.py +++ b/dpgs_sandbox/tests/test_apps.py @@ -3,8 +3,7 @@ from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, override_settings -settings_public = {"TENANT_MODEL": "shared_public.Tenant", "DOMAIN_MODEL": "shared_public.Domain"} -settings_default = {"URLCONF": ""} +BASE_DEFAULT = {"TENANT_MODEL": "shared_public.Tenant", "DOMAIN_MODEL": "shared_public.Domain", "URLCONF": ""} class AppConfigTestCase(TestCase): @@ -18,138 +17,167 @@ def setUp(self): @override_settings() def test_missing_tenants(self): del settings.TENANTS - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_tenant_dict() + self.assertEqual(str(ctx.exception), "TENANTS dict setting not set.") @override_settings(TENANTS=list) def test_wrong_type_tenants(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_tenant_dict() + self.assertEqual(str(ctx.exception), "TENANTS dict setting not set.") @override_settings(TENANTS={}) def test_no_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS must contain a 'public' dict.") @override_settings(TENANTS={"public": None}) def test_wrong_type_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS must contain a 'public' dict.") @override_settings(TENANTS={"public": 4}) def test_other_type_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS must contain a 'public' dict.") - @override_settings(TENANTS={"public": {"DOMAIN_MODEL": ""}}) - def test_no_tenant_model_public(self): - with self.assertRaises(ImproperlyConfigured): - self.app_config._check_public_schema() - - @override_settings(TENANTS={"public": {"TENANT_MODEL": ""}}) - def test_no_domain_model_public(self): - with self.assertRaises(ImproperlyConfigured): - self.app_config._check_public_schema() - - @override_settings(TENANTS={"public": {**settings_public, "URLCONF": ""}}) + @override_settings(TENANTS={"public": {"URLCONF": ""}}) def test_urlconf_on_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS['public'] cannot contain a 'URLCONF' key.") - @override_settings(TENANTS={"public": {**settings_public, "WS_URLCONF": ""}}) + @override_settings(TENANTS={"public": {"WS_URLCONF": ""}}) def test_wsurlconf_on_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS['public'] cannot contain a 'WS_URLCONF' key.") - @override_settings(TENANTS={"public": {**settings_public, "DOMAINS": ""}}) + @override_settings(TENANTS={"public": {"DOMAINS": ""}}) def test_domains_on_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS['public'] cannot contain a 'DOMAINS' key.") - @override_settings(TENANTS={"public": {**settings_public, "FALLBACK_DOMAINS": ""}}) + @override_settings(TENANTS={"public": {"FALLBACK_DOMAINS": ""}}) def test_fallback_domains_on_public(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_public_schema() + self.assertEqual(str(ctx.exception), "TENANTS['public'] cannot contain a 'FALLBACK_DOMAINS' key.") - @override_settings(TENANTS=settings_public) + @override_settings(TENANTS={}) def test_no_default(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS must contain a 'default' dict.") @override_settings(TENANTS={"default": None}) def test_wrong_type_default(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS must contain a 'default' dict.") @override_settings(TENANTS={"default": "wawa"}) def test_other_type_default(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: + self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS must contain a 'default' dict.") + + @override_settings(TENANTS={"default": {"DOMAIN_MODEL": ""}}) + def test_no_tenant_model_default(self): + with self.assertRaises(ImproperlyConfigured) as ctx: + self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['default'] must contain a 'TENANT_MODEL' key.") + + @override_settings(TENANTS={"default": {"TENANT_MODEL": ""}}) + def test_no_domain_model_default(self): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['default'] must contain a 'DOMAIN_MODEL' key.") - @override_settings(TENANTS={"default": {}}) + @override_settings(TENANTS={"default": {"TENANT_MODEL": None, "DOMAIN_MODEL": None}}) def test_no_urlconf_default(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['default'] must contain a 'URLCONF' key.") - @override_settings(TENANTS={"default": {**settings_default, "DOMAINS": ""}}) + @override_settings(TENANTS={"default": {**BASE_DEFAULT, "DOMAINS": ""}}) def test_domains_on_default(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['default'] cannot contain a 'DOMAINS' key.") - @override_settings(TENANTS={"default": {**settings_default, "FALLBACK_DOMAINS": ""}}) + @override_settings(TENANTS={"default": {**BASE_DEFAULT, "FALLBACK_DOMAINS": ""}}) def test_fallback_domains_on_default(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['default'] cannot contain a 'FALLBACK_DOMAINS' key.") def test_repeated_clone_reference(self): - with override_settings(TENANTS={"public": {}, "default": {**settings_default, "CLONE_REFERENCE": "public"}}): - with self.assertRaises(ImproperlyConfigured): + with override_settings(TENANTS={"public": {}, "default": {**BASE_DEFAULT, "CLONE_REFERENCE": "public"}}): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() - with override_settings(TENANTS={"default": {**settings_default, "CLONE_REFERENCE": "default"}}): - with self.assertRaises(ImproperlyConfigured): + self.assertEqual(str(ctx.exception), "TENANTS['default']['CLONE_REFERENCE'] must be a unique schema name.") + with override_settings(TENANTS={"default": {**BASE_DEFAULT, "CLONE_REFERENCE": "default"}}): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_default_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['default']['CLONE_REFERENCE'] must be a unique schema name.") def test_valid_schema_name(self): with override_settings(TENANTS={"pg_whatever": {}}): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_overall_schemas() + self.assertEqual(str(ctx.exception), "'pg_whatever' is not a valid schema name.") with override_settings(TENANTS={"&$&*": {}}): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_overall_schemas() + self.assertEqual(str(ctx.exception), "'&$&*' is not a valid schema name.") @override_settings(TENANTS={"www": {}}) def test_domains_on_others(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_overall_schemas() + self.assertEqual(str(ctx.exception), "TENANTS['www'] must contain a 'DOMAINS' list.") @override_settings(DATABASE_ROUTERS=()) def test_database_routers(self): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_complementary_settings() + self.assertEqual( + str(ctx.exception), "DATABASE_ROUTERS setting must contain 'django_pgschemas.routers.SyncRouter'." + ) def test_extra_search_paths(self): with override_settings( - TENANTS={"public": settings_public, "default": {}, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["public"] + TENANTS={"public": {}, "default": BASE_DEFAULT, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["public"] ): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_extra_search_paths() + self.assertEqual(str(ctx.exception), "Do not include 'public' on PGSCHEMAS_EXTRA_SEARCH_PATHS.") with override_settings( - TENANTS={"public": settings_public, "default": {}, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["default"] + TENANTS={"public": {}, "default": BASE_DEFAULT, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["default"] ): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_extra_search_paths() + self.assertEqual(str(ctx.exception), "Do not include 'default' on PGSCHEMAS_EXTRA_SEARCH_PATHS.") with override_settings( - TENANTS={"public": settings_public, "default": {}, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["www"] + TENANTS={"public": {}, "default": BASE_DEFAULT, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["www"] ): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_extra_search_paths() + self.assertEqual(str(ctx.exception), "Do not include 'www' on PGSCHEMAS_EXTRA_SEARCH_PATHS.") with override_settings( - TENANTS={"public": settings_public, "default": {"CLONE_REFERENCE": "sample"}, "www": {}}, + TENANTS={"public": {}, "default": {**BASE_DEFAULT, "CLONE_REFERENCE": "sample"}, "www": {}}, PGSCHEMAS_EXTRA_SEARCH_PATHS=["sample"], ): - with self.assertRaises(ImproperlyConfigured): + with self.assertRaises(ImproperlyConfigured) as ctx: self.app_config._check_extra_search_paths() + self.assertEqual(str(ctx.exception), "Do not include 'sample' on PGSCHEMAS_EXTRA_SEARCH_PATHS.") - @override_settings(TENANTS={"public": settings_public, "default": settings_default}) + @override_settings(TENANTS={"public": {}, "default": BASE_DEFAULT}) def test_all_good_here(self): self.app_config.ready() diff --git a/dpgs_sandbox/tests/test_bug_url_cache.py b/dpgs_sandbox/tests/test_bug_url_cache.py index b5fe428..d02cf0e 100644 --- a/dpgs_sandbox/tests/test_bug_url_cache.py +++ b/dpgs_sandbox/tests/test_bug_url_cache.py @@ -4,7 +4,7 @@ from django.test import TransactionTestCase, tag from django_pgschemas.test.client import TenantClient -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() diff --git a/dpgs_sandbox/tests/test_cache.py b/dpgs_sandbox/tests/test_cache.py index 8638edc..4a9f326 100644 --- a/dpgs_sandbox/tests/test_cache.py +++ b/dpgs_sandbox/tests/test_cache.py @@ -1,4 +1,4 @@ -from django_pgschemas.cache import make_key, reverse_key +from django_pgschemas.contrib.cache import make_key, reverse_key from django_pgschemas.test.cases import FastTenantTestCase diff --git a/dpgs_sandbox/tests/test_checks.py b/dpgs_sandbox/tests/test_checks.py index 37d1ff0..5aa8dcb 100644 --- a/dpgs_sandbox/tests/test_checks.py +++ b/dpgs_sandbox/tests/test_checks.py @@ -3,12 +3,11 @@ from django.core import checks from django.test import TestCase, override_settings +from django_pgschemas.checks import check_other_apps, check_principal_apps, check_schema_names, get_user_app from django_pgschemas.utils import get_tenant_model -from django_pgschemas.checks import check_principal_apps, check_other_apps, check_schema_names, get_user_app - TenantModel = get_tenant_model() -BASE_PUBLIC = {"TENANT_MODEL": "shared_public.Tenant", "DOMAIN_MODEL": "shared_public.DOMAIN"} +BASE_DEFAULT = {"TENANT_MODEL": "shared_public.Tenant", "DOMAIN_MODEL": "shared_public.DOMAIN"} class AppChecksTestCase(TestCase): @@ -20,7 +19,7 @@ def setUp(self): self.app_config = apps.get_app_config("django_pgschemas") def test_core_apps_location(self): - with override_settings(TENANTS={"public": {"APPS": [], **BASE_PUBLIC}}): + with override_settings(TENANTS={"public": {"APPS": []}, "default": BASE_DEFAULT}): errors = check_principal_apps(self.app_config) expected_errors = [ checks.Error("Your tenant app 'shared_public' must be on the 'public' schema.", id="pgschemas.W001"), @@ -28,7 +27,7 @@ def test_core_apps_location(self): ] self.assertEqual(errors, expected_errors) with override_settings( - TENANTS={"public": {"APPS": ["shared_public"], **BASE_PUBLIC}, "default": {"APPS": ["shared_public"]}} + TENANTS={"public": {"APPS": ["shared_public"]}, "default": {**BASE_DEFAULT, "APPS": ["shared_public"]}} ): errors = check_principal_apps(self.app_config) expected_errors = [ diff --git a/dpgs_sandbox/tests/test_executors.py b/dpgs_sandbox/tests/test_executors.py index dc09a4b..0bf2426 100644 --- a/dpgs_sandbox/tests/test_executors.py +++ b/dpgs_sandbox/tests/test_executors.py @@ -3,7 +3,7 @@ from django.db import connections from django.test import TransactionTestCase -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() @@ -28,10 +28,10 @@ def tearDownClass(cls): def test_all_schemas_in_sequential(self): # If there are no errors, then this test passed - management.call_command("migrate", all_schemas=True, executor="sequential", verbosity=0) + management.call_command("migrate", all_schemas=True, parallel=False, verbosity=0) connections.close_all() def test_all_schemas_in_parallel(self): # If there are no errors, then this test passed - management.call_command("migrate", all_schemas=True, executor="parallel", verbosity=0) + management.call_command("migrate", all_schemas=True, parallel=True, verbosity=0) connections.close_all() diff --git a/dpgs_sandbox/tests/test_file_storage.py b/dpgs_sandbox/tests/test_file_storage.py index 4653946..c675bd7 100644 --- a/dpgs_sandbox/tests/test_file_storage.py +++ b/dpgs_sandbox/tests/test_file_storage.py @@ -2,8 +2,8 @@ import shutil import tempfile -from django.db import connection from django.core.files.base import ContentFile +from django.db import connection from django.test import TransactionTestCase, override_settings from django_pgschemas.contrib.files import TenantFileSystemStorage diff --git a/dpgs_sandbox/tests/test_middleware.py b/dpgs_sandbox/tests/test_middleware.py index 015f826..b1b0e65 100644 --- a/dpgs_sandbox/tests/test_middleware.py +++ b/dpgs_sandbox/tests/test_middleware.py @@ -1,11 +1,11 @@ from importlib import import_module from django.http import Http404 -from django.test import TestCase, RequestFactory +from django.test import RequestFactory, TestCase from django.utils.module_loading import import_string from django_pgschemas.middleware import TenantMiddleware -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() diff --git a/dpgs_sandbox/tests/test_protocol_router.py b/dpgs_sandbox/tests/test_protocol_router.py index 0284384..c2d52b1 100644 --- a/dpgs_sandbox/tests/test_protocol_router.py +++ b/dpgs_sandbox/tests/test_protocol_router.py @@ -1,7 +1,7 @@ from django.test import TestCase from django_pgschemas.contrib.channels import TenantProtocolRouter -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() diff --git a/dpgs_sandbox/tests/test_schema_creation_commands.py b/dpgs_sandbox/tests/test_schema_creation_commands.py index b9ea7b8..dade765 100644 --- a/dpgs_sandbox/tests/test_schema_creation_commands.py +++ b/dpgs_sandbox/tests/test_schema_creation_commands.py @@ -21,29 +21,29 @@ def test_cloneschema(self): @utils.run_in_public_schema def fixup(): - utils._create_clone_schema_function() + utils._create_clone_schema_function("default") fixup() - self.assertFalse(utils.schema_exists("cloned")) + self.assertFalse(utils.schema_exists("cloned", "default")) call_command("cloneschema", "sample", "cloned", verbosity=0) # All good - self.assertTrue(utils.schema_exists("cloned")) + self.assertTrue(utils.schema_exists("cloned", "default")) with self.assertRaises(CommandError): # Existing destination call_command("cloneschema", "sample", "cloned", verbosity=0) with self.assertRaises(CommandError): # Not existing source call_command("cloneschema", "nonexisting", "newschema", verbosity=0) - utils.drop_schema("cloned") + utils.drop_schema("cloned", "default") def test_createrefschema(self): "Tests 'createrefschema' command" - utils.drop_schema("cloned") + utils.drop_schema("cloned", "default") call_command("createrefschema", verbosity=0) # All good - self.assertTrue(utils.schema_exists("sample")) - utils.drop_schema("cloned") + self.assertTrue(utils.schema_exists("sample", "default")) + utils.drop_schema("cloned", "default") call_command("createrefschema", recreate=True, verbosity=0) # All good too - self.assertTrue(utils.schema_exists("sample")) - utils.drop_schema("cloned") + self.assertTrue(utils.schema_exists("sample", "default")) + utils.drop_schema("cloned", "default") call_command("createrefschema", recreate=True, verbosity=0) # All good too - self.assertTrue(utils.schema_exists("sample")) + self.assertTrue(utils.schema_exists("sample", "default")) class InteractiveCloneSchemaTestCase(TransactionTestCase): @@ -79,4 +79,4 @@ def patched_input(*args, **kwargs): with StringIO() as stdout: with StringIO() as stderr: call_command("cloneschema", "tenant1", "tenant2", verbosity=1, stdout=stdout, stderr=stderr) - self.assertTrue(utils.schema_exists("tenant2")) + self.assertTrue(utils.schema_exists("tenant2", "default")) diff --git a/dpgs_sandbox/tests/test_signals.py b/dpgs_sandbox/tests/test_signals.py index fe324e0..bb3482b 100644 --- a/dpgs_sandbox/tests/test_signals.py +++ b/dpgs_sandbox/tests/test_signals.py @@ -17,7 +17,7 @@ def test_tenant_delete_callback(self): tenant = TenantModel(schema_name="tenant1") tenant.save() tenant.create_schema(sync_schema=False) - self.assertTrue(schema_exists("tenant1")) + self.assertTrue(schema_exists("tenant1", "default")) TenantModel.objects.all().delete() - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) TenantModel.auto_create_schema, TenantModel.auto_drop_schema = backup_create, backup_drop diff --git a/dpgs_sandbox/tests/test_tenants.py b/dpgs_sandbox/tests/test_tenants.py index cdb68c2..ddf111c 100644 --- a/dpgs_sandbox/tests/test_tenants.py +++ b/dpgs_sandbox/tests/test_tenants.py @@ -2,15 +2,14 @@ from django.apps import apps from django.conf import settings -from django.core.management import call_command from django.contrib.auth import authenticate -from django.db import connection, transaction, ProgrammingError -from django.dispatch import receiver +from django.core.management import call_command +from django.db import ProgrammingError, transaction from django.test import TestCase, TransactionTestCase -from django_pgschemas.schema import SchemaDescriptor +from django_pgschemas.schema import SchemaDescriptor, schema_handler from django_pgschemas.signals import schema_post_sync -from django_pgschemas.utils import get_tenant_model, get_domain_model, schema_exists, drop_schema +from django_pgschemas.utils import drop_schema, get_domain_model, get_tenant_model, schema_exists TenantModel = get_tenant_model() DomainModel = get_domain_model() @@ -29,27 +28,27 @@ class TenantAutomaticTestCase(TransactionTestCase): def test_new_creation_deletion(self): "Tests automatic creation/deletion for new tenant's save/delete" - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) tenant = TenantModel(schema_name="tenant1") tenant.save(verbosity=0) - self.assertTrue(schema_exists("tenant1")) + self.assertTrue(schema_exists("tenant1", "default")) # Self-cleanup tenant.delete(force_drop=True) - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) def test_existing_creation(self): "Tests automatic creation for existing tenant's save" - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) tenant = TenantModel(schema_name="tenant1") tenant.auto_create_schema = False tenant.save(verbosity=0) - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) tenant.auto_create_schema = True tenant.save(verbosity=0) - self.assertTrue(schema_exists("tenant1")) + self.assertTrue(schema_exists("tenant1", "default")) # Self-cleanup tenant.delete(force_drop=True) - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) def test_new_aborted_creation(self): "Tests recovery on automatic creation for new tenant's save" @@ -57,12 +56,12 @@ def test_new_aborted_creation(self): def signal_receiver(*args, **kwargs): raise Exception - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) tenant = TenantModel(schema_name="tenant1") schema_post_sync.connect(signal_receiver) with self.assertRaises(Exception): tenant.save(verbosity=0) - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) self.assertEqual(0, TenantModel.objects.count()) schema_post_sync.disconnect(signal_receiver) @@ -72,7 +71,7 @@ def test_existing_aborted_creation(self): def signal_receiver(*args, **kwargs): raise Exception - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) tenant = TenantModel(schema_name="tenant1") tenant.auto_create_schema = False tenant.save(verbosity=0) @@ -80,7 +79,7 @@ def signal_receiver(*args, **kwargs): schema_post_sync.connect(signal_receiver) with self.assertRaises(Exception): tenant.save(verbosity=0) - self.assertFalse(schema_exists("tenant1")) + self.assertFalse(schema_exists("tenant1", "default")) self.assertEqual(1, TenantModel.objects.count()) schema_post_sync.disconnect(signal_receiver) # Self-cleanup @@ -114,7 +113,7 @@ def setUpClass(cls): user.set_password("weakpassword") user.save() TenantData.objects.create(user=user, catalog=catalog) - connection.set_schema_to_public() + schema_handler.set_schema_to_public() super().setUpClass() @classmethod @@ -123,8 +122,8 @@ def tearDownClass(cls): for key in settings.TENANTS: if key == "default": continue - drop_schema(key) - drop_schema("tenant") + drop_schema(key, "default") + drop_schema("tenant", "default") call_command("migrateschema", verbosity=0) @contextmanager diff --git a/dpgs_sandbox/tests/test_test_client.py b/dpgs_sandbox/tests/test_test_client.py index 473dd9e..7f44de9 100644 --- a/dpgs_sandbox/tests/test_test_client.py +++ b/dpgs_sandbox/tests/test_test_client.py @@ -1,7 +1,7 @@ from django.test import TestCase -from django_pgschemas.test.client import TenantRequestFactory, TenantClient -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.test.client import TenantClient, TenantRequestFactory +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() diff --git a/dpgs_sandbox/tests/test_urlresolvers.py b/dpgs_sandbox/tests/test_urlresolvers.py index be4fe08..95542a2 100644 --- a/dpgs_sandbox/tests/test_urlresolvers.py +++ b/dpgs_sandbox/tests/test_urlresolvers.py @@ -1,14 +1,13 @@ import sys from importlib import import_module -from django.db import connection -from django.test import TestCase, RequestFactory +from django.test import RequestFactory, TestCase from django.urls import reverse from django_pgschemas.middleware import TenantMiddleware -from django_pgschemas.schema import SchemaDescriptor +from django_pgschemas.schema import SchemaDescriptor, schema_handler from django_pgschemas.urlresolvers import TenantPrefixPattern, get_urlconf_from_schema -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() @@ -48,7 +47,7 @@ def fake_get_response(request): tenant.save(verbosity=0) DomainModel.objects.create(tenant=tenant, domain="{}.test.com".format(schema_name)) DomainModel.objects.create(tenant=tenant, domain="everyone.test.com", folder=schema_name) # primary - connection.set_schema_to_public() + schema_handler.set_schema_to_public() super().setUpClass() @classmethod @@ -99,7 +98,7 @@ def setUpClass(cls): tenant.save(verbosity=0) DomainModel.objects.create(tenant=tenant, domain="{}.test.com".format(schema_name)) DomainModel.objects.create(tenant=tenant, domain="everyone.test.com", folder=schema_name) # primary - connection.set_schema_to_public() + schema_handler.set_schema_to_public() super().setUpClass() @classmethod diff --git a/dpgs_sandbox/tests/test_utils.py b/dpgs_sandbox/tests/test_utils.py index 5321214..bdcb25e 100644 --- a/dpgs_sandbox/tests/test_utils.py +++ b/dpgs_sandbox/tests/test_utils.py @@ -3,7 +3,7 @@ from django.db.utils import InternalError from django.test import TestCase, override_settings -from django_pgschemas import utils, schema +from django_pgschemas import schema, utils class UtilsTestCase(TestCase): @@ -22,11 +22,6 @@ def test_get_tenant_model(self): def test_get_domain_model(self): self.assertEqual(utils.get_domain_model()._meta.model_name, "domain") - def test_get_tenant_database_alias(self): - self.assertEqual(utils.get_tenant_database_alias(), "default") - with override_settings(PGSCHEMAS_TENANT_DB_ALIAS="something"): - self.assertEqual(utils.get_tenant_database_alias(), "something") - def test_get_limit_set_calls(self): self.assertFalse(utils.get_limit_set_calls()) with override_settings(PGSCHEMAS_LIMIT_SET_CALLS=True): @@ -78,38 +73,38 @@ def inner(): cursor.close() def test_schema_exists(self): - self.assertTrue(utils.schema_exists("public")) - self.assertTrue(utils.schema_exists("www")) - self.assertTrue(utils.schema_exists("blog")) - self.assertTrue(utils.schema_exists("sample")) - self.assertFalse(utils.schema_exists("default")) - self.assertFalse(utils.schema_exists("tenant")) + self.assertTrue(utils.schema_exists("public", "default")) + self.assertTrue(utils.schema_exists("www", "default")) + self.assertTrue(utils.schema_exists("blog", "default")) + self.assertTrue(utils.schema_exists("sample", "default")) + self.assertFalse(utils.schema_exists("default", "default")) + self.assertFalse(utils.schema_exists("tenant", "default")) def test_dynamic_models_exist(self): self.assertTrue(utils.dynamic_models_exist()) - utils.drop_schema("public") + utils.drop_schema("public", "default") self.assertFalse(utils.dynamic_models_exist()) def test_create_drop_schema(self): - self.assertFalse(utils.create_schema("public", check_if_exists=True)) # Schema existed already - self.assertTrue(utils.schema_exists("public")) # Schema exists - self.assertTrue(utils.drop_schema("public")) # Schema was dropped - self.assertFalse(utils.drop_schema("public")) # Schema no longer exists - self.assertFalse(utils.schema_exists("public")) # Schema doesn't exist - self.assertTrue(utils.create_schema("public", sync_schema=False)) # Schema was created - self.assertTrue(utils.schema_exists("public")) # Schema exists + self.assertFalse(utils.create_schema("public", "default", check_if_exists=True)) # Schema existed already + self.assertTrue(utils.schema_exists("public", "default")) # Schema exists + self.assertTrue(utils.drop_schema("public", "default")) # Schema was dropped + self.assertFalse(utils.drop_schema("public", "default")) # Schema no longer exists + self.assertFalse(utils.schema_exists("public", "default")) # Schema doesn't exist + self.assertTrue(utils.create_schema("public", "default", sync_schema=False)) # Schema was created + self.assertTrue(utils.schema_exists("public", "default")) # Schema exists def test_clone_schema(self): with schema.SchemaDescriptor.create(schema_name="public"): - utils._create_clone_schema_function() - self.assertFalse(utils.schema_exists("sample2")) # Schema doesn't exist previously - utils.clone_schema("sample", "sample2", dry_run=True) # Dry run - self.assertFalse(utils.schema_exists("sample2")) # Schema won't exist, dry run - utils.clone_schema("sample", "sample2") # Real run, schema was cloned - self.assertTrue(utils.schema_exists("sample2")) # Schema exists + utils._create_clone_schema_function("default") + self.assertFalse(utils.schema_exists("sample2", "default")) # Schema doesn't exist previously + utils.clone_schema("sample", "sample2", "default", dry_run=True) # Dry run + self.assertFalse(utils.schema_exists("sample2", "default")) # Schema won't exist, dry run + utils.clone_schema("sample", "sample2", "default") # Real run, schema was cloned + self.assertTrue(utils.schema_exists("sample2", "default")) # Schema exists with self.assertRaises(InternalError): - utils.clone_schema("sample", "sample2") # Schema already exists, error - self.assertTrue(utils.schema_exists("sample2")) # Schema still exists + utils.clone_schema("sample", "sample2", "default") # Schema already exists, error + self.assertTrue(utils.schema_exists("sample2", "default")) # Schema still exists def test_create_or_clone_schema(self): - self.assertFalse(utils.create_or_clone_schema("sample")) # Schema existed + self.assertFalse(utils.create_or_clone_schema("sample", "default")) # Schema existed diff --git a/dpgs_sandbox/tests/test_whowill_command.py b/dpgs_sandbox/tests/test_whowill_command.py index c4661ea..ea33a89 100644 --- a/dpgs_sandbox/tests/test_whowill_command.py +++ b/dpgs_sandbox/tests/test_whowill_command.py @@ -3,7 +3,7 @@ from django.core import management from django.test import TransactionTestCase -from django_pgschemas.utils import get_tenant_model, get_domain_model +from django_pgschemas.utils import get_domain_model, get_tenant_model TenantModel = get_tenant_model() DomainModel = get_domain_model() diff --git a/poetry.lock b/poetry.lock index 3534bc1..dbc71ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,51 +1,51 @@ [[package]] -category = "dev" -description = "A configurable sidebar-enabled Sphinx theme" name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = "*" -version = "0.7.12" [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.3" [[package]] -category = "main" -description = "ASGI specs, helper code, and adapters" name = "asgiref" +version = "3.3.1" +description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.5" -version = "3.2.7" [package.extras] -tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"] +tests = ["pytest", "pytest-asyncio"] [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "dev" -description = "WebSocket client & server library, WAMP real-time framework" name = "autobahn" +version = "20.7.1" +description = "WebSocket client & server library, WAMP real-time framework" +category = "dev" optional = false python-versions = ">=3.5" -version = "20.4.2" [package.dependencies] cryptography = ">=2.7" @@ -53,23 +53,23 @@ txaio = ">=20.3.1" [package.extras] accelerate = ["wsaccel (>=0.6.2)"] -all = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)", "wsaccel (>=0.6.2)", "python-snappy (>=0.5)", "msgpack (>=0.6.1)", "ujson (>=1.35)", "cbor2 (>=5.0.1)", "cbor (>=1.0.0)", "py-ubjson (>=0.8.4)", "flatbuffers (>=1.10)", "pyopenssl (>=16.2.0)", "service-identity (>=18.1.0)", "pynacl (>=1.0.1)", "pytrie (>=0.2)", "pyqrcode (>=1.1)", "cffi (>=1.11.5)", "argon2-cffi (>=18.1.0)", "passlib (>=1.7.1)", "cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)"] +all = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)", "wsaccel (>=0.6.2)", "python-snappy (>=0.5)", "msgpack (>=0.6.1)", "ujson (>=1.35)", "cbor2 (>=5.0.1)", "cbor (>=1.0.0)", "py-ubjson (>=0.8.4)", "flatbuffers (>=1.10)", "pyopenssl (>=16.2.0)", "service-identity (>=18.1.0)", "pynacl (>=1.0.1)", "pytrie (>=0.2)", "pyqrcode (>=1.1)", "cffi (>=1.11.5)", "argon2-cffi (>=18.1.0)", "passlib (>=1.7.1)", "cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)", "py-multihash (>=0.2.3)"] compress = ["python-snappy (>=0.5)"] -dev = ["pep8-naming (>=0.3.3)", "flake8 (>=2.5.1)", "pyflakes (>=1.0.0)", "pytest (>=2.8.6,<3.3.0)", "twine (>=1.6.5)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "awscli", "qualname", "passlib", "wheel", "pytest-asyncio (<0.6)", "pytest-aiohttp"] +dev = ["pep8-naming (>=0.3.3)", "flake8 (>=2.5.1)", "pyflakes (>=1.0.0)", "pytest (>=2.8.6,<3.3.0)", "twine (>=1.6.5)", "sphinx (>=1.2.3)", "sphinxcontrib-images (>=0.9.2)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "awscli", "qualname", "passlib", "wheel", "pytest-asyncio (<0.6)", "pytest-aiohttp"] encryption = ["pyopenssl (>=16.2.0)", "service-identity (>=18.1.0)", "pynacl (>=1.0.1)", "pytrie (>=0.2)", "pyqrcode (>=1.1)"] nvx = ["cffi (>=1.11.5)"] scram = ["cffi (>=1.11.5)", "argon2-cffi (>=18.1.0)", "passlib (>=1.7.1)"] serialization = ["msgpack (>=0.6.1)", "ujson (>=1.35)", "cbor2 (>=5.0.1)", "cbor (>=1.0.0)", "py-ubjson (>=0.8.4)", "flatbuffers (>=1.10)"] twisted = ["zope.interface (>=3.6.0)", "twisted (>=20.3.0)", "attrs (>=19.2.0)"] -xbr = ["cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "twisted (>=20.3.0)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)"] +xbr = ["cbor2 (>=5.1.0)", "zlmdb (>=20.4.1)", "twisted (>=20.3.0)", "autobahn (>=18.11.2)", "web3 (>=4.8.1)", "py-eth-sig-utils (>=0.4.0)", "py-ecc (>=1.7.1)", "eth-abi (>=1.3.0)", "mnemonic (>=0.13)", "base58 (>=1.0.2,<2.0)", "ecdsa (>=0.13)", "py-multihash (>=0.2.3)"] [[package]] -category = "dev" -description = "Self-service finite-state machines for the programmer on the go." name = "automat" +version = "20.2.0" +description = "Self-service finite-state machines for the programmer on the go." +category = "dev" optional = false python-versions = "*" -version = "20.2.0" [package.dependencies] attrs = ">=19.2.0" @@ -79,168 +79,173 @@ six = "*" visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] [[package]] -category = "dev" -description = "Internationalization utilities" name = "babel" +version = "2.9.0" +description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8.0" [package.dependencies] pytz = ">=2015.7" [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "19.10b0" [package.dependencies] appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" +click = ">=7.1.2" +dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +mypy-extensions = ">=0.4.3" pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" +regex = ">=2020.1.8" +toml = ">=0.10.1" typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" [package.extras] +colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "dev" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false python-versions = "*" -version = "2020.4.5.1" [[package]] -category = "dev" -description = "Foreign Function Interface for Python calling C code." name = "cffi" +version = "1.14.4" +description = "Foreign Function Interface for Python calling C code." +category = "dev" optional = false python-versions = "*" -version = "1.14.0" [package.dependencies] pycparser = "*" [[package]] -category = "dev" -description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." name = "channels" +version = "2.4.0" +description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only." +category = "dev" optional = false python-versions = ">=3.5" -version = "2.4.0" [package.dependencies] -Django = ">=2.2" asgiref = ">=3.2,<4.0" daphne = ">=2.3,<3.0" +Django = ">=2.2" [package.extras] tests = ["pytest (>=4.4,<5.0)", "pytest-django (>=3.4,<4.0)", "pytest-asyncio (>=0.10,<1.0)", "async-generator (>=1.10,<2.0)", "async-timeout (>=3.0,<4.0)", "coverage (>=4.5,<5.0)"] [[package]] -category = "dev" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "dev" optional = false python-versions = "*" -version = "3.0.4" [[package]] -category = "dev" -description = "Composable command line interface toolkit" name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.1" [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" [[package]] -category = "dev" -description = "Symbolic constants in Python" name = "constantly" +version = "15.1.0" +description = "Symbolic constants in Python" +category = "dev" optional = false python-versions = "*" -version = "15.1.0" [[package]] -category = "dev" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.3" +description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.1" [package.dependencies] -[package.dependencies.toml] -optional = true -version = "*" +toml = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["toml"] [[package]] -category = "dev" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." name = "cryptography" +version = "3.3.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "dev" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "2.9" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" [package.dependencies] -cffi = ">=1.8,<1.11.3 || >1.11.3" +cffi = ">=1.12" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -idna = ["idna (>=2.1)"] -pep8test = ["flake8", "flake8-import-order", "pep8-naming"] -test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -category = "dev" -description = "Django ASGI (HTTP/WebSocket) server" name = "daphne" +version = "2.5.0" +description = "Django ASGI (HTTP/WebSocket) server" +category = "dev" optional = false python-versions = "*" -version = "2.5.0" [package.dependencies] asgiref = ">=3.2,<4.0" autobahn = ">=0.18" - -[package.dependencies.twisted] -extras = ["tls"] -version = ">=18.7" +twisted = {version = ">=18.7", extras = ["tls"]} [package.extras] -tests = ["hypothesis (4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] +tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"] + +[[package]] +name = "dataclasses" +version = "0.8" +description = "A backport of the dataclasses module for Python 3.6" +category = "dev" +optional = false +python-versions = ">=3.6, <3.7" [[package]] -category = "main" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." name = "django" +version = "3.1.4" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.6" -version = "3.0.5" [package.dependencies] -asgiref = ">=3.2,<4.0" +asgiref = ">=3.2.10,<4" pytz = "*" sqlparse = ">=0.2.2" @@ -249,73 +254,102 @@ argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] -category = "dev" -description = "Style checker for Sphinx (or other) RST documentation" name = "doc8" +version = "0.8.1" +description = "Style checker for Sphinx (or other) RST documentation" +category = "dev" optional = false python-versions = "*" -version = "0.8.0" [package.dependencies] chardet = "*" docutils = "*" +Pygments = "*" restructuredtext-lint = ">=0.7" six = "*" stevedore = "*" [[package]] -category = "dev" -description = "Docutils -- Python Documentation Utilities" name = "docutils" +version = "0.16" +description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.16" [[package]] -category = "dev" -description = "A featureful, immutable, and correct URL for Python." name = "hyperlink" +version = "20.0.1" +description = "A featureful, immutable, and correct URL for Python." +category = "dev" optional = false -python-versions = "*" -version = "19.0.0" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] idna = ">=2.5" [[package]] -category = "dev" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.7" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.9" +python-versions = "*" [[package]] -category = "dev" -description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" +version = "1.2.0" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.2.0" [[package]] +name = "importlib-metadata" +version = "3.1.1" +description = "Read metadata from Python packages" category = "dev" -description = "" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] name = "incremental" +version = "17.5.0" +description = "" +category = "dev" optional = false python-versions = "*" -version = "17.5.0" [package.extras] scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] [[package]] +name = "isort" +version = "5.6.4" +description = "A Python utility / library to sort Python imports." category = "dev" -description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] name = "jinja2" +version = "2.11.2" +description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -324,102 +358,109 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "dev" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" [[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" -description = "Core utilities for Python packages" +optional = false +python-versions = "*" + +[[package]] name = "packaging" +version = "20.7" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.3" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" [[package]] -category = "dev" -description = "Python Build Reasonableness" name = "pbr" +version = "5.5.1" +description = "Python Build Reasonableness" +category = "dev" optional = false -python-versions = "*" -version = "5.4.5" +python-versions = ">=2.6" [[package]] -category = "main" -description = "psycopg2 - Python-PostgreSQL Database Adapter" name = "psycopg2" +version = "2.8.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "2.8.5" [[package]] -category = "dev" -description = "ASN.1 types and codecs" name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "dev" optional = false python-versions = "*" -version = "0.4.8" [[package]] -category = "dev" -description = "A collection of ASN.1-based protocols modules." name = "pyasn1-modules" +version = "0.2.8" +description = "A collection of ASN.1-based protocols modules." +category = "dev" optional = false python-versions = "*" -version = "0.2.8" [package.dependencies] pyasn1 = ">=0.4.6,<0.5.0" [[package]] -category = "dev" -description = "C parser in Python" name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" [[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." name = "pygments" +version = "2.7.3" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.5" -version = "2.6.1" [[package]] -category = "dev" -description = "Hamcrest framework for matcher objects" name = "pyhamcrest" +version = "2.0.2" +description = "Hamcrest framework for matcher objects" +category = "dev" optional = false python-versions = ">=3.5" -version = "2.0.2" [[package]] -category = "dev" -description = "Python wrapper module around the OpenSSL library" name = "pyopenssl" +version = "20.0.0" +description = "Python wrapper module around the OpenSSL library" +category = "dev" optional = false -python-versions = "*" -version = "19.1.0" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" [package.dependencies] -cryptography = ">=2.8" +cryptography = ">=3.2" six = ">=1.5.2" [package.extras] @@ -427,65 +468,65 @@ docs = ["sphinx", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2020.4" +description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" -version = "2019.3" [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.4.4" [[package]] -category = "dev" -description = "Python HTTP for Humans." name = "requests" +version = "2.20.0" +description = "Python HTTP for Humans." +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.23.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.8" +urllib3 = ">=1.21.1,<1.25" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "dev" -description = "reStructuredText linter" name = "restructuredtext-lint" +version = "1.3.2" +description = "reStructuredText linter" +category = "dev" optional = false python-versions = "*" -version = "1.3.0" [package.dependencies] docutils = ">=0.11,<1.0" [[package]] -category = "dev" -description = "Service identity verification for pyOpenSSL & cryptography." name = "service-identity" +version = "18.1.0" +description = "Service identity verification for pyOpenSSL & cryptography." +category = "dev" optional = false python-versions = "*" -version = "18.1.0" [package.dependencies] attrs = ">=16.0.0" @@ -500,40 +541,39 @@ idna = ["idna"] tests = ["coverage (>=4.2.0)", "pytest"] [[package]] -category = "dev" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.14.0" [[package]] -category = "dev" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." name = "snowballstemmer" +version = "2.0.0" +description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" -version = "2.0.0" [[package]] -category = "dev" -description = "Python documentation generator" name = "sphinx" +version = "1.8.5" +description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.5" [package.dependencies] -Jinja2 = ">=2.3" -Pygments = ">=2.0" alabaster = ">=0.7,<0.8" babel = ">=1.3,<2.0 || >2.0" -colorama = ">=0.3.5" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.11" imagesize = "*" +Jinja2 = ">=2.3" packaging = "*" +Pygments = ">=2.0" requests = ">=2.0.0" -setuptools = "*" six = ">=1.5" snowballstemmer = ">=1.1" sphinxcontrib-websupport = "*" @@ -543,12 +583,12 @@ test = ["mock", "pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8- websupport = ["sqlalchemy (>=0.9)", "whoosh (>=2.0)"] [[package]] -category = "dev" -description = "Sphinx plugin for help Django commands documentation." name = "sphinx-django-command" +version = "0.1.3" +description = "Sphinx plugin for help Django commands documentation." +category = "dev" optional = false python-versions = "*" -version = "0.1.3" [package.dependencies] Django = "*" @@ -556,141 +596,163 @@ docutils = "*" sphinx = "*" [[package]] -category = "dev" -description = "Read the Docs theme for Sphinx" name = "sphinx-rtd-theme" +version = "0.4.3" +description = "Read the Docs theme for Sphinx" +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [package.dependencies] sphinx = "*" [[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.4" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." category = "dev" -description = "Sphinx API for Web Apps" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] name = "sphinxcontrib-websupport" +version = "1.2.4" +description = "Sphinx API for Web Apps" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.2.1" + +[package.dependencies] +sphinxcontrib-serializinghtml = "*" [package.extras] lint = ["flake8"] test = ["pytest", "sqlalchemy", "whoosh", "sphinx"] [[package]] -category = "main" -description = "Non-validating SQL parser" name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.3.1" +python-versions = ">=3.5" [[package]] -category = "dev" -description = "Manage dynamic plugins for Python applications" name = "stevedore" +version = "3.3.0" +description = "Manage dynamic plugins for Python applications" +category = "dev" optional = false -python-versions = "*" -version = "1.32.0" +python-versions = ">=3.6" [package.dependencies] +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" -six = ">=1.10.0" [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false -python-versions = "*" -version = "0.10.0" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -category = "dev" -description = "An asynchronous networking framework written in Python" name = "twisted" +version = "20.3.0" +description = "An asynchronous networking framework written in Python" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "20.3.0" [package.dependencies] -Automat = ">=0.3.0" -PyHamcrest = ">=1.9.0,<1.10.0 || >1.10.0" attrs = ">=19.2.0" +Automat = ">=0.3.0" constantly = ">=15.1" hyperlink = ">=17.1.1" +idna = {version = ">=0.6,<2.3 || >2.3", optional = true, markers = "extra == \"tls\""} incremental = ">=16.10.1" +PyHamcrest = ">=1.9.0,<1.10.0 || >1.10.0" +pyopenssl = {version = ">=16.0.0", optional = true, markers = "extra == \"tls\""} +service_identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""} "zope.interface" = ">=4.4.2" -[package.dependencies.idna] -optional = true -version = ">=0.6,<2.3 || >2.3" - -[package.dependencies.pyopenssl] -optional = true -version = ">=16.0.0" - -[package.dependencies.service_identity] -optional = true -version = ">=18.1.0" - [package.extras] -all_non_platform = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +all_non_platform = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] conch = ["pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)"] dev = ["pyflakes (>=1.0.0)", "twisted-dev-tools (>=0.0.2)", "python-subunit", "sphinx (>=1.3.1)", "towncrier (>=17.4.0)"] http2 = ["h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)"] -macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] -osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +macos_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +osx_platform = ["pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] soap = ["soappy"] -tls = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)"] -windows_platform = ["pywin32 (!=226)", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,<2.3 || >2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] +tls = ["pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)"] +windows_platform = ["pywin32 (!=226)", "pyopenssl (>=16.0.0)", "service_identity (>=18.1.0)", "idna (>=0.6,!=2.3)", "pyasn1", "cryptography (>=2.5)", "appdirs (>=1.4.0)", "bcrypt (>=3.0.0)", "soappy", "pyserial (>=3.0)", "h2 (>=3.0,<4.0)", "priority (>=1.1.0,<2.0)", "pywin32 (!=226)"] [[package]] -category = "dev" -description = "Compatibility API between asyncio/Twisted/Trollius" name = "txaio" +version = "20.4.1" +description = "Compatibility API between asyncio/Twisted/Trollius" +category = "dev" optional = false python-versions = ">=3.5" -version = "20.4.1" [package.extras] all = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] -dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (1.3.0)", "twine (>=1.6.5)"] +dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)"] twisted = ["zope.interface (>=3.6)", "twisted (>=20.3.0)"] [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.1" [[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" -description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = "*" + +[[package]] name = "urllib3" +version = "1.24.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.9" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] +name = "zipp" +version = "3.4.0" +description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" -description = "Interfaces for Python" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[[package]] name = "zope.interface" +version = "5.2.0" +description = "Interfaces for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.1.0" - -[package.dependencies] -setuptools = "*" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -698,8 +760,9 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] -content-hash = "05a9ff112439464f83c31db1a4077e9489643a969e6a93cfd727cbb7911b1d20" +lock-version = "1.1" python-versions = "^3.6" +content-hash = "a2460a4e4c76d89b1ea885e288d759e6629c0c5df72bd90068fc2619f3f4c5d4" [metadata.files] alabaster = [ @@ -707,66 +770,74 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] asgiref = [ - {file = "asgiref-3.2.7-py2.py3-none-any.whl", hash = "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"}, - {file = "asgiref-3.2.7.tar.gz", hash = "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5"}, + {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, + {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] autobahn = [ - {file = "autobahn-20.4.2-py2.py3-none-any.whl", hash = "sha256:6818f4227ab4bce807727903c4b47cee02c15fd4811ab707be9f9eeaf30beaac"}, - {file = "autobahn-20.4.2.tar.gz", hash = "sha256:1c943f94bb759c07445a6984a3bc4c76f73e2a90a1fc2c3521c211579b73d3db"}, + {file = "autobahn-20.7.1-py2.py3-none-any.whl", hash = "sha256:24ce276d313e84d68241c3aef30d484f352b90a40168981b3640312c821df77b"}, + {file = "autobahn-20.7.1.tar.gz", hash = "sha256:86bbce30cdd407137c57670993a8f9bfdfe3f8e994b889181d85e844d5aa8dfb"}, ] automat = [ {file = "Automat-20.2.0-py2.py3-none-any.whl", hash = "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"}, {file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"}, ] babel = [ - {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, - {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, + {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, + {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, ] black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, - {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, + {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, + {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, + {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, + {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, + {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, + {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, + {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, + {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, + {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, + {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, + {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, + {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, + {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, + {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, + {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, + {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, + {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, + {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, + {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, + {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, + {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, + {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"}, + {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, + {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, + {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, ] channels = [ {file = "channels-2.4.0-py2.py3-none-any.whl", hash = "sha256:80a5ad1962ae039a3dcc0a5cb5212413e66e2f11ad9e9db8004834436daf3400"}, @@ -777,103 +848,113 @@ chardet = [ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] click = [ - {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, - {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] constantly = [ {file = "constantly-15.1.0-py2.py3-none-any.whl", hash = "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"}, {file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"}, ] coverage = [ - {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, - {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, - {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, - {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, - {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, - {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, - {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, - {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, - {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, - {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, - {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, - {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, - {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, - {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, - {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, - {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, - {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, - {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, - {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, - {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, - {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, - {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, - {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, - {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, - {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, - {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, - {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, - {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, - {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, - {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, - {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] cryptography = [ - {file = "cryptography-2.9-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:ef9a55013676907df6c9d7dd943eb1770d014f68beaa7e73250fb43c759f4585"}, - {file = "cryptography-2.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2a2ad24d43398d89f92209289f15265107928f22a8d10385f70def7a698d6a02"}, - {file = "cryptography-2.9-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:95e1296e0157361fe2f5f0ed307fd31f94b0ca13372e3673fa95095a627636a1"}, - {file = "cryptography-2.9-cp27-cp27m-win32.whl", hash = "sha256:192ca04a36852a994ef21df13cca4d822adbbdc9d5009c0f96f1d2929e375d4f"}, - {file = "cryptography-2.9-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1d0760c7e46436ec90834d6f10477ff09475c692ed1695329d324b2c5cd547"}, - {file = "cryptography-2.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:19ae795137682a9778892fb4390c07811828b173741bce91e30f899424b3934d"}, - {file = "cryptography-2.9-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d1bf5a1a0d60c7f9a78e448adcb99aa101f3f9588b16708044638881be15d6bc"}, - {file = "cryptography-2.9-cp35-abi3-macosx_10_9_intel.whl", hash = "sha256:1b9b535d6b55936a79dbe4990b64bb16048f48747c76c29713fea8c50eca2acf"}, - {file = "cryptography-2.9-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:7a279f33a081d436e90e91d1a7c338553c04e464de1c9302311a5e7e4b746088"}, - {file = "cryptography-2.9-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:49870684da168b90110bbaf86140d4681032c5e6a2461adc7afdd93be5634216"}, - {file = "cryptography-2.9-cp35-cp35m-win32.whl", hash = "sha256:6b91cab3841b4c7cb70e4db1697c69f036c8bc0a253edc0baa6783154f1301e4"}, - {file = "cryptography-2.9-cp35-cp35m-win_amd64.whl", hash = "sha256:587f98ce27ac4547177a0c6fe0986b8736058daffe9160dcf5f1bd411b7fbaa1"}, - {file = "cryptography-2.9-cp36-cp36m-win32.whl", hash = "sha256:cc20316e3f5a6b582fc3b029d8dc03aabeb645acfcb7fc1d9848841a33265748"}, - {file = "cryptography-2.9-cp36-cp36m-win_amd64.whl", hash = "sha256:3be7a5722d5bfe69894d3f7bbed15547b17619f3a88a318aab2e37f457524164"}, - {file = "cryptography-2.9-cp37-cp37m-win32.whl", hash = "sha256:7598974f6879a338c785c513e7c5a4329fbc58b9f6b9a6305035fca5b1076552"}, - {file = "cryptography-2.9-cp37-cp37m-win_amd64.whl", hash = "sha256:5aca6f00b2f42546b9bdf11a69f248d1881212ce5b9e2618b04935b87f6f82a1"}, - {file = "cryptography-2.9-cp38-cp38-win32.whl", hash = "sha256:9fc9da390e98cb6975eadf251b6e5fa088820141061bf041cd5c72deba1dc526"}, - {file = "cryptography-2.9-cp38-cp38-win_amd64.whl", hash = "sha256:6b744039b55988519cc183149cceb573189b3e46e16ccf6f8c46798bb767c9dc"}, - {file = "cryptography-2.9.tar.gz", hash = "sha256:0cacd3ef5c604b8e5f59bf2582c076c98a37fe206b31430d0cd08138aff0986e"}, + {file = "cryptography-3.3.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:541dd758ad49b45920dda3b5b48c968f8b2533d8981bcdb43002798d8f7a89ed"}, + {file = "cryptography-3.3.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:49570438e60f19243e7e0d504527dd5fe9b4b967b5a1ff21cc12b57602dd85d3"}, + {file = "cryptography-3.3.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a4ac9648d39ce71c2f63fe7dc6db144b9fa567ddfc48b9fde1b54483d26042"}, + {file = "cryptography-3.3.2-cp27-cp27m-win32.whl", hash = "sha256:aa4969f24d536ae2268c902b2c3d62ab464b5a66bcb247630d208a79a8098e9b"}, + {file = "cryptography-3.3.2-cp27-cp27m-win_amd64.whl", hash = "sha256:1bd0ccb0a1ed775cd7e2144fe46df9dc03eefd722bbcf587b3e0616ea4a81eff"}, + {file = "cryptography-3.3.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e18e6ab84dfb0ab997faf8cca25a86ff15dfea4027b986322026cc99e0a892da"}, + {file = "cryptography-3.3.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c7390f9b2119b2b43160abb34f63277a638504ef8df99f11cb52c1fda66a2e6f"}, + {file = "cryptography-3.3.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:0d7b69674b738068fa6ffade5c962ecd14969690585aaca0a1b1fc9058938a72"}, + {file = "cryptography-3.3.2-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:922f9602d67c15ade470c11d616f2b2364950602e370c76f0c94c94ae672742e"}, + {file = "cryptography-3.3.2-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:a0f0b96c572fc9f25c3f4ddbf4688b9b38c69836713fb255f4a2715d93cbaf44"}, + {file = "cryptography-3.3.2-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:a777c096a49d80f9d2979695b835b0f9c9edab73b59e4ceb51f19724dda887ed"}, + {file = "cryptography-3.3.2-cp36-abi3-win32.whl", hash = "sha256:3c284fc1e504e88e51c428db9c9274f2da9f73fdf5d7e13a36b8ecb039af6e6c"}, + {file = "cryptography-3.3.2-cp36-abi3-win_amd64.whl", hash = "sha256:7951a966613c4211b6612b0352f5bf29989955ee592c4a885d8c7d0f830d0433"}, + {file = "cryptography-3.3.2.tar.gz", hash = "sha256:5a60d3780149e13b7a6ff7ad6526b38846354d11a15e21068e57073e29e19bed"}, ] daphne = [ {file = "daphne-2.5.0-py2.py3-none-any.whl", hash = "sha256:aa64840015709bbc9daa3c4464a4a4d437937d6cda10a9b51e913eb319272553"}, {file = "daphne-2.5.0.tar.gz", hash = "sha256:1ca46d7419103958bbc9576fb7ba3b25b053006e22058bc97084ee1a7d44f4ba"}, ] +dataclasses = [ + {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, + {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, +] django = [ - {file = "Django-3.0.5-py3-none-any.whl", hash = "sha256:642d8eceab321ca743ae71e0f985ff8fdca59f07aab3a9fb362c617d23e33a76"}, - {file = "Django-3.0.5.tar.gz", hash = "sha256:d4666c2edefa38c5ede0ec1655424c56dc47ceb04b6d8d62a7eac09db89545c1"}, + {file = "Django-3.1.4-py3-none-any.whl", hash = "sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2"}, + {file = "Django-3.1.4.tar.gz", hash = "sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"}, ] doc8 = [ - {file = "doc8-0.8.0-py2.py3-none-any.whl", hash = "sha256:d12f08aa77a4a65eb28752f4bc78f41f611f9412c4155e2b03f1f5d4a45efe04"}, - {file = "doc8-0.8.0.tar.gz", hash = "sha256:2df89f9c1a5abfb98ab55d0175fed633cae0cf45025b8b1e0ee5ea772be28543"}, + {file = "doc8-0.8.1-py2.py3-none-any.whl", hash = "sha256:4d58a5c8c56cedd2b2c9d6e3153be5d956cf72f6051128f0f2255c66227df721"}, + {file = "doc8-0.8.1.tar.gz", hash = "sha256:4d1df12598807cf08ffa9a1d5ef42d229ee0de42519da01b768ff27211082c12"}, ] docutils = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] hyperlink = [ - {file = "hyperlink-19.0.0-py2.py3-none-any.whl", hash = "sha256:ab4a308feb039b04f855a020a6eda3b18ca5a68e6d8f8c899cbe9e653721d04f"}, - {file = "hyperlink-19.0.0.tar.gz", hash = "sha256:4288e34705da077fada1111a24a0aa08bb1e76699c9ce49876af722441845654"}, + {file = "hyperlink-20.0.1-py2.py3-none-any.whl", hash = "sha256:c528d405766f15a2c536230de7e160b65a08e20264d8891b3eb03307b0df3c63"}, + {file = "hyperlink-20.0.1.tar.gz", hash = "sha256:47fcc7cd339c6cb2444463ec3277bdcfe142c8b1daf2160bdd52248deec815af"}, ] idna = [ - {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, - {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, + {file = "idna-2.7-py2.py3-none-any.whl", hash = "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e"}, + {file = "idna-2.7.tar.gz", hash = "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"}, ] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] +importlib-metadata = [ + {file = "importlib_metadata-3.1.1-py3-none-any.whl", hash = "sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013"}, + {file = "importlib_metadata-3.1.1.tar.gz", hash = "sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"}, +] incremental = [ {file = "incremental-17.5.0-py2.py3-none-any.whl", hash = "sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f"}, {file = "incremental-17.5.0.tar.gz", hash = "sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3"}, ] +isort = [ + {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, + {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, +] jinja2 = [ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, @@ -897,48 +978,73 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, + {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] packaging = [ - {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, - {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, + {file = "packaging-20.7-py2.py3-none-any.whl", hash = "sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"}, + {file = "packaging-20.7.tar.gz", hash = "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236"}, ] pathspec = [ - {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, - {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] pbr = [ - {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, - {file = "pbr-5.4.5.tar.gz", hash = "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c"}, + {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"}, + {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"}, ] psycopg2 = [ - {file = "psycopg2-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf"}, - {file = "psycopg2-2.8.5-cp27-cp27m-win_amd64.whl", hash = "sha256:acf56d564e443e3dea152efe972b1434058244298a94348fc518d6dd6a9fb0bb"}, - {file = "psycopg2-2.8.5-cp34-cp34m-win32.whl", hash = "sha256:440a3ea2c955e89321a138eb7582aa1d22fe286c7d65e26a2c5411af0a88ae72"}, - {file = "psycopg2-2.8.5-cp34-cp34m-win_amd64.whl", hash = "sha256:6b306dae53ec7f4f67a10942cf8ac85de930ea90e9903e2df4001f69b7833f7e"}, - {file = "psycopg2-2.8.5-cp35-cp35m-win32.whl", hash = "sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055"}, - {file = "psycopg2-2.8.5-cp35-cp35m-win_amd64.whl", hash = "sha256:6a471d4d2a6f14c97a882e8d3124869bc623f3df6177eefe02994ea41fd45b52"}, - {file = "psycopg2-2.8.5-cp36-cp36m-win32.whl", hash = "sha256:27c633f2d5db0fc27b51f1b08f410715b59fa3802987aec91aeb8f562724e95c"}, - {file = "psycopg2-2.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2df2bf1b87305bd95eb3ac666ee1f00a9c83d10927b8144e8e39644218f4cf81"}, - {file = "psycopg2-2.8.5-cp37-cp37m-win32.whl", hash = "sha256:ac5b23d0199c012ad91ed1bbb971b7666da651c6371529b1be8cbe2a7bf3c3a9"}, - {file = "psycopg2-2.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2c0afb40cfb4d53487ee2ebe128649028c9a78d2476d14a67781e45dc287f080"}, - {file = "psycopg2-2.8.5-cp38-cp38-win32.whl", hash = "sha256:2327bf42c1744a434ed8ed0bbaa9168cac7ee5a22a9001f6fc85c33b8a4a14b7"}, - {file = "psycopg2-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535"}, - {file = "psycopg2-2.8.5.tar.gz", hash = "sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"}, + {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"}, + {file = "psycopg2-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5"}, + {file = "psycopg2-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad"}, + {file = "psycopg2-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3"}, + {file = "psycopg2-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821"}, + {file = "psycopg2-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301"}, + {file = "psycopg2-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a"}, + {file = "psycopg2-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d"}, + {file = "psycopg2-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84"}, + {file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"}, + {file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"}, + {file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"}, + {file = "psycopg2-2.8.6-cp39-cp39-win32.whl", hash = "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3"}, + {file = "psycopg2-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7"}, + {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, ] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, @@ -975,62 +1081,82 @@ pycparser = [ {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, + {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, + {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, ] pyhamcrest = [ {file = "PyHamcrest-2.0.2-py3-none-any.whl", hash = "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"}, {file = "PyHamcrest-2.0.2.tar.gz", hash = "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316"}, ] pyopenssl = [ - {file = "pyOpenSSL-19.1.0-py2.py3-none-any.whl", hash = "sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504"}, - {file = "pyOpenSSL-19.1.0.tar.gz", hash = "sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507"}, + {file = "pyOpenSSL-20.0.0-py2.py3-none-any.whl", hash = "sha256:898aefbde331ba718570244c3b01dcddb1b31a3b336613436a45e52e27d9a82d"}, + {file = "pyOpenSSL-20.0.0.tar.gz", hash = "sha256:92f08eccbd73701cf744e8ffd6989aa7842d48cbe3fea8a7c031c5647f590ac5"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, + {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, + {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, ] regex = [ - {file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"}, - {file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"}, - {file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"}, - {file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"}, - {file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"}, - {file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"}, - {file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"}, - {file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"}, - {file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"}, + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, ] requests = [ - {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, - {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, + {file = "requests-2.20.0-py2.py3-none-any.whl", hash = "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"}, + {file = "requests-2.20.0.tar.gz", hash = "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c"}, ] restructuredtext-lint = [ - {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"}, + {file = "restructuredtext_lint-1.3.2.tar.gz", hash = "sha256:d3b10a1fe2ecac537e51ae6d151b223b78de9fafdd50e5eb6b08c243df173c80"}, ] service-identity = [ {file = "service_identity-18.1.0-py2.py3-none-any.whl", hash = "sha256:001c0707759cb3de7e49c078a7c0c9cd12594161d3bf06b9c254fdcb1a60dc36"}, {file = "service_identity-18.1.0.tar.gz", hash = "sha256:0858a54aabc5b459d1aafa8a518ed2081a285087f349fe3e55197989232e2e2d"}, ] six = [ - {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, - {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] snowballstemmer = [ {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, @@ -1047,22 +1173,25 @@ sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-0.4.3-py2.py3-none-any.whl", hash = "sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4"}, {file = "sphinx_rtd_theme-0.4.3.tar.gz", hash = "sha256:728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"}, ] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, + {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, +] sphinxcontrib-websupport = [ - {file = "sphinxcontrib-websupport-1.2.1.tar.gz", hash = "sha256:545f5da4bd7757e82b8a23ce3af9500c6ffeedbcb13429fca436ad1e80bd10cf"}, - {file = "sphinxcontrib_websupport-1.2.1-py2.py3-none-any.whl", hash = "sha256:69364896eae5d1145d82b6ee09f66d597099ef8069615e2888921ec48005470f"}, + {file = "sphinxcontrib-websupport-1.2.4.tar.gz", hash = "sha256:4edf0223a0685a7c485ae5a156b6f529ba1ee481a1417817935b20bde1956232"}, + {file = "sphinxcontrib_websupport-1.2.4-py2.py3-none-any.whl", hash = "sha256:6fc9287dfc823fe9aa432463edd6cea47fa9ebbf488d7f289b322ffcfca075c7"}, ] sqlparse = [ - {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"}, - {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"}, + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, ] stevedore = [ - {file = "stevedore-1.32.0-py2.py3-none-any.whl", hash = "sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b"}, - {file = "stevedore-1.32.0.tar.gz", hash = "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b"}, + {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, + {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, ] toml = [ - {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, - {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, - {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] twisted = [ {file = "Twisted-20.3.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:cdbc4c7f0cd7a2218b575844e970f05a1be1861c607b0e048c9bceca0c4d42f7"}, @@ -1101,64 +1230,94 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, + {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, + {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] urllib3 = [ - {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, - {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, + {file = "urllib3-1.24.3-py2.py3-none-any.whl", hash = "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"}, + {file = "urllib3-1.24.3.tar.gz", hash = "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4"}, +] +zipp = [ + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, ] "zope.interface" = [ - {file = "zope.interface-5.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645a7092b77fdbc3f68d3cc98f9d3e71510e419f54019d6e282328c0dd140dcd"}, - {file = "zope.interface-5.1.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d1fe9d7d09bb07228650903d6a9dc48ea649e3b8c69b1d263419cc722b3938e8"}, - {file = "zope.interface-5.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a744132d0abaa854d1aad50ba9bc64e79c6f835b3e92521db4235a1991176813"}, - {file = "zope.interface-5.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:461d4339b3b8f3335d7e2c90ce335eb275488c587b61aca4b305196dde2ff086"}, - {file = "zope.interface-5.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:269b27f60bcf45438e8683269f8ecd1235fa13e5411de93dae3b9ee4fe7f7bc7"}, - {file = "zope.interface-5.1.0-cp27-cp27m-win32.whl", hash = "sha256:6874367586c020705a44eecdad5d6b587c64b892e34305bb6ed87c9bbe22a5e9"}, - {file = "zope.interface-5.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8149ded7f90154fdc1a40e0c8975df58041a6f693b8f7edcd9348484e9dc17fe"}, - {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0103cba5ed09f27d2e3de7e48bb320338592e2fabc5ce1432cf33808eb2dfd8b"}, - {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b0becb75418f8a130e9d465e718316cd17c7a8acce6fe8fe07adc72762bee425"}, - {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8"}, - {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4f98f70328bc788c86a6a1a8a14b0ea979f81ae6015dd6c72978f1feff70ecda"}, - {file = "zope.interface-5.1.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:af2c14efc0bb0e91af63d00080ccc067866fb8cbbaca2b0438ab4105f5e0f08d"}, - {file = "zope.interface-5.1.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6"}, - {file = "zope.interface-5.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d7804f6a71fc2dda888ef2de266727ec2f3915373d5a785ed4ddc603bbc91e08"}, - {file = "zope.interface-5.1.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:74bf0a4f9091131de09286f9a605db449840e313753949fe07c8d0fe7659ad1e"}, - {file = "zope.interface-5.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ba4261c8ad00b49d48bbb3b5af388bb7576edfc0ca50a49c11dcb77caa1d897e"}, - {file = "zope.interface-5.1.0-cp35-cp35m-win32.whl", hash = "sha256:ebb4e637a1fb861c34e48a00d03cffa9234f42bef923aec44e5625ffb9a8e8f9"}, - {file = "zope.interface-5.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:911714b08b63d155f9c948da2b5534b223a1a4fc50bb67139ab68b277c938578"}, - {file = "zope.interface-5.1.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:e74671e43ed4569fbd7989e5eecc7d06dc134b571872ab1d5a88f4a123814e9f"}, - {file = "zope.interface-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b1d2ed1cbda2ae107283befd9284e650d840f8f7568cb9060b5466d25dc48975"}, - {file = "zope.interface-5.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ef739fe89e7f43fb6494a43b1878a36273e5924869ba1d866f752c5812ae8d58"}, - {file = "zope.interface-5.1.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:eb9b92f456ff3ec746cd4935b73c1117538d6124b8617bc0fe6fda0b3816e345"}, - {file = "zope.interface-5.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:dcefc97d1daf8d55199420e9162ab584ed0893a109f45e438b9794ced44c9fd0"}, - {file = "zope.interface-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:f40db0e02a8157d2b90857c24d89b6310f9b6c3642369852cdc3b5ac49b92afc"}, - {file = "zope.interface-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:14415d6979356629f1c386c8c4249b4d0082f2ea7f75871ebad2e29584bd16c5"}, - {file = "zope.interface-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5e86c66a6dea8ab6152e83b0facc856dc4d435fe0f872f01d66ce0a2131b7f1d"}, - {file = "zope.interface-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:39106649c3082972106f930766ae23d1464a73b7d30b3698c986f74bf1256a34"}, - {file = "zope.interface-5.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8cccf7057c7d19064a9e27660f5aec4e5c4001ffcf653a47531bde19b5aa2a8a"}, - {file = "zope.interface-5.1.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:562dccd37acec149458c1791da459f130c6cf8902c94c93b8d47c6337b9fb826"}, - {file = "zope.interface-5.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:da2844fba024dd58eaa712561da47dcd1e7ad544a257482392472eae1c86d5e5"}, - {file = "zope.interface-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:1ae4693ccee94c6e0c88a4568fb3b34af8871c60f5ba30cf9f94977ed0e53ddd"}, - {file = "zope.interface-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:dd98c436a1fc56f48c70882cc243df89ad036210d871c7427dc164b31500dc11"}, - {file = "zope.interface-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b87ed2dc05cb835138f6a6e3595593fea3564d712cb2eb2de963a41fd35758c"}, - {file = "zope.interface-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:558a20a0845d1a5dc6ff87cd0f63d7dac982d7c3be05d2ffb6322a87c17fa286"}, - {file = "zope.interface-5.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b726194f938791a6691c7592c8b9e805fc6d1b9632a833b9c0640828cd49cbc"}, - {file = "zope.interface-5.1.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:60a207efcd8c11d6bbeb7862e33418fba4e4ad79846d88d160d7231fcb42a5ee"}, - {file = "zope.interface-5.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b054eb0a8aa712c8e9030065a59b5e6a5cf0746ecdb5f087cca5ec7685690c19"}, - {file = "zope.interface-5.1.0-cp38-cp38-win32.whl", hash = "sha256:27d287e61639d692563d9dab76bafe071fbeb26818dd6a32a0022f3f7ca884b5"}, - {file = "zope.interface-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:a5f8f85986197d1dd6444763c4a15c991bfed86d835a1f6f7d476f7198d5f56a"}, - {file = "zope.interface-5.1.0.tar.gz", hash = "sha256:40e4c42bd27ed3c11b2c983fecfb03356fae1209de10686d03c02c8696a1d90e"}, + {file = "zope.interface-5.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:518950fe6a5d56f94ba125107895f938a4f34f704c658986eae8255edb41163b"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6278c080d4afffc9016e14325f8734456831124e8c12caa754fd544435c08386"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:538298e4e113ccb8b41658d5a4b605bebe75e46a30ceca22a5a289cf02c80bec"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:778d0ec38bbd288b150a3ae363c8ffd88d2207a756842495e9bffd8a8afbc89a"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:64ea6c221aeee4796860405e1aedec63424cda4202a7ad27a5066876db5b0fd2"}, + {file = "zope.interface-5.2.0-cp27-cp27m-win32.whl", hash = "sha256:92dc0fb79675882d0b6138be4bf0cec7ea7c7eede60aaca78303d8e8dbdaa523"}, + {file = "zope.interface-5.2.0-cp27-cp27m-win_amd64.whl", hash = "sha256:844fad925ac5c2ad4faaceb3b2520ad016b5280105c6e16e79838cf951903a7b"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:588384d70a0f19b47409cfdb10e0c27c20e4293b74fc891df3d8eb47782b8b3e"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:07d61722dd7d85547b7c6b0f5486b4338001fab349f2ac5cabc0b7182eb3425d"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:495b63fd0302f282ee6c1e6ea0f1c12cb3d1a49c8292d27287f01845ff252a96"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:299bde0ab9e5c4a92f01a152b7fbabb460f31343f1416f9b7b983167ab1e33bc"}, + {file = "zope.interface-5.2.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:32546af61a9a9b141ca38d971aa6eb9800450fa6620ce6323cc30eec447861f3"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2ab88d8f228f803fcb8cb7d222c579d13dab2d3622c51e8cf321280da01102a7"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:cbd0f2cbd8689861209cd89141371d3a22a11613304d1f0736492590aa0ab332"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:21e49123f375703cf824214939d39df0af62c47d122d955b2a8d9153ea08cfd5"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:eccac3d9aadc68e994b6d228cb0c8919fc47a5350d85a1b4d3d81d1e98baf40c"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:42b278ac0989d6f5cf58d7e0828ea6b5951464e3cf2ff229dd09a96cb6ba0c86"}, + {file = "zope.interface-5.2.0-cp35-cp35m-win32.whl", hash = "sha256:83b4aa5344cce005a9cff5d0321b2e318e871cc1dfc793b66c32dd4f59e9770d"}, + {file = "zope.interface-5.2.0-cp35-cp35m-win_amd64.whl", hash = "sha256:4df9afd17bd5477e9f8c8b6bb8507e18dd0f8b4efe73bb99729ff203279e9e3b"}, + {file = "zope.interface-5.2.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:1743bcfe45af8846b775086471c28258f4c6e9ee8ef37484de4495f15a98b549"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aedc6c672b351afe6dfe17ff83ee5e7eb6ed44718f879a9328a68bdb20b57e11"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4af87cdc0d4b14e600e6d3d09793dce3b7171348a094ba818e2a68ae7ee67546"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b7a00ecb1434f8183395fac5366a21ee73d14900082ca37cf74993cf46baa56c"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:8ceb3667dd13b8133f2e4d637b5b00f240f066448e2aa89a41f4c2d78a26ce50"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:efef581c8ba4d990770875e1a2218e856849d32ada2680e53aebc5d154a17e20"}, + {file = "zope.interface-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:e4bc372b953bf6cec65a8d48482ba574f6e051621d157cf224227dbb55486b1e"}, + {file = "zope.interface-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3cc94c69f6bd48ed86e8e24f358cb75095c8129827df1298518ab860115269a4"}, + {file = "zope.interface-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ba32f4a91c1cb7314c429b03afbf87b1fff4fb1c8db32260e7310104bd77f0c7"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1b5f6c8fff4ed32aa2dd43e84061bc8346f32d3ba6ad6e58f088fe109608f102"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:efd550b3da28195746bb43bd1d815058181a7ca6d9d6aa89dd37f5eefe2cacb7"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:aab9f1e34d810feb00bf841993552b8fcc6ae71d473c505381627143d0018a6a"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:55465121e72e208a7b69b53de791402affe6165083b2ea71b892728bd19ba9ae"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:32b40a4c46d199827d79c86bb8cb88b1bbb764f127876f2cb6f3a47f63dbada3"}, + {file = "zope.interface-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:abb61afd84f23099ac6099d804cdba9bd3b902aaaded3ffff47e490b0a495520"}, + {file = "zope.interface-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:21f579134a47083ffb5ddd1307f0405c91aa8b61ad4be6fd5af0171474fe0c45"}, + {file = "zope.interface-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c48ddb63e2b20fba4c6a2bf81b4d49e99b6d4587fb67a6cd33a2c1f003af3e3"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2dcab01c660983ba5e5a612e0c935141ccbee67d2e2e14b833e01c2354bd8034"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:150e8bcb7253a34a4535aeea3de36c0bb3b1a6a47a183a95d65a194b3e07f232"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:681dbb33e2b40262b33fd383bae63c36d33fd79fa1a8e4092945430744ffd34a"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:2ced4c35061eea623bc84c7711eedce8ecc3c2c51cd9c6afa6290df3bae9e104"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f37d45fab14ffef9d33a0dc3bc59ce0c5313e2253323312d47739192da94f5fd"}, + {file = "zope.interface-5.2.0-cp38-cp38-win32.whl", hash = "sha256:9789bd945e9f5bd026ed3f5b453d640befb8b1fc33a779c1fe8d3eb21fe3fb4a"}, + {file = "zope.interface-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0a990dcc97806e5980bbb54b2e46b9cde9e48932d8e6984daf71ef1745516123"}, + {file = "zope.interface-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b94df9f2fdde7b9314321bab8448e6ad5a23b80542dcab53e329527d4099dcb"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6936aa9da390402d646a32a6a38d5409c2d2afb2950f045a7d02ab25a4e7d08d"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:adf9ee115ae8ff8b6da4b854b4152f253b390ba64407a22d75456fe07dcbda65"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:f44906f70205d456d503105023041f1e63aece7623b31c390a0103db4de17537"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f057897711a630a0b7a6a03f1acf379b6ba25d37dc5dc217a97191984ba7f2fc"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:05a97ba92c1c7c26f25c9f671aa1ef85ffead6cdad13770e5b689cf983adc7e1"}, + {file = "zope.interface-5.2.0-cp39-cp39-win32.whl", hash = "sha256:27c267dc38a0f0079e96a2945ee65786d38ef111e413c702fbaaacbab6361d00"}, + {file = "zope.interface-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2b6d6eb693bc2fc6c484f2e5d93bd0b0da803fa77bf974f160533e555e4d095"}, + {file = "zope.interface-5.2.0.tar.gz", hash = "sha256:8251f06a77985a2729a8bdbefbae79ee78567dddc3acbd499b87e705ca59fe24"}, ] diff --git a/pyproject.toml b/pyproject.toml index f47a694..364ea1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "django-pgschemas" -version = "0.5.0" +version = "1.0.0-alpha.0" description = "Multi-tenancy on Django using PostgreSQL schemas." license = "MIT" authors = ["Lorenzo Peña "] @@ -9,7 +9,7 @@ repository = "https://github.com/lorinkoz/django-pgschemas" documentation = "https://django-pgschemas.readthedocs.io/" keywords = ["django", "tenants", "schemas", "multi-tenancy", "postgresql"] classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production", "Framework :: Django", "Framework :: Django :: 2.0", "Framework :: Django :: 2.1", @@ -23,10 +23,11 @@ django = ">=2.0,<4.0" psycopg2 = "^2.7" [tool.poetry.dev-dependencies] -black = "^19.10b0" +black = "^20.8b1" channels = "^2.1" coverage = {extras = ["toml"], version = "^5.1"} doc8 = "^0.8.0" +isort = "^5.6.4" sphinx = "^1.8" sphinx_rtd_theme = "^0.4.2" sphinx-django-command = "^0.1.3" @@ -34,6 +35,12 @@ sphinx-django-command = "^0.1.3" [tool.black] line-length = 120 +[tool.isort] +atomic = true +combine_as_imports = true +line_length = 120 +profile = "black" + [tool.coverage.run] source = ["django_pgschemas"] diff --git a/requirements.txt b/requirements.txt index c6dba8e..16ff5e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ constantly==15.1.0 coverage==5.1 cryptography==2.9 daphne==2.5.0 -django==3.0.5 +django==3.0.7 doc8==0.8.0 docutils==0.16 hyperlink==19.0.0