From 2e562fc279c99f90322b52aaa10cefc9e0fbd1ba Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Tue, 12 Oct 2021 12:29:12 +0300 Subject: [PATCH 1/7] feat: sqlalchemy plugin --- boxv2/plugins/sqlalchemy/__init__.py | 0 boxv2/plugins/sqlalchemy/plugin.py | 0 .../sqlalchemy/template/cookiecutter.json | 11 ++ .../{{cookiecutter.bot_name}}/alembic.ini | 38 +++++ .../migrations/env.py | 38 +++++ .../migrations/script.py.mako | 24 +++ .../versions/c630e92c23e1_botx_main_tables.py | 152 ++++++++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 boxv2/plugins/sqlalchemy/__init__.py create mode 100644 boxv2/plugins/sqlalchemy/plugin.py create mode 100644 boxv2/plugins/sqlalchemy/template/cookiecutter.json create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/script.py.mako create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py diff --git a/boxv2/plugins/sqlalchemy/__init__.py b/boxv2/plugins/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boxv2/plugins/sqlalchemy/plugin.py b/boxv2/plugins/sqlalchemy/plugin.py new file mode 100644 index 0000000..e69de29 diff --git a/boxv2/plugins/sqlalchemy/template/cookiecutter.json b/boxv2/plugins/sqlalchemy/template/cookiecutter.json new file mode 100644 index 0000000..395902c --- /dev/null +++ b/boxv2/plugins/sqlalchemy/template/cookiecutter.json @@ -0,0 +1,11 @@ +{ + "bot_name": "boxv2-bot", + "bot_display_name": "boxv2 bot", + "bot_short_description": "TODO", + "bot_docs_link": "TODO", + "test_cov": 0, + + "bot_image_name_prefix": "registry.example.com/bots/", + + "bot_name_underscored": "{{ cookiecutter.bot_name|replace('-', '_') }}" +} diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini new file mode 100644 index 0000000..ce65b9e --- /dev/null +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini @@ -0,0 +1,38 @@ +# https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file + +[alembic] +script_location = ./migrations + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py new file mode 100644 index 0000000..b91765e --- /dev/null +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py @@ -0,0 +1,38 @@ +import pathlib +import sys +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool + +# init config +sys.path.append(str(pathlib.Path(__file__).resolve().parents[1])) + +from boxv2.application import get_app_settings # isort:skip + +settings = get_app_settings() + +context_config = context.config + +fileConfig(context_config.config_file_name) + +target_metadata = None + +context_config.set_main_option("sqlalchemy.url", str(settings.POSTGRES_DSN)) + + +def run_migrations_online() -> None: + connectable = engine_from_config( + context_config.get_section(context_config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +run_migrations_online() diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/script.py.mako b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/script.py.mako new file mode 100644 index 0000000..f20fc2b --- /dev/null +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +Doc: https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py new file mode 100644 index 0000000..1b18706 --- /dev/null +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py @@ -0,0 +1,152 @@ +"""botx main tables + +Revision ID: c630e92c23e1 +Revises: +Create Date: 2019-06-28 17:22:48.360104 + +Doc: https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects.postgresql import UUID + +revision = "c630e92c23e1" +down_revision = None +branch_labels = None +depends_on = None + + +def create_botxcts_table() -> None: + op.create_table("botxcts", sa.Column("host", sa.Text, primary_key=True)) + + +def create_botxbot_table() -> None: + op.create_table( + "botxbot", + sa.Column("bot_id", UUID, primary_key=True), + sa.Column("name", sa.Text), + sa.Column("current_bot", sa.Boolean, default=False), + sa.Column( + "cts_id", + sa.Text, + sa.ForeignKey("botxcts.host", ondelete="CASCADE"), + nullable=False, + ), + ) + op.execute( + """ + CREATE OR REPLACE FUNCTION check_only_one_current_bot_on_cts() + RETURNS TRIGGER AS + $$ + BEGIN + IF NEW.current_bot = TRUE AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN + IF NEW.cts_id IN ( + SELECT cts_id + FROM "botxbot" + WHERE current_bot = TRUE AND bot_id != NEW.bot_id + GROUP BY cts_id + ) THEN + RAISE EXCEPTION '% cts already has registered main bot', NEW.cts_id; + END IF; + END IF; + + RETURN NEW; + END; + $$ LANGUAGE 'plpgsql'; + + CREATE TRIGGER check_only_one_current_bot_on_cts_trigger + BEFORE INSERT OR UPDATE + ON "botxbot" + FOR EACH ROW + EXECUTE PROCEDURE check_only_one_current_bot_on_cts(); + """ + ) + + +def create_botxuser_table() -> None: + op.create_table( + "botxuser", + sa.Column("user_huid", UUID, primary_key=True), + sa.Column("username", sa.Text), + sa.Column("ad_login", sa.Text), + sa.Column("ad_domain", sa.Text), + sa.Column("cts_id", sa.Text, sa.ForeignKey("botxcts.host", ondelete="CASCADE")), + ) + + +def create_botxchat_table() -> None: + op.create_table( + "botxchat", + sa.Column("group_chat_id", UUID, primary_key=True), + sa.Column("last_sync_id", UUID), + sa.Column("chat_type", sa.Text, nullable=False), + sa.Column( + "creator_id", UUID, sa.ForeignKey("botxuser.user_huid", ondelete="SET NULL") + ), + sa.Column( + "cts_id", + sa.Text, + sa.ForeignKey("botxcts.host", ondelete="CASCADE"), + nullable=False, + ), + ) + + +def create_chat_admins_table() -> None: + op.create_table( + "chat_user_admins", + sa.Column( + "botxchat_id", + UUID, + sa.ForeignKey("botxchat.group_chat_id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column( + "botxuser_id", + UUID, + sa.ForeignKey("botxuser.user_huid", ondelete="CASCADE"), + nullable=False, + ), + ) + op.create_primary_key( + "pk_chat_user_admins", "chat_user_admins", ["botxchat_id", "botxuser_id"] + ) + + +def create_chat_members_table() -> None: + op.create_table( + "chat_user_members", + sa.Column( + "botxchat_id", + UUID, + sa.ForeignKey("botxchat.group_chat_id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column( + "botxuser_id", + UUID, + sa.ForeignKey("botxuser.user_huid", ondelete="CASCADE"), + nullable=False, + ), + ) + op.create_primary_key( + "pk_chat_user_members", "chat_user_members", ["botxchat_id", "botxuser_id"] + ) + + +def upgrade() -> None: + create_botxcts_table() + create_botxbot_table() + create_botxuser_table() + create_botxchat_table() + create_chat_members_table() + create_chat_admins_table() + + +def downgrade() -> None: + op.drop_table("chat_user_members") + op.drop_table("chat_user_admins") + op.drop_table("botxchat") + op.drop_table("botxuser") + op.drop_table("botxbot") + op.drop_table("botxcts") From 68ccafc7de34737fd4d53ba235e62f6b65a21c9a Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Wed, 13 Oct 2021 16:05:51 +0300 Subject: [PATCH 2/7] feat: sqlalchemy + healthcheck --- boxv2/__main__.py | 1 + boxv2/application.py | 1 + boxv2/main.py | 18 +- boxv2/plugin.py | 29 +- boxv2/plugins/debug/commands.py | 16 +- boxv2/plugins/redis/plugin.py | 22 +- boxv2/plugins/sqlalchemy/__init__.py | 3 + boxv2/plugins/sqlalchemy/plugin.py | 62 +++ .../{{cookiecutter.bot_name}}/alembic.ini | 2 +- .../app/db/__init__.py | 0 .../app/db/migrations/__init__.py | 0 .../{ => app/db}/migrations/env.py | 14 +- .../{ => app/db}/migrations/script.py.mako | 0 .../app/db/migrations/versions/__init__.py | 0 .../app/db/models.py | 82 +++ .../versions/c630e92c23e1_botx_main_tables.py | 152 ------ boxv2/plugins/sqlalchemy/url_scheme_utils.py | 11 + boxv2/plugins/tortoise/__init__.py | 3 - boxv2/plugins/tortoise/dependencies.py | 75 --- boxv2/plugins/tortoise/models.py | 89 ---- boxv2/plugins/tortoise/plugin.py | 36 -- boxv2/plugins/tortoise/repo.py | 35 -- .../tortoise/template/cookiecutter.json | 11 - .../{{cookiecutter.bot_name}}/alembic.ini | 38 -- .../migrations/env.py | 38 -- .../migrations/script.py.mako | 24 - .../versions/c630e92c23e1_botx_main_tables.py | 152 ------ .../{{cookiecutter.bot_name}}/pyproject.toml | 2 +- poetry.lock | 503 ++++++++++-------- pyproject.toml | 5 +- setup.cfg | 6 +- 31 files changed, 534 insertions(+), 896 deletions(-) create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/__init__.py create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/__init__.py rename boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/{ => app/db}/migrations/env.py (68%) rename boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/{ => app/db}/migrations/script.py.mako (100%) create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/versions/__init__.py create mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py delete mode 100644 boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py create mode 100644 boxv2/plugins/sqlalchemy/url_scheme_utils.py delete mode 100644 boxv2/plugins/tortoise/__init__.py delete mode 100644 boxv2/plugins/tortoise/dependencies.py delete mode 100644 boxv2/plugins/tortoise/models.py delete mode 100644 boxv2/plugins/tortoise/plugin.py delete mode 100644 boxv2/plugins/tortoise/repo.py delete mode 100644 boxv2/plugins/tortoise/template/cookiecutter.json delete mode 100644 boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/alembic.ini delete mode 100644 boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/env.py delete mode 100644 boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/script.py.mako delete mode 100644 boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py diff --git a/boxv2/__main__.py b/boxv2/__main__.py index 54f4139..7ad8ab3 100644 --- a/boxv2/__main__.py +++ b/boxv2/__main__.py @@ -1,3 +1,4 @@ +"""Module as an executable magic file.""" from boxv2.main import main main() diff --git a/boxv2/application.py b/boxv2/application.py index 02c202b..abcbb5c 100644 --- a/boxv2/application.py +++ b/boxv2/application.py @@ -38,6 +38,7 @@ def get_application(settings: Optional[BaseAppSettings] = None) -> FastAPI: application.add_event_handler("shutdown", bot_shutdown(bot)) for plugin in plugin_instances: + setattr(application.state, plugin.get_name(), plugin) application.add_event_handler("startup", plugin.on_startup) application.add_event_handler("shutdown", plugin.on_shutdown) diff --git a/boxv2/main.py b/boxv2/main.py index 307d78b..be1ccff 100644 --- a/boxv2/main.py +++ b/boxv2/main.py @@ -3,10 +3,11 @@ import sys from pathlib import Path from tempfile import mkdtemp -from typing import Optional +from typing import Any, Optional import click -from cookiecutter.exceptions import CookiecutterException, RepositoryNotFound +from cookiecutter.exceptions import CookiecutterException # type: ignore +from cookiecutter.exceptions import RepositoryNotFound from cookiecutter.main import cookiecutter # type: ignore from loguru import logger @@ -35,7 +36,7 @@ def main( render_plugins_template(plg, extra_context) -def render_plugins_template(plugin: str, extra_content: dict[str, str]) -> None: +def render_plugins_template(plugin: str, extra_content: dict[str, Any]) -> None: """Render plugin's template (if exists) and add it to project.""" temp_dir = Path(mkdtemp()) @@ -60,7 +61,7 @@ def render_plugins_template(plugin: str, extra_content: dict[str, str]) -> None: shutil.rmtree(temp_dir) -def render_extra_template(template: str, extra_content: dict[str, str]) -> None: +def render_extra_template(template: str, extra_content: dict[str, Any]) -> None: """Render template to overwrite existing default.""" temp_dir = Path(mkdtemp()) try: @@ -79,10 +80,10 @@ def render_extra_template(template: str, extra_content: dict[str, str]) -> None: def _copy_inspect(path: str, names: list[str]) -> set[str]: ignore = set() for name in names: - if name != "__pycache__": - logger.debug(f"Copying file {path}/{name}...") - else: + if name == "__pycache__": ignore.add(name) + else: + logger.debug(f"Copying file {path}/{name}...") return ignore @@ -94,8 +95,7 @@ def _get_logger_level(int_level: int) -> str: return "DEBUG" elif int_level == 1: return "INFO" - else: - return "WARNING" + return "WARNING" if __name__ == "__main__": diff --git a/boxv2/plugin.py b/boxv2/plugin.py index dc9ba7c..8cbf024 100644 --- a/boxv2/plugin.py +++ b/boxv2/plugin.py @@ -2,12 +2,12 @@ import inspect from pathlib import Path -from typing import Any, Type +from typing import Any, Optional, Type from botx import Bot from botx.dependencies.models import Depends from fastapi import FastAPI -from pydantic import BaseSettings +from pydantic import BaseModel, BaseSettings from boxv2.utils.import_utils import import_object @@ -20,6 +20,14 @@ class Config: # noqa: WPS431 json_loads = lambda x: x # noqa: E731,WPS111 +class HealtCheckData(BaseModel): + """Data returning by plugin's healthcheck method.""" + + healthcheck_supported: bool = True + healthy: Optional[bool] # True - ok, False - error + information: dict[str, Any] = {} + + class BasePlugin: """Base class for plugins.""" @@ -42,10 +50,23 @@ async def on_startup(self, *args: Any, **kwargs: Any) -> None: async def on_shutdown(self, *args: Any, **kwargs: Any) -> None: """Shutdown hook.""" + async def healthcheck(self) -> HealtCheckData: + """Runtime check for plugin functioning.""" + return HealtCheckData(healthcheck_supported=False) + @classmethod def get_template_path(cls) -> Path: """Absolute path to plugin's template directory.""" - return Path(inspect.getfile(cls)).resolve().parents[0] / cls.template + plugin_path = Path(inspect.getfile(cls)).resolve().parents[0] + return plugin_path / cls.template + + @classmethod + def get_name(cls) -> str: + """Get plugin's name.""" + module = inspect.getmodule(cls) + if module is not None: + return module.__name__.split(".")[-2] + return cls.__name__.lower() def _merge_settings(self) -> None: # noqa: WPS231 app_values = {} @@ -61,7 +82,7 @@ def _merge_settings(self) -> None: # noqa: WPS231 app_values[key] = getattr(self.settings, key) plugin_settings = self.settings_class(**app_values) - for field_name in plugin_settings.__fields__: + for field_name in plugin_settings.__fields__: # noqa: WPS609 self.settings.__dict__[field_name] = getattr(plugin_settings, field_name) diff --git a/boxv2/plugins/debug/commands.py b/boxv2/plugins/debug/commands.py index 0db7cc9..abaa1f5 100644 --- a/boxv2/plugins/debug/commands.py +++ b/boxv2/plugins/debug/commands.py @@ -10,4 +10,18 @@ @collector.hidden(command="/_debug:commit_sha") async def commit_sha(message: Message, bot: Bot) -> None: """Show git commit SHA.""" - await bot.answer_message(environ.get("COMMIT_SHA"), message) + await bot.answer_message(str(environ.get("COMMIT_SHA")), message) + + +@collector.hidden(command="/_debug:plugins") +async def plugins(message: Message, bot: Bot) -> None: + """Show git commit SHA.""" + names_and_status = [ + (plugin.get_name(), (await plugin.healthcheck()).json(indent=2)) + for plugin in bot.state.plugins + ] + template = "**{name}**\n ```json\n{status}\n``` \n\n" + text = "\n\n".join( + [template.format(name=name, status=status) for name, status in names_and_status] + ) + await bot.answer_message(text, message) diff --git a/boxv2/plugins/redis/plugin.py b/boxv2/plugins/redis/plugin.py index abc5488..c2a4698 100644 --- a/boxv2/plugins/redis/plugin.py +++ b/boxv2/plugins/redis/plugin.py @@ -4,7 +4,7 @@ from pydantic import RedisDsn -from boxv2.plugin import BasePlugin, BasePluginSettings +from boxv2.plugin import BasePlugin, BasePluginSettings, HealtCheckData from boxv2.plugins.redis.repo import RedisRepo @@ -22,7 +22,7 @@ class Plugin(BasePlugin): async def on_startup(self, *args: Any, **kwargs: Any) -> None: """Startup hook.""" - prefix = self.settings.REDIS_PREFIX or self.settings.NAME + prefix = self._get_prefix() expire = self.settings.REDIS_EXPIRE or 0 self.redis_repo = await RedisRepo.init( dsn=str(self.settings.REDIS_DSN), prefix=prefix, expire=expire @@ -32,3 +32,21 @@ async def on_startup(self, *args: Any, **kwargs: Any) -> None: async def on_shutdown(self, *args: Any, **kwargs: Any) -> None: """Shutdown hook.""" await self.redis_repo.close() + + async def healthcheck(self) -> HealtCheckData: + """Healthcheck.""" + try: + information = await self.redis_repo.redis.info() + except Exception as exc: + return HealtCheckData(healthy=False, info={"error": str(exc)}) + return HealtCheckData( + healthy=True, + information={ + "server_version": information["server"]["redis_version"], + "dsn": self.settings.REDIS_DSN, + "prefix": self._get_prefix(), + }, + ) + + def _get_prefix(self) -> str: + return self.settings.REDIS_PREFIX or self.settings.NAME diff --git a/boxv2/plugins/sqlalchemy/__init__.py b/boxv2/plugins/sqlalchemy/__init__.py index e69de29..e5c8a0d 100644 --- a/boxv2/plugins/sqlalchemy/__init__.py +++ b/boxv2/plugins/sqlalchemy/__init__.py @@ -0,0 +1,3 @@ +from .plugin import SQLAlchemyPlugin as Plugin + +__all__ = ["Plugin"] diff --git a/boxv2/plugins/sqlalchemy/plugin.py b/boxv2/plugins/sqlalchemy/plugin.py index e69de29..64c4767 100644 --- a/boxv2/plugins/sqlalchemy/plugin.py +++ b/boxv2/plugins/sqlalchemy/plugin.py @@ -0,0 +1,62 @@ +"""Postgresql database plugin.""" + +from typing import Any, Optional + +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine +from sqlalchemy.orm import sessionmaker + +from boxv2.plugin import BasePlugin, BasePluginSettings, HealtCheckData +from boxv2.plugins.sqlalchemy.url_scheme_utils import make_url_async + + +class Settings(BasePluginSettings): + """Settings for Tortoise ORM plugin.""" + + POSTGRES_DSN: str + SQL_DEBUG: bool = False + + +class SQLAlchemyPlugin(BasePlugin): + """Tortoise ORM plugin.""" + + settings_class = Settings + _session: Optional[AsyncSession] + engine: Optional[AsyncEngine] + + async def on_startup(self, *args: Any, **kwargs: Any) -> None: + """Startup hook.""" + self.engine = create_async_engine( + make_url_async(self.settings.POSTGRES_DSN), echo=self.settings.SQL_DEBUG + ) + + make_session = sessionmaker( + self.engine, expire_on_commit=False, class_=AsyncSession + ) + self._session = make_session() + + async def on_shutdown(self, *args: Any, **kwargs: Any) -> None: + """Shutdown hook.""" + if self.session is not None: + await self.session.close() + + async def healthcheck(self) -> HealtCheckData: + """Healthcheck function.""" + try: + async with self.session.begin(): + rows = await self.session.execute("select version()") + except Exception as exc: + return HealtCheckData(healthy=False, information={"error": str(exc)}) + return HealtCheckData( + healthy=True, + info={ + "server_version": rows.scalars().one(), + "dsn": self.settings.POSTGRES_DSN, + }, + ) + + @property + def session(self) -> AsyncSession: + """Return an SQLAlchemy session instance.""" + if self._session is None: + raise RuntimeError("Plugin not yet initialized!") + return self._session diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini index ce65b9e..ef1d150 100644 --- a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/alembic.ini @@ -1,7 +1,7 @@ # https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file [alembic] -script_location = ./migrations +script_location = ./app/db/migrations [loggers] keys = root,sqlalchemy,alembic diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/__init__.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/__init__.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/env.py similarity index 68% rename from boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py rename to boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/env.py index b91765e..25c06d4 100644 --- a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/env.py +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/env.py @@ -6,19 +6,17 @@ from sqlalchemy import engine_from_config, pool # init config -sys.path.append(str(pathlib.Path(__file__).resolve().parents[1])) +sys.path.append(str(pathlib.Path(__file__).resolve().parents[3])) from boxv2.application import get_app_settings # isort:skip +from boxv2.plugins.sqlalchemy.url_scheme_utils import make_url_sync # isort:skip +from app.db.models import Base # isort:skip -settings = get_app_settings() - +postgres_dsn = make_url_sync(get_app_settings().POSTGRES_DSN) context_config = context.config - fileConfig(context_config.config_file_name) - -target_metadata = None - -context_config.set_main_option("sqlalchemy.url", str(settings.POSTGRES_DSN)) +target_metadata = Base.metadata +context_config.set_main_option("sqlalchemy.url", postgres_dsn) def run_migrations_online() -> None: diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/script.py.mako b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/script.py.mako similarity index 100% rename from boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/script.py.mako rename to boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/script.py.mako diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/versions/__init__.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/migrations/versions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py new file mode 100644 index 0000000..c4e8a26 --- /dev/null +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py @@ -0,0 +1,82 @@ +"""Database models declarations.""" + +from typing import Any, Generic, List, TypeVar + +from sqlalchemy import Column, Integer, String, insert, update as _update +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.future import select + +T = TypeVar("T") # noqa: WPS111 + +Base = declarative_base() + + +# This is an example of asynchronous usage of SQLAlchemy. +# You may rewrite this class as need for your models. +class CRUDMixin(Generic[T]): + """Mixin for CRUD operations for models.""" + + id: int # noqa: WPS125 + + @classmethod + async def create(cls, **kwargs: Any) -> None: + """Create object.""" + query = insert(cls).values(**kwargs) + session = get_session() + async with session.begin(): + await session.execute(query) + + @classmethod + async def update(cls, id: int, **kwargs: Any) -> None: # noqa: WPS125 + """Update object by id.""" + query = ( + _update(cls) + .where(cls.id == id) + .values(**kwargs) + .execution_options(synchronize_session="fetch") + ) + session = get_session() + async with session.begin(): + await session.execute(query) + + @classmethod + async def get(cls, id: int) -> T: # noqa: WPS125 + """Get object by id.""" + query = select(cls).where(cls.id == id) + session = get_session() + async with session.begin(): + rows = await session.execute(query) + return rows.scalars().one() + + @classmethod + async def all(cls) -> List[T]: # noqa: WPS125 + """Get all objects.""" + query = select(cls) + session = get_session() + async with session.begin(): + rows = await session.execute(query) + return rows.scalars().all() + + +# This is an example model. You may rewrite it a you need or write any other models. +# After it you may run `alembic revision --autogenerate` to generate migrations +# and `alembic upgrade head` to apply it on database. +# Migrations files will be stored at `app/db/migrations/versions` +class Record(Base, CRUDMixin): + """Simple database model for example.""" + + __tablename__ = "record" + + id: int = Column(Integer, primary_key=True, autoincrement=True) # noqa: WPS125 + record_data: str = Column(String) + + def __repr__(self) -> str: + """Show string representation of record.""" + return self.record_data + + +def get_session() -> AsyncSession: + from app.main import app # should not be imported before app initialization + + return app.state.sqlalchemy.session diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py deleted file mode 100644 index 1b18706..0000000 --- a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py +++ /dev/null @@ -1,152 +0,0 @@ -"""botx main tables - -Revision ID: c630e92c23e1 -Revises: -Create Date: 2019-06-28 17:22:48.360104 - -Doc: https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script -""" -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects.postgresql import UUID - -revision = "c630e92c23e1" -down_revision = None -branch_labels = None -depends_on = None - - -def create_botxcts_table() -> None: - op.create_table("botxcts", sa.Column("host", sa.Text, primary_key=True)) - - -def create_botxbot_table() -> None: - op.create_table( - "botxbot", - sa.Column("bot_id", UUID, primary_key=True), - sa.Column("name", sa.Text), - sa.Column("current_bot", sa.Boolean, default=False), - sa.Column( - "cts_id", - sa.Text, - sa.ForeignKey("botxcts.host", ondelete="CASCADE"), - nullable=False, - ), - ) - op.execute( - """ - CREATE OR REPLACE FUNCTION check_only_one_current_bot_on_cts() - RETURNS TRIGGER AS - $$ - BEGIN - IF NEW.current_bot = TRUE AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN - IF NEW.cts_id IN ( - SELECT cts_id - FROM "botxbot" - WHERE current_bot = TRUE AND bot_id != NEW.bot_id - GROUP BY cts_id - ) THEN - RAISE EXCEPTION '% cts already has registered main bot', NEW.cts_id; - END IF; - END IF; - - RETURN NEW; - END; - $$ LANGUAGE 'plpgsql'; - - CREATE TRIGGER check_only_one_current_bot_on_cts_trigger - BEFORE INSERT OR UPDATE - ON "botxbot" - FOR EACH ROW - EXECUTE PROCEDURE check_only_one_current_bot_on_cts(); - """ - ) - - -def create_botxuser_table() -> None: - op.create_table( - "botxuser", - sa.Column("user_huid", UUID, primary_key=True), - sa.Column("username", sa.Text), - sa.Column("ad_login", sa.Text), - sa.Column("ad_domain", sa.Text), - sa.Column("cts_id", sa.Text, sa.ForeignKey("botxcts.host", ondelete="CASCADE")), - ) - - -def create_botxchat_table() -> None: - op.create_table( - "botxchat", - sa.Column("group_chat_id", UUID, primary_key=True), - sa.Column("last_sync_id", UUID), - sa.Column("chat_type", sa.Text, nullable=False), - sa.Column( - "creator_id", UUID, sa.ForeignKey("botxuser.user_huid", ondelete="SET NULL") - ), - sa.Column( - "cts_id", - sa.Text, - sa.ForeignKey("botxcts.host", ondelete="CASCADE"), - nullable=False, - ), - ) - - -def create_chat_admins_table() -> None: - op.create_table( - "chat_user_admins", - sa.Column( - "botxchat_id", - UUID, - sa.ForeignKey("botxchat.group_chat_id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column( - "botxuser_id", - UUID, - sa.ForeignKey("botxuser.user_huid", ondelete="CASCADE"), - nullable=False, - ), - ) - op.create_primary_key( - "pk_chat_user_admins", "chat_user_admins", ["botxchat_id", "botxuser_id"] - ) - - -def create_chat_members_table() -> None: - op.create_table( - "chat_user_members", - sa.Column( - "botxchat_id", - UUID, - sa.ForeignKey("botxchat.group_chat_id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column( - "botxuser_id", - UUID, - sa.ForeignKey("botxuser.user_huid", ondelete="CASCADE"), - nullable=False, - ), - ) - op.create_primary_key( - "pk_chat_user_members", "chat_user_members", ["botxchat_id", "botxuser_id"] - ) - - -def upgrade() -> None: - create_botxcts_table() - create_botxbot_table() - create_botxuser_table() - create_botxchat_table() - create_chat_members_table() - create_chat_admins_table() - - -def downgrade() -> None: - op.drop_table("chat_user_members") - op.drop_table("chat_user_admins") - op.drop_table("botxchat") - op.drop_table("botxuser") - op.drop_table("botxbot") - op.drop_table("botxcts") diff --git a/boxv2/plugins/sqlalchemy/url_scheme_utils.py b/boxv2/plugins/sqlalchemy/url_scheme_utils.py new file mode 100644 index 0000000..6901860 --- /dev/null +++ b/boxv2/plugins/sqlalchemy/url_scheme_utils.py @@ -0,0 +1,11 @@ +"""Utility functions for sqlalchemy plugin.""" + + +def make_url_async(url: str) -> str: + """Add +asyncpg to url scheme.""" + return "postgresql+asyncpg" + url[url.find(":") :] # noqa: WPS336 + + +def make_url_sync(url: str) -> str: + """Remove +asyncpg from url scheme.""" + return "postgresql" + url[url.find(":") :] # noqa: WPS336 diff --git a/boxv2/plugins/tortoise/__init__.py b/boxv2/plugins/tortoise/__init__.py deleted file mode 100644 index 7d7931a..0000000 --- a/boxv2/plugins/tortoise/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import Plugin - -__all__ = ["Plugin"] diff --git a/boxv2/plugins/tortoise/dependencies.py b/boxv2/plugins/tortoise/dependencies.py deleted file mode 100644 index 753f8ae..0000000 --- a/boxv2/plugins/tortoise/dependencies.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Crud functions for usage in commands like dependencies.""" -from botx import CommandTypes, Depends, Message, SystemEvents, UserKinds - -from boxv2.plugins.tortoise.models import BotXBot, BotXChat, BotXCTS, BotXUser - - -@Depends -async def auto_models_update(message: Message) -> None: - """Save or update information about user(s), chat and cts.""" - cts, _ = await BotXCTS.get_or_create(host=message.host) - chat, _ = await BotXChat.get_or_create( - group_chat_id=message.group_chat_id, - defaults={"chat_type": message.chat_type, "cts": cts}, - ) - - current_bot, _ = await BotXBot.get_or_create( - bot_id=message.bot_id, defaults={"current_bot": True, "cts": cts} - ) - - chat.last_sync_id = message.sync_id - await chat.save() - - if message.command.command_type == CommandTypes.system: - if message.command.body == SystemEvents.chat_created.value: - await _update_chat_members(message, cts, chat) - else: - await _update_user_info(message, cts, chat) - - -async def _update_user_info(message: Message, cts: BotXCTS, chat: BotXChat) -> None: - """Create or update user's huid, username, login, domain and role in chat.""" - bot_user, _ = await BotXUser.get_or_create( - user_huid=message.user_huid, defaults={"cts": cts} - ) - if message.user.username: - bot_user.username = message.user.username - bot_user.ad_login = message.user.ad_login.lower() - bot_user.ad_domain = message.user.ad_domain.lower() - await bot_user.save() - - if message.user.is_creator: - chat.creator = bot_user - await chat.save() - - if message.user.is_admin: - await bot_user.administered_chats.add(chat) - else: - await chat.admins.remove(bot_user) - - await chat.members.add(bot_user) - - -async def _update_chat_members(message: Message, cts: BotXCTS, chat: BotXChat) -> None: - """Create or update chat's admins, creator and other members (users and bots).""" - for user in message.command.data.members: - if user.user_kind in {UserKinds.user, UserKinds.cts_user}: - bot_user, _ = await BotXUser.get_or_create( - user_huid=user.huid, - defaults={"username": user.name, "cts": cts}, - ) - await chat.members.add(bot_user) - - if user.admin: - await chat.admins.add(bot_user) - - if user.huid == message.command.data.creator: - chat.creator = bot_user - await chat.save() - - else: - bot, _ = await BotXBot.get_or_create( - bot_id=user.huid, defaults={"cts": cts} - ) - bot.name = user.name - await bot.save() diff --git a/boxv2/plugins/tortoise/models.py b/boxv2/plugins/tortoise/models.py deleted file mode 100644 index 082e443..0000000 --- a/boxv2/plugins/tortoise/models.py +++ /dev/null @@ -1,89 +0,0 @@ -"""BotX models for users and chats.""" -from botx import ChatTypes -from tortoise import fields, models - -HOST_MAX_LENGTH = 255 -CHAT_TYPE_MAX_LENGTH = 16 - - -class BotXCTS(models.Model): - """Model for cts. Has host only.""" - - host = fields.CharField(pk=True, max_length=HOST_MAX_LENGTH) - - bots: fields.ReverseRelation["BotXBot"] - chats: fields.ReverseRelation["BotXChat"] - - def __str__(self) -> str: # noqa: D105 - return f"<{self.__class__.__name__} host: {self.host}>" - - -class BotXBot(models.Model): - """Model for bot. Bot is chat member like user, but has less fields.""" - - bot_id = fields.UUIDField(pk=True) - name = fields.TextField(null=True) - # allow to find current bot in chats - current_bot = fields.BooleanField(default=False) - cts: fields.ForeignKeyRelation[BotXCTS] = fields.ForeignKeyField( - "botx.BotXCTS", related_name="bots", on_delete=fields.CASCADE - ) - - def __str__(self) -> str: # noqa: D105 - model = self.__class__.__name__ - return "<{0} bot_id: {1} current: {2}>".format( - model, self.bot_id, self.current_bot - ) - - -class BotXUser(models.Model): - """Model for chat member. If user on the same cts with bot botx send all fields.""" - - user_huid = fields.UUIDField(pk=True) - username = fields.TextField(null=True) - ad_login = fields.TextField(null=True) - ad_domain = fields.TextField(null=True) - cts: fields.ForeignKeyRelation[BotXCTS] = fields.ForeignKeyField( - "botx.BotXCTS", related_name="users", on_delete=fields.CASCADE - ) - - chats: fields.ManyToManyRelation["BotXChat"] - created_chats: fields.ReverseRelation["BotXChat"] - administered_chats: fields.ManyToManyRelation["BotXChat"] - - class Meta: - unique_together = (("ad_login", "ad_domain"),) - - def __str__(self) -> str: # noqa: D105 - return f"<{self.__class__.__name__} user_huid: {self.user_huid}>" - - -class BotXChat(models.Model): - """Model for chat. Has members and meta info like chat type, last sync_id, etc.""" - - group_chat_id = fields.UUIDField(pk=True) - chat_type = fields.CharEnumField(ChatTypes, max_length=CHAT_TYPE_MAX_LENGTH) - - last_sync_id = fields.UUIDField(null=True) - - creator: fields.ForeignKeyRelation[BotXUser] = fields.ForeignKeyField( - "botx.BotXUser", - related_name="created_chats", - null=True, - on_delete=fields.SET_NULL, - ) - - cts: fields.ForeignKeyRelation[BotXCTS] = fields.ForeignKeyField( - "botx.BotXCTS", related_name="chats", on_delete=fields.CASCADE - ) - admins: fields.ManyToManyRelation[BotXUser] = fields.ManyToManyField( - "botx.BotXUser", related_name="administered_chats", through="chat_user_admins" - ) - members: fields.ManyToManyRelation[BotXUser] = fields.ManyToManyField( - "botx.BotXUser", related_name="chats", through="chat_user_members" - ) - - def __str__(self) -> str: # noqa: D105 - return "<{0} group_chat_id: {1} chat_type: {2}>".format( - self.__class__.__name__, self.group_chat_id, self.chat_type - ) diff --git a/boxv2/plugins/tortoise/plugin.py b/boxv2/plugins/tortoise/plugin.py deleted file mode 100644 index 8aededf..0000000 --- a/boxv2/plugins/tortoise/plugin.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Postgresql database plugin.""" - -from typing import Any, Dict, List, Optional - -from pydantic import PostgresDsn -from tortoise import Tortoise - -from boxv2.plugin import BasePlugin, BasePluginSettings -from boxv2.plugins.tortoise.dependencies import auto_models_update - - -class Settings(BasePluginSettings): - """Settings for Tortoise ORM plugin.""" - - POSTGRES_DSN: PostgresDsn - EXTRA_MODELS: Optional[Dict[str, List[str]]] = {} # noqa: WPS234 - SQL_DEBUG: bool = False - - -class Plugin(BasePlugin): - """Tortoise ORM plugin.""" - - settings_class = Settings - dependencies = [auto_models_update] - - async def on_startup(self, *args: Any, **kwargs: Any) -> None: - """Startup hook.""" - extra_models = self.settings.EXTRA_MODELS or {} - await Tortoise.init( - db_url=str(self.settings.POSTGRES_DSN), - modules={"botx": ["boxv2.plugins.tortoise.models"], **extra_models}, - ) - - async def on_shutdown(self, *args: Any, **kwargs: Any) -> None: - """Shutdown hook.""" - await Tortoise.close_connections() diff --git a/boxv2/plugins/tortoise/repo.py b/boxv2/plugins/tortoise/repo.py deleted file mode 100644 index cf3894d..0000000 --- a/boxv2/plugins/tortoise/repo.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Repository for botx models.""" -from uuid import UUID - -from botx import ChatTypes - -from boxv2.plugins.tortoise.models import BotXBot, BotXChat, BotXCTS, BotXUser - - -async def get_user_by_huid(huid: UUID) -> BotXUser: - """Get botx user by huid.""" - return await BotXUser.get(user_huid=huid) - - -async def get_cts_of_user_by_huid(huid: UUID) -> BotXCTS: - """Get user's cts by huid.""" - return await BotXCTS.get(users__user_huid=huid) - - -async def get_personal_chat_for_user(user_huid: UUID) -> BotXChat: - """Get personal chat with user by huid.""" - return await BotXChat.get( - members__user_huid=user_huid, - cts__bots__current_bot=True, - chat_type=ChatTypes.chat, - ) - - -async def get_cts_of_current_bot_by_host(host: str) -> BotXCTS: - """Get current bot's cts by host.""" - return await BotXCTS.get(host=host, bots__current_bot=True) - - -async def get_current_bot_by_host(host: str) -> BotXBot: - """Get current bot by host.""" - return await BotXBot.get(cts_id=host, current_bot=True) diff --git a/boxv2/plugins/tortoise/template/cookiecutter.json b/boxv2/plugins/tortoise/template/cookiecutter.json deleted file mode 100644 index 395902c..0000000 --- a/boxv2/plugins/tortoise/template/cookiecutter.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "bot_name": "boxv2-bot", - "bot_display_name": "boxv2 bot", - "bot_short_description": "TODO", - "bot_docs_link": "TODO", - "test_cov": 0, - - "bot_image_name_prefix": "registry.example.com/bots/", - - "bot_name_underscored": "{{ cookiecutter.bot_name|replace('-', '_') }}" -} diff --git a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/alembic.ini b/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/alembic.ini deleted file mode 100644 index ce65b9e..0000000 --- a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/alembic.ini +++ /dev/null @@ -1,38 +0,0 @@ -# https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file - -[alembic] -script_location = ./migrations - -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/env.py b/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/env.py deleted file mode 100644 index b91765e..0000000 --- a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/env.py +++ /dev/null @@ -1,38 +0,0 @@ -import pathlib -import sys -from logging.config import fileConfig - -from alembic import context -from sqlalchemy import engine_from_config, pool - -# init config -sys.path.append(str(pathlib.Path(__file__).resolve().parents[1])) - -from boxv2.application import get_app_settings # isort:skip - -settings = get_app_settings() - -context_config = context.config - -fileConfig(context_config.config_file_name) - -target_metadata = None - -context_config.set_main_option("sqlalchemy.url", str(settings.POSTGRES_DSN)) - - -def run_migrations_online() -> None: - connectable = engine_from_config( - context_config.get_section(context_config.config_ini_section), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) - - with context.begin_transaction(): - context.run_migrations() - - -run_migrations_online() diff --git a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/script.py.mako b/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/script.py.mako deleted file mode 100644 index f20fc2b..0000000 --- a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -Doc: https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py b/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py deleted file mode 100644 index 1b18706..0000000 --- a/boxv2/plugins/tortoise/template/{{cookiecutter.bot_name}}/migrations/versions/c630e92c23e1_botx_main_tables.py +++ /dev/null @@ -1,152 +0,0 @@ -"""botx main tables - -Revision ID: c630e92c23e1 -Revises: -Create Date: 2019-06-28 17:22:48.360104 - -Doc: https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script -""" -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects.postgresql import UUID - -revision = "c630e92c23e1" -down_revision = None -branch_labels = None -depends_on = None - - -def create_botxcts_table() -> None: - op.create_table("botxcts", sa.Column("host", sa.Text, primary_key=True)) - - -def create_botxbot_table() -> None: - op.create_table( - "botxbot", - sa.Column("bot_id", UUID, primary_key=True), - sa.Column("name", sa.Text), - sa.Column("current_bot", sa.Boolean, default=False), - sa.Column( - "cts_id", - sa.Text, - sa.ForeignKey("botxcts.host", ondelete="CASCADE"), - nullable=False, - ), - ) - op.execute( - """ - CREATE OR REPLACE FUNCTION check_only_one_current_bot_on_cts() - RETURNS TRIGGER AS - $$ - BEGIN - IF NEW.current_bot = TRUE AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN - IF NEW.cts_id IN ( - SELECT cts_id - FROM "botxbot" - WHERE current_bot = TRUE AND bot_id != NEW.bot_id - GROUP BY cts_id - ) THEN - RAISE EXCEPTION '% cts already has registered main bot', NEW.cts_id; - END IF; - END IF; - - RETURN NEW; - END; - $$ LANGUAGE 'plpgsql'; - - CREATE TRIGGER check_only_one_current_bot_on_cts_trigger - BEFORE INSERT OR UPDATE - ON "botxbot" - FOR EACH ROW - EXECUTE PROCEDURE check_only_one_current_bot_on_cts(); - """ - ) - - -def create_botxuser_table() -> None: - op.create_table( - "botxuser", - sa.Column("user_huid", UUID, primary_key=True), - sa.Column("username", sa.Text), - sa.Column("ad_login", sa.Text), - sa.Column("ad_domain", sa.Text), - sa.Column("cts_id", sa.Text, sa.ForeignKey("botxcts.host", ondelete="CASCADE")), - ) - - -def create_botxchat_table() -> None: - op.create_table( - "botxchat", - sa.Column("group_chat_id", UUID, primary_key=True), - sa.Column("last_sync_id", UUID), - sa.Column("chat_type", sa.Text, nullable=False), - sa.Column( - "creator_id", UUID, sa.ForeignKey("botxuser.user_huid", ondelete="SET NULL") - ), - sa.Column( - "cts_id", - sa.Text, - sa.ForeignKey("botxcts.host", ondelete="CASCADE"), - nullable=False, - ), - ) - - -def create_chat_admins_table() -> None: - op.create_table( - "chat_user_admins", - sa.Column( - "botxchat_id", - UUID, - sa.ForeignKey("botxchat.group_chat_id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column( - "botxuser_id", - UUID, - sa.ForeignKey("botxuser.user_huid", ondelete="CASCADE"), - nullable=False, - ), - ) - op.create_primary_key( - "pk_chat_user_admins", "chat_user_admins", ["botxchat_id", "botxuser_id"] - ) - - -def create_chat_members_table() -> None: - op.create_table( - "chat_user_members", - sa.Column( - "botxchat_id", - UUID, - sa.ForeignKey("botxchat.group_chat_id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column( - "botxuser_id", - UUID, - sa.ForeignKey("botxuser.user_huid", ondelete="CASCADE"), - nullable=False, - ), - ) - op.create_primary_key( - "pk_chat_user_members", "chat_user_members", ["botxchat_id", "botxuser_id"] - ) - - -def upgrade() -> None: - create_botxcts_table() - create_botxbot_table() - create_botxuser_table() - create_botxchat_table() - create_chat_members_table() - create_chat_admins_table() - - -def downgrade() -> None: - op.drop_table("chat_user_members") - op.drop_table("chat_user_admins") - op.drop_table("botxchat") - op.drop_table("botxuser") - op.drop_table("botxbot") - op.drop_table("botxcts") diff --git a/boxv2/template/{{cookiecutter.bot_name}}/pyproject.toml b/boxv2/template/{{cookiecutter.bot_name}}/pyproject.toml index 68a20c6..06294d7 100644 --- a/boxv2/template/{{cookiecutter.bot_name}}/pyproject.toml +++ b/boxv2/template/{{cookiecutter.bot_name}}/pyproject.toml @@ -7,7 +7,7 @@ description = "" authors = [] [tool.poetry.dependencies] -python = "^3.9" +python = ">=3.9,<3.10" boxv2 = { git = "https://github.com/ExpressApp/boxv2.git", branch = "master"} [tool.poetry.dev-dependencies] diff --git a/poetry.lock b/poetry.lock index 22df127..b271158 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ tests = ["aiofiles (>=0.6.0,<0.7.0)", "molten (>=1.0.1,<2.0.0)", "starlette (>=0 [[package]] name = "certifi" -version = "2021.5.30" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -230,7 +230,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "charset-normalizer" -version = "2.0.6" +version = "2.0.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -241,7 +241,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.1" +version = "8.0.3" description = "Composable command line interface toolkit" category = "main" optional = false @@ -278,7 +278,7 @@ six = ">=1.10" [[package]] name = "coverage" -version = "6.0.1" +version = "6.0.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -297,7 +297,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "databases" -version = "0.4.3" +version = "0.4.2" description = "Async database support for Python." category = "main" optional = false @@ -305,7 +305,7 @@ python-versions = ">=3.6" [package.dependencies] asyncpg = {version = "*", optional = true, markers = "extra == \"postgresql\""} -sqlalchemy = "<1.4" +sqlalchemy = "*" [package.extras] mysql = ["aiomysql"] @@ -323,7 +323,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "eradicate" -version = "1.0" +version = "2.0.0" description = "Removes commented-out code." category = "dev" optional = false @@ -376,7 +376,7 @@ pycodestyle = "*" [[package]] name = "flake8-broken-line" -version = "0.2.1" +version = "0.3.0" description = "Flake8 plugin to forbid backslashes for line breaks" category = "dev" optional = false @@ -387,16 +387,19 @@ flake8 = ">=3.5,<4.0" [[package]] name = "flake8-bugbear" -version = "19.8.0" +version = "21.9.2" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -attrs = "*" +attrs = ">=19.2.0" flake8 = ">=3.0.0" +[package.extras] +dev = ["coverage", "black", "hypothesis", "hypothesmith"] + [[package]] name = "flake8-commas" version = "2.0.0" @@ -410,26 +413,27 @@ flake8 = ">=2,<4.0.0" [[package]] name = "flake8-comprehensions" -version = "3.6.1" +version = "3.7.0" description = "A flake8 plugin to help you write better list/set/dict comprehensions." category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -flake8 = ">=3.0,<3.2.0 || >3.2.0,<4" +flake8 = ">=3.0,<3.2.0 || >3.2.0,<5" [[package]] name = "flake8-debugger" -version = "3.2.1" +version = "4.0.0" description = "ipdb/pdb statement checker plugin for flake8" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -flake8 = ">=1.5" +flake8 = ">=3.0" pycodestyle = "*" +six = "*" [[package]] name = "flake8-docstrings" @@ -445,7 +449,7 @@ pydocstyle = ">=2.1" [[package]] name = "flake8-eradicate" -version = "0.3.0" +version = "1.1.0" description = "Flake8 plugin to find commented out code" category = "dev" optional = false @@ -453,12 +457,12 @@ python-versions = ">=3.6,<4.0" [package.dependencies] attrs = "*" -eradicate = ">=1.0,<2.0" +eradicate = ">=2.0,<3.0" flake8 = ">=3.5,<4.0" [[package]] name = "flake8-isort" -version = "3.0.1" +version = "4.0.0" description = "flake8 plugin that integrates isort ." category = "dev" optional = false @@ -466,11 +470,11 @@ python-versions = "*" [package.dependencies] flake8 = ">=3.2.1,<4" -isort = {version = ">=4.3.5,<5", extras = ["pyproject"]} +isort = ">=4.3.5,<6" testfixtures = ">=6.8.0,<7" [package.extras] -test = ["pytest (>=4.0.2,<6)"] +test = ["pytest (>=4.0.2,<6)", "toml"] [[package]] name = "flake8-polyfill" @@ -485,7 +489,7 @@ flake8 = "*" [[package]] name = "flake8-quotes" -version = "2.1.2" +version = "3.3.0" description = "Flake8 lint for quotes." category = "dev" optional = false @@ -496,19 +500,20 @@ flake8 = "*" [[package]] name = "flake8-rst-docstrings" -version = "0.0.12" +version = "0.2.3" description = "Python docstring reStructuredText (RST) validator" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.3" [package.dependencies] flake8 = ">=3.0.0" -restructuredtext_lint = "*" +pygments = "*" +restructuredtext-lint = "*" [[package]] name = "flake8-string-format" -version = "0.2.3" +version = "0.3.0" description = "string format checker, plugin for flake8" category = "dev" optional = false @@ -540,6 +545,17 @@ python-versions = ">=3.7" gitdb = ">=4.0.1,<5" typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} +[[package]] +name = "greenlet" +version = "1.1.2" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] + [[package]] name = "h11" version = "0.12.0" @@ -591,7 +607,7 @@ http2 = ["h2 (>=3.0.0,<4.0.0)"] [[package]] name = "idna" -version = "3.2" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -613,9 +629,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[package.dependencies] -toml = {version = "*", optional = true, markers = "extra == \"pyproject\""} - [package.extras] pipfile = ["pipreqs", "requirementslib"] pyproject = ["toml"] @@ -720,7 +733,7 @@ python-versions = ">=3.5" [[package]] name = "mypy" -version = "0.800" +version = "0.910" description = "Optional static typing for Python" category = "dev" optional = false @@ -728,11 +741,12 @@ python-versions = ">=3.5" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" +toml = "*" typing-extensions = ">=3.7.4" [package.extras] dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<1.5.0)"] [[package]] name = "mypy-extensions" @@ -771,7 +785,7 @@ python-versions = ">=2.6" [[package]] name = "pep8-naming" -version = "0.9.1" +version = "0.11.1" description = "Check PEP-8 naming conventions, plugin for flake8" category = "dev" optional = false @@ -1045,7 +1059,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.9.30" +version = "2021.10.8" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -1156,23 +1170,35 @@ python-versions = "*" [[package]] name = "sqlalchemy" -version = "1.3.24" +version = "1.4.25" description = "Database Abstraction Library" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} [package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.0)"] +mariadb_connector = ["mariadb (>=1.0.1)"] mssql = ["pyodbc"] mssql_pymssql = ["pymssql"] mssql_pyodbc = ["pyodbc"] -mysql = ["mysqlclient"] -oracle = ["cx-oracle"] -postgresql = ["psycopg2"] -postgresql_pg8000 = ["pg8000 (<1.16.6)"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6)"] postgresql_psycopg2binary = ["psycopg2-binary"] postgresql_psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] [[package]] name = "starlette" @@ -1320,7 +1346,7 @@ python-versions = "*" [[package]] name = "wemake-python-styleguide" -version = "0.14.1" +version = "0.15.3" description = "The strictest and most opinionated python linter ever" category = "dev" optional = false @@ -1332,18 +1358,18 @@ attrs = "*" darglint = ">=1.2,<2.0" flake8 = ">=3.7,<4.0" flake8-bandit = ">=2.1,<3.0" -flake8-broken-line = ">=0.2,<0.3" -flake8-bugbear = ">=19.3,<20.0" +flake8-broken-line = ">=0.3,<0.4" +flake8-bugbear = ">=20.1,<22.0" flake8-commas = ">=2.0,<3.0" -flake8-comprehensions = ">=3.1.0,<4.0.0" -flake8-debugger = ">=3.1,<4.0" -flake8-docstrings = ">=1.3.1,<2.0.0" -flake8-eradicate = ">=0.3,<0.4" -flake8-isort = ">=3.0.1,<4" -flake8-quotes = ">=2.0.1,<3.0.0" -flake8-rst-docstrings = ">=0.0.12,<0.0.13" -flake8-string-format = ">=0.2,<0.3" -pep8-naming = ">=0.9.1,<0.10.0" +flake8-comprehensions = ">=3.1,<4.0" +flake8-debugger = ">=4.0,<5.0" +flake8-docstrings = ">=1.3,<2.0" +flake8-eradicate = ">=1.0,<2.0" +flake8-isort = ">=4.0,<5.0" +flake8-quotes = ">=3.0,<4.0" +flake8-rst-docstrings = ">=0.2.3,<0.3.0" +flake8-string-format = ">=0.3,<0.4" +pep8-naming = ">=0.11,<0.12" pygments = ">=2.4,<3.0" typing_extensions = ">=3.6,<4.0" @@ -1372,7 +1398,7 @@ dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] [metadata] lock-version = "1.1" python-versions = "~3.9" -content-hash = "cda4cc1094bc7e0841386cdaed06cd2a735ca4b81d718517ad13a62fbdb50465" +content-hash = "4eb931a4093b55ba1ff4f68a79144c8f487c18e6f41a02dd89865d21107afd7e" [metadata.files] aioredis = [ @@ -1457,20 +1483,20 @@ botx = [ {file = "botx-0.20.4.tar.gz", hash = "sha256:4d0cd3d38ddd7aba692714939da742c97ee1499ce4174ac546a7ac87e30a8191"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, - {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, + {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, + {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -1481,54 +1507,54 @@ cookiecutter = [ {file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"}, ] coverage = [ - {file = "coverage-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22"}, - {file = "coverage-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a"}, - {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16"}, - {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec"}, - {file = "coverage-6.0.1-cp310-cp310-win32.whl", hash = "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194"}, - {file = "coverage-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f"}, - {file = "coverage-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7"}, - {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43"}, - {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5"}, - {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b"}, - {file = "coverage-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a"}, - {file = "coverage-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb"}, - {file = "coverage-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc"}, - {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e"}, - {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7"}, - {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666"}, - {file = "coverage-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b"}, - {file = "coverage-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724"}, - {file = "coverage-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32"}, - {file = "coverage-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397"}, - {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345"}, - {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827"}, - {file = "coverage-6.0.1-cp38-cp38-win32.whl", hash = "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee"}, - {file = "coverage-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12"}, - {file = "coverage-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e"}, - {file = "coverage-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793"}, - {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d"}, - {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf"}, - {file = "coverage-6.0.1-cp39-cp39-win32.whl", hash = "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad"}, - {file = "coverage-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815"}, - {file = "coverage-6.0.1-pp36-none-any.whl", hash = "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5"}, - {file = "coverage-6.0.1-pp37-none-any.whl", hash = "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26"}, - {file = "coverage-6.0.1.tar.gz", hash = "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854"}, + {file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"}, + {file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"}, + {file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"}, + {file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"}, + {file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"}, + {file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"}, + {file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"}, + {file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"}, + {file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"}, + {file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"}, + {file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"}, + {file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"}, + {file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"}, + {file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"}, + {file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"}, + {file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"}, + {file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"}, + {file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"}, ] darglint = [ {file = "darglint-1.8.0-py3-none-any.whl", hash = "sha256:ac6797bcc918cd8d8f14c168a4a364f54e1aeb4ced59db58e7e4c6dfec2fe15c"}, {file = "darglint-1.8.0.tar.gz", hash = "sha256:aa605ef47817a6d14797d32b390466edab621768ea4ca5cc0f3c54f6d8dcaec8"}, ] databases = [ - {file = "databases-0.4.3-py3-none-any.whl", hash = "sha256:f82b02c28fdddf7ffe7ee1945f5abef44d687ba97b9a1c81492c7f035d4c90e6"}, - {file = "databases-0.4.3.tar.gz", hash = "sha256:1521db7f6d3c581ff81b3552e130b27a13aefea2a57295e65738081831137afc"}, + {file = "databases-0.4.2-py3-none-any.whl", hash = "sha256:44788f6fd344f27baa2be9b42c3926222932000a3ba7dce5a305167a2cb1f6bb"}, + {file = "databases-0.4.2.tar.gz", hash = "sha256:2ca1b72f0209744e25fababc44c183e5fb6f7dd7af4274379f2ec6f14c00432a"}, ] docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] eradicate = [ - {file = "eradicate-1.0.tar.gz", hash = "sha256:4ffda82aae6fd49dfffa777a857cb758d77502a1f2e0f54c9ac5155a39d2d01a"}, + {file = "eradicate-2.0.0.tar.gz", hash = "sha256:27434596f2c5314cc9b31410c93d8f7e8885747399773cd088d3adea647a60c8"}, ] fastapi = [ {file = "fastapi-0.68.2-py3-none-any.whl", hash = "sha256:36bcdd3dbea87c586061005e4a40b9bd0145afd766655b4e0ec1d8870b32555c"}, @@ -1542,49 +1568,51 @@ flake8-bandit = [ {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"}, ] flake8-broken-line = [ - {file = "flake8-broken-line-0.2.1.tar.gz", hash = "sha256:414477070231a5aa05468d48db2742a594b53fbc1ecba28044646706a11fb861"}, - {file = "flake8_broken_line-0.2.1-py3-none-any.whl", hash = "sha256:75858359e3ccd4f1d92a9e7582aa5c9e4485cbc920dd05954703900cf907667e"}, + {file = "flake8-broken-line-0.3.0.tar.gz", hash = "sha256:f74e052833324a9e5f0055032f7ccc54b23faabafe5a26241c2f977e70b10b50"}, + {file = "flake8_broken_line-0.3.0-py3-none-any.whl", hash = "sha256:611f79c7f27118e7e5d3dc098ef7681c40aeadf23783700c5dbee840d2baf3af"}, ] flake8-bugbear = [ - {file = "flake8-bugbear-19.8.0.tar.gz", hash = "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571"}, - {file = "flake8_bugbear-19.8.0-py35.py36.py37-none-any.whl", hash = "sha256:ded4d282778969b5ab5530ceba7aa1a9f1b86fa7618fc96a19a1d512331640f8"}, + {file = "flake8-bugbear-21.9.2.tar.gz", hash = "sha256:db9a09893a6c649a197f5350755100bb1dd84f110e60cf532fdfa07e41808ab2"}, + {file = "flake8_bugbear-21.9.2-py36.py37.py38-none-any.whl", hash = "sha256:4f7eaa6f05b7d7ea4cbbde93f7bcdc5438e79320fa1ec420d860c181af38b769"}, ] flake8-commas = [ {file = "flake8-commas-2.0.0.tar.gz", hash = "sha256:d3005899466f51380387df7151fb59afec666a0f4f4a2c6a8995b975de0f44b7"}, {file = "flake8_commas-2.0.0-py2.py3-none-any.whl", hash = "sha256:ee2141a3495ef9789a3894ed8802d03eff1eaaf98ce6d8653a7c573ef101935e"}, ] flake8-comprehensions = [ - {file = "flake8-comprehensions-3.6.1.tar.gz", hash = "sha256:4888de89248b7f7535159189ff693c77f8354f6d37a02619fa28c9921a913aa0"}, - {file = "flake8_comprehensions-3.6.1-py3-none-any.whl", hash = "sha256:e9a010b99aa90c05790d45281ad9953df44a4a08a1a8f6cd41f98b4fc6a268a0"}, + {file = "flake8-comprehensions-3.7.0.tar.gz", hash = "sha256:6b3218b2dde8ac5959c6476cde8f41a79e823c22feb656be2710cd2a3232cef9"}, + {file = "flake8_comprehensions-3.7.0-py3-none-any.whl", hash = "sha256:a5d7aea6315bbbd6fbcb2b4e80bff6a54d1600155e26236e555d0c6fe1d62522"}, ] flake8-debugger = [ - {file = "flake8-debugger-3.2.1.tar.gz", hash = "sha256:712d7c1ff69ddf3f0130e94cc88c2519e720760bce45e8c330bfdcb61ab4090d"}, + {file = "flake8-debugger-4.0.0.tar.gz", hash = "sha256:e43dc777f7db1481db473210101ec2df2bd39a45b149d7218a618e954177eda6"}, + {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"}, ] flake8-docstrings = [ {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, ] flake8-eradicate = [ - {file = "flake8-eradicate-0.3.0.tar.gz", hash = "sha256:d0b3d283d85079917acbfe39b9d637385cd82cba3ae3d76c1278c07ddcf0d9b9"}, - {file = "flake8_eradicate-0.3.0-py3-none-any.whl", hash = "sha256:e8b32b32300bfb407fe7ef74667c8d2d3a6a81bdf6f09c14a7bcc82b7b870f8b"}, + {file = "flake8-eradicate-1.1.0.tar.gz", hash = "sha256:f5917d6dbca352efcd10c15fdab9c55c48f0f26f6a8d47898b25d39101f170a8"}, + {file = "flake8_eradicate-1.1.0-py3-none-any.whl", hash = "sha256:d8e39b684a37c257a53cda817d86e2d96c9ba3450ddc292742623a5dfee04d9e"}, ] flake8-isort = [ - {file = "flake8-isort-3.0.1.tar.gz", hash = "sha256:5d976da513cc390232ad5a9bb54aee8a092466a15f442d91dfc525834bee727a"}, - {file = "flake8_isort-3.0.1-py2.py3-none-any.whl", hash = "sha256:df1dd6dd73f6a8b128c9c783356627231783cccc82c13c6dc343d1a5a491699b"}, + {file = "flake8-isort-4.0.0.tar.gz", hash = "sha256:2b91300f4f1926b396c2c90185844eb1a3d5ec39ea6138832d119da0a208f4d9"}, + {file = "flake8_isort-4.0.0-py2.py3-none-any.whl", hash = "sha256:729cd6ef9ba3659512dee337687c05d79c78e1215fdf921ed67e5fe46cce2f3c"}, ] flake8-polyfill = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] flake8-quotes = [ - {file = "flake8-quotes-2.1.2.tar.gz", hash = "sha256:c844c9592940c8926c60f00bc620808912ff2acd34923ab5338f3a5ca618a331"}, + {file = "flake8-quotes-3.3.0.tar.gz", hash = "sha256:f1dd87830ed77ff2ce47fc0ee0fd87ae20e8f045355354ffbf4dcaa18d528217"}, ] flake8-rst-docstrings = [ - {file = "flake8-rst-docstrings-0.0.12.tar.gz", hash = "sha256:01d38327801781b26c3dfeb71ae37e5a02c5ca1b774a686f63feab8824ca6f9c"}, + {file = "flake8-rst-docstrings-0.2.3.tar.gz", hash = "sha256:3045794e1c8467fba33aaea5c246b8369efc9c44ef8b0b20199bb6df7a4bd47b"}, + {file = "flake8_rst_docstrings-0.2.3-py3-none-any.whl", hash = "sha256:565bbb391d7e4d0042924102221e9857ad72929cdd305b26501736ec22c1451a"}, ] flake8-string-format = [ - {file = "flake8-string-format-0.2.3.tar.gz", hash = "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1"}, - {file = "flake8_string_format-0.2.3-py2.py3-none-any.whl", hash = "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2"}, + {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"}, + {file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"}, ] gitdb = [ {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, @@ -1594,6 +1622,58 @@ gitpython = [ {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"}, {file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"}, ] +greenlet = [ + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, +] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, @@ -1650,8 +1730,8 @@ httpx = [ {file = "httpx-0.16.1.tar.gz", hash = "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537"}, ] idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] iso8601 = [ {file = "iso8601-0.1.16-py2.py3-none-any.whl", hash = "sha256:906714829fedbc89955d52806c903f2332e3948ed94e31e85037f9e0226b8376"}, @@ -1726,28 +1806,29 @@ more-itertools = [ {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"}, ] mypy = [ - {file = "mypy-0.800-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:e1c84c65ff6d69fb42958ece5b1255394714e0aac4df5ffe151bc4fe19c7600a"}, - {file = "mypy-0.800-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:947126195bfe4709c360e89b40114c6746ae248f04d379dca6f6ab677aa07641"}, - {file = "mypy-0.800-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:b95068a3ce3b50332c40e31a955653be245666a4bc7819d3c8898aa9fb9ea496"}, - {file = "mypy-0.800-cp35-cp35m-win_amd64.whl", hash = "sha256:ca7ad5aed210841f1e77f5f2f7d725b62c78fa77519312042c719ed2ab937876"}, - {file = "mypy-0.800-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e32b7b282c4ed4e378bba8b8dfa08e1cfa6f6574067ef22f86bee5b1039de0c9"}, - {file = "mypy-0.800-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e497a544391f733eca922fdcb326d19e894789cd4ff61d48b4b195776476c5cf"}, - {file = "mypy-0.800-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:5615785d3e2f4f03ab7697983d82c4b98af5c321614f51b8f1034eb9ebe48363"}, - {file = "mypy-0.800-cp36-cp36m-win_amd64.whl", hash = "sha256:2b216eacca0ec0ee124af9429bfd858d5619a0725ee5f88057e6e076f9eb1a7b"}, - {file = "mypy-0.800-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e3b8432f8df19e3c11235c4563a7250666dc9aa7cdda58d21b4177b20256ca9f"}, - {file = "mypy-0.800-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d16c54b0dffb861dc6318a8730952265876d90c5101085a4bc56913e8521ba19"}, - {file = "mypy-0.800-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d2fc8beb99cd88f2d7e20d69131353053fbecea17904ee6f0348759302c52fa"}, - {file = "mypy-0.800-cp37-cp37m-win_amd64.whl", hash = "sha256:aa9d4901f3ee1a986a3a79fe079ffbf7f999478c281376f48faa31daaa814e86"}, - {file = "mypy-0.800-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:319ee5c248a7c3f94477f92a729b7ab06bf8a6d04447ef3aa8c9ba2aa47c6dcf"}, - {file = "mypy-0.800-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:74f5aa50d0866bc6fb8e213441c41e466c86678c800700b87b012ed11c0a13e0"}, - {file = "mypy-0.800-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a301da58d566aca05f8f449403c710c50a9860782148332322decf73a603280b"}, - {file = "mypy-0.800-cp38-cp38-win_amd64.whl", hash = "sha256:b9150db14a48a8fa114189bfe49baccdff89da8c6639c2717750c7ae62316738"}, - {file = "mypy-0.800-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5fdf935a46aa20aa937f2478480ebf4be9186e98e49cc3843af9a5795a49a25"}, - {file = "mypy-0.800-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6f8425fecd2ba6007e526209bb985ce7f49ed0d2ac1cc1a44f243380a06a84fb"}, - {file = "mypy-0.800-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5ff616787122774f510caeb7b980542a7cc2222be3f00837a304ea85cd56e488"}, - {file = "mypy-0.800-cp39-cp39-win_amd64.whl", hash = "sha256:90b6f46dc2181d74f80617deca611925d7e63007cf416397358aa42efb593e07"}, - {file = "mypy-0.800-py3-none-any.whl", hash = "sha256:3e0c159a7853e3521e3f582adb1f3eac66d0b0639d434278e2867af3a8c62653"}, - {file = "mypy-0.800.tar.gz", hash = "sha256:e0202e37756ed09daf4b0ba64ad2c245d357659e014c3f51d8cd0681ba66940a"}, + {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, + {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, + {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, + {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, + {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, + {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, + {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, + {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, + {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, + {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, + {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, + {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, + {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, + {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, + {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, + {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, + {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, + {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, + {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, + {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, + {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, + {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, + {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1766,8 +1847,8 @@ pbr = [ {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"}, ] pep8-naming = [ - {file = "pep8-naming-0.9.1.tar.gz", hash = "sha256:a33d38177056321a167decd6ba70b890856ba5025f0a8eca6a3eda607da93caf"}, - {file = "pep8_naming-0.9.1-py2.py3-none-any.whl", hash = "sha256:45f330db8fcfb0fba57458c77385e288e7a3be1d01e8ea4268263ef677ceea5f"}, + {file = "pep8-naming-0.11.1.tar.gz", hash = "sha256:a1dd47dd243adfe8a83616e27cf03164960b507530f155db94e10b36a6cd6724"}, + {file = "pep8_naming-0.11.1-py2.py3-none-any.whl", hash = "sha256:f43bfe3eea7e0d73e8b5d07d6407ab47f2476ccaeff6937c84275cd30b016738"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -1927,47 +2008,47 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.9.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66696c8336a1b5d1182464f3af3427cc760118f26d0b09a2ddc16a976a4d2637"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d87459ad3ab40cd8493774f8a454b2e490d8e729e7e402a0625867a983e4e02"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf6a1e023caf5e9a982f5377414e1aeac55198831b852835732cfd0a0ca5ff"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:255791523f80ea8e48e79af7120b4697ef3b74f6886995dcdb08c41f8e516be0"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e502f8d4e5ef714bcc2c94d499684890c94239526d61fdf1096547db91ca6aa6"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4907fb0f9b9309a5bded72343e675a252c2589a41871874feace9a05a540241e"}, - {file = "regex-2021.9.30-cp310-cp310-win32.whl", hash = "sha256:3be40f720af170a6b20ddd2ad7904c58b13d2b56f6734ee5d09bbdeed2fa4816"}, - {file = "regex-2021.9.30-cp310-cp310-win_amd64.whl", hash = "sha256:c2b180ed30856dfa70cfe927b0fd38e6b68198a03039abdbeb1f2029758d87e7"}, - {file = "regex-2021.9.30-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6f2d2f93001801296fe3ca86515eb04915472b5380d4d8752f09f25f0b9b0ed"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fa7ba9ab2eba7284e0d7d94f61df7af86015b0398e123331362270d71fab0b9"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28040e89a04b60d579c69095c509a4f6a1a5379cd865258e3a186b7105de72c6"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f588209d3e4797882cd238195c175290dbc501973b10a581086b5c6bcd095ffb"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42952d325439ef223e4e9db7ee6d9087b5c68c5c15b1f9de68e990837682fc7b"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cae4099031d80703954c39680323dabd87a69b21262303160776aa0e55970ca0"}, - {file = "regex-2021.9.30-cp36-cp36m-win32.whl", hash = "sha256:0de8ad66b08c3e673b61981b9e3626f8784d5564f8c3928e2ad408c0eb5ac38c"}, - {file = "regex-2021.9.30-cp36-cp36m-win_amd64.whl", hash = "sha256:b345ecde37c86dd7084c62954468a4a655fd2d24fd9b237949dd07a4d0dd6f4c"}, - {file = "regex-2021.9.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6f08187136f11e430638c2c66e1db091105d7c2e9902489f0dbc69b44c222b4"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55442650f541d195a535ccec33078c78a9521973fb960923da7515e9ed78fa6"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87e9c489aa98f50f367fb26cc9c8908d668e9228d327644d7aa568d47e456f47"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2cb7d4909ed16ed35729d38af585673f1f0833e73dfdf0c18e5be0061107b99"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0861e7f6325e821d5c40514c551fd538b292f8cc3960086e73491b9c5d8291d"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:81fdc90f999b2147fc62e303440c424c47e5573a9b615ed5d43a5b832efcca9e"}, - {file = "regex-2021.9.30-cp37-cp37m-win32.whl", hash = "sha256:8c1ad61fa024195136a6b7b89538030bd00df15f90ac177ca278df9b2386c96f"}, - {file = "regex-2021.9.30-cp37-cp37m-win_amd64.whl", hash = "sha256:e3770781353a4886b68ef10cec31c1f61e8e3a0be5f213c2bb15a86efd999bc4"}, - {file = "regex-2021.9.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c065d95a514a06b92a5026766d72ac91bfabf581adb5b29bc5c91d4b3ee9b83"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9925985be05d54b3d25fd6c1ea8e50ff1f7c2744c75bdc4d3b45c790afa2bcb3"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470f2c882f2672d8eeda8ab27992aec277c067d280b52541357e1acd7e606dae"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad0517df22a97f1da20d8f1c8cb71a5d1997fa383326b81f9cf22c9dadfbdf34"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e30838df7bfd20db6466fd309d9b580d32855f8e2c2e6d74cf9da27dcd9b63"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b34d2335d6aedec7dcadd3f8283b9682fadad8b9b008da8788d2fce76125ebe"}, - {file = "regex-2021.9.30-cp38-cp38-win32.whl", hash = "sha256:e07049cece3462c626d650e8bf42ddbca3abf4aa08155002c28cb6d9a5a281e2"}, - {file = "regex-2021.9.30-cp38-cp38-win_amd64.whl", hash = "sha256:37868075eda024470bd0feab872c692ac4ee29db1e14baec103257bf6cc64346"}, - {file = "regex-2021.9.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d331f238a7accfbbe1c4cd1ba610d4c087b206353539331e32a8f05345c74aec"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6348a7ab2a502cbdd0b7fd0496d614007489adb7361956b38044d1d588e66e04"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b1cca6c23f19bee8dc40228d9c314d86d1e51996b86f924aca302fc8f8bf9"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f1125bc5172ab3a049bc6f4b9c0aae95a2a2001a77e6d6e4239fa3653e202b5"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:638e98d069b14113e8afba6a54d1ca123f712c0d105e67c1f9211b2a825ef926"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a0b0db6b49da7fa37ca8eddf9f40a8dbc599bad43e64f452284f37b6c34d91c"}, - {file = "regex-2021.9.30-cp39-cp39-win32.whl", hash = "sha256:9910869c472e5a6728680ca357b5846546cbbd2ab3ad5bef986ef0bc438d0aa6"}, - {file = "regex-2021.9.30-cp39-cp39-win_amd64.whl", hash = "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed"}, - {file = "regex-2021.9.30.tar.gz", hash = "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7"}, + {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, + {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, + {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, + {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, + {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, + {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, + {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, + {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, + {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, + {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, + {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, + {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, + {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, + {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, + {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, + {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, ] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, @@ -2001,40 +2082,36 @@ snowballstemmer = [ {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, ] sqlalchemy = [ - {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, - {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, - {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, - {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a36ea43919e51b0de0c0bc52bcfdad7683f6ea9fb81b340cdabb9df0e045e0f7"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:75cd5d48389a7635393ff5a9214b90695c06b3d74912109c3b00ce7392b69c6c"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-win32.whl", hash = "sha256:16ef07e102d2d4f974ba9b0d4ac46345a411ad20ad988b3654d59ff08e553b1c"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-win_amd64.whl", hash = "sha256:a79abdb404d9256afb8aeaa0d3a4bc7d3b6d8b66103d8b0f2f91febd3909976e"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ad59e2e16578b6c1a2873e4888134112365605b08a6067dd91e899e026efa1c"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a505ecc0642f52e7c65afb02cc6181377d833b7df0994ecde15943b18d0fa89c"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a28fe28c359835f3be20c89efd517b35e8f97dbb2ca09c6cf0d9ac07f62d7ef6"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41a916d815a3a23cb7fff8d11ad0c9b93369ac074e91e428075e088fe57d5358"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:842c49dd584aedd75c2ee05f6c950730c3ffcddd21c5824ed0f820808387e1e3"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-win32.whl", hash = "sha256:6b602e3351f59f3999e9fb8b87e5b95cb2faab6a6ecdb482382ac6fdfbee5266"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-win_amd64.whl", hash = "sha256:6400b22e4e41cc27623a9a75630b7719579cd9a3a2027bcf16ad5aaa9a7806c0"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:dd4ed12a775f2cde4519f4267d3601990a97d8ecde5c944ab06bfd6e8e8ea177"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b7778a205f956755e05721eebf9f11a6ac18b2409bff5db53ce5fe7ede79831"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08d9396a2a38e672133266b31ed39b2b1f2b5ec712b5bff5e08033970563316a"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e93978993a2ad0af43f132be3ea8805f56b2f2cd223403ec28d3e7d5c6d39ed1"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-win32.whl", hash = "sha256:0566a6e90951590c0307c75f9176597c88ef4be2724958ca1d28e8ae05ec8822"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-win_amd64.whl", hash = "sha256:0b08a53e40b34205acfeb5328b832f44437956d673a6c09fce55c66ab0e54916"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:33a1e86abad782e90976de36150d910748b58e02cd7d35680d441f9a76806c18"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ed67aae8cde4d32aacbdba4f7f38183d14443b714498eada5e5a7a37769c0b7"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ebd69365717becaa1b618220a3df97f7c08aa68e759491de516d1c3667bba54"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0cd2d5c7ea96d3230cb20acac3d89de3b593339c1447b4d64bfcf4eac1110"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-win32.whl", hash = "sha256:c211e8ec81522ce87b0b39f0cf0712c998d4305a030459a0e115a2b3dc71598f"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-win_amd64.whl", hash = "sha256:9a1df8c93a0dd9cef0839917f0c6c49f46c75810cf8852be49884da4a7de3c59"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:1b38db2417b9f7005d6ceba7ce2a526bf10e3f6f635c0f163e6ed6a42b5b62b2"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e37621b37c73b034997b5116678862f38ee70e5a054821c7b19d0e55df270dec"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:91cd87d1de0111eaca11ccc3d31af441c753fa2bc22df72e5009cfb0a1af5b03"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90fe429285b171bcc252e21515703bdc2a4721008d1f13aa5b7150336f8a8493"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-win32.whl", hash = "sha256:6003771ea597346ab1e97f2f58405c6cacbf6a308af3d28a9201a643c0ac7bb3"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-win_amd64.whl", hash = "sha256:9ebe49c3960aa2219292ea2e5df6acdc425fc828f2f3d50b4cfae1692bcb5f02"}, + {file = "SQLAlchemy-1.4.25.tar.gz", hash = "sha256:1adf3d25e2e33afbcd48cfad8076f9378793be43e7fec3e4334306cac6bec138"}, ] starlette = [ {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, @@ -2118,8 +2195,8 @@ wcwidth = [ {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] wemake-python-styleguide = [ - {file = "wemake-python-styleguide-0.14.1.tar.gz", hash = "sha256:e13dc580fa56b7b548de8da170bccb8ddff2d4ab026ca987db8a9893bf8a7b5b"}, - {file = "wemake_python_styleguide-0.14.1-py3-none-any.whl", hash = "sha256:73a501e0547275287a2b926515c000cc25026a8bceb9dcc1bf73ef85a223a3c6"}, + {file = "wemake-python-styleguide-0.15.3.tar.gz", hash = "sha256:8b89aedabae67b7b915908ed06c178b702068137c0d8afe1fb59cdc829cd2143"}, + {file = "wemake_python_styleguide-0.15.3-py3-none-any.whl", hash = "sha256:a382f6c9ec87d56daa08a11e47cab019c99b384f1393b32564ebc74c6da80441"}, ] werkzeug = [ {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, diff --git a/pyproject.toml b/pyproject.toml index 4581a18..cb128f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,13 +19,14 @@ alembic = "^1.5.4" psycopg2-binary = "^2.8.6" # for alembic loguru = "^0.5.3" prometheus-client = "^0.9.0" +SQLAlchemy = "^1.4.25" [tool.poetry.dev-dependencies] black = "~20.8b1" isort = "~4.3.21" autoflake = "~1.4" -mypy = "~0.800" -wemake-python-styleguide = "~0.14" +mypy = "~0.910" +wemake-python-styleguide = "~0.15.3" pytest = "~5.4.1" pytest-cov = "~2.8.1" pytest-asyncio = "~0.12.0" diff --git a/setup.cfg b/setup.cfg index b25a90d..2188c70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ plugins = pydantic.mypy disallow_untyped_defs = True strict_optional = True follow_imports = skip +exclude = template\/.* [pydantic-mypy] init_forbid_extra = True @@ -77,12 +78,13 @@ max-arguments = 10 nested-classes-whitelist = Config, Meta, Params no-accept-encodings = True inline-quotes = " -exclude = boxv2/cookiecutter, boxv2/tests/fixtures.py +exclude = boxv2/template, boxv2/plugins/sqlalchemy/template, boxv2/tests/fixtures.py per-file-ignores = boxv2/settings.py:WPS115 boxv2/plugins/*/plugin.py:WPS115 - boxv2/plugins/plugin.py:WPS609, + boxv2/plugin.py:WPS609 */__init__.py:D104,WPS412,WPS300,WPS410 + # See https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html ignore = # black handles whitespace before ':'. From d035beec305e477c139ae6ae427fa0c486f5ece5 Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Thu, 14 Oct 2021 13:56:42 +0300 Subject: [PATCH 3/7] feat: sqlalchemy + healthcheck --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb128f7..576abe2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ fastapi = "^0.68.2" uvicorn = "^0.15.0" click = "^8.0.1" sentry-sdk = "^0.20.2" -tortoise-orm = "^0.16.21" databases = { version = "~0.4.1", extras = ["postgresql"] } aioredis = "^1.3.1" alembic = "^1.5.4" From 2140b55272308c1bbe3d087b825a007fc5e17fdd Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Fri, 15 Oct 2021 10:34:09 +0300 Subject: [PATCH 4/7] fix: remove healthcheck_supported --- boxv2/plugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/boxv2/plugin.py b/boxv2/plugin.py index 8cbf024..c712a75 100644 --- a/boxv2/plugin.py +++ b/boxv2/plugin.py @@ -23,8 +23,7 @@ class Config: # noqa: WPS431 class HealtCheckData(BaseModel): """Data returning by plugin's healthcheck method.""" - healthcheck_supported: bool = True - healthy: Optional[bool] # True - ok, False - error + healthy: Optional[bool] = None # True - ok, False - error, None - not supported information: dict[str, Any] = {} @@ -52,7 +51,7 @@ async def on_shutdown(self, *args: Any, **kwargs: Any) -> None: async def healthcheck(self) -> HealtCheckData: """Runtime check for plugin functioning.""" - return HealtCheckData(healthcheck_supported=False) + return HealtCheckData() @classmethod def get_template_path(cls) -> Path: From f589520685340a911f0a0d072d63279ae739529a Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Fri, 15 Oct 2021 10:52:41 +0300 Subject: [PATCH 5/7] fix: review changes --- boxv2/application.py | 13 +++-- boxv2/plugin.py | 4 ++ boxv2/plugins/logger/plugin.py | 6 +- boxv2/plugins/sqlalchemy/plugin.py | 4 +- .../app/db/models.py | 57 +------------------ 5 files changed, 21 insertions(+), 63 deletions(-) diff --git a/boxv2/application.py b/boxv2/application.py index 206cf58..461486d 100644 --- a/boxv2/application.py +++ b/boxv2/application.py @@ -1,11 +1,12 @@ """Functions to make bot application.""" +import itertools import os -from typing import Any, Callable, List, Optional +from typing import Callable, Optional from botx import Bot, Collector from fastapi import FastAPI -import itertools + from boxv2.endpoints import get_router from boxv2.plugin import get_plugin_by_path from boxv2.settings import BaseAppSettings @@ -19,9 +20,11 @@ def get_application(settings: Optional[BaseAppSettings] = None) -> FastAPI: application = FastAPI(title=settings.NAME) plugin_classes = [get_plugin_by_path(plugin) for plugin in settings.PLUGINS] - dependencies = list(itertools.chain.from_iterable( - [plugin_class.dependencies for plugin_class in plugin_classes] - )) + dependencies = list( + itertools.chain.from_iterable( + [plugin_class.dependencies for plugin_class in plugin_classes] + ) + ) bot = Bot( # type: ignore bot_accounts=settings.BOT_CREDENTIALS, diff --git a/boxv2/plugin.py b/boxv2/plugin.py index c712a75..09c28bc 100644 --- a/boxv2/plugin.py +++ b/boxv2/plugin.py @@ -64,6 +64,10 @@ def get_name(cls) -> str: """Get plugin's name.""" module = inspect.getmodule(cls) if module is not None: + # + # boxv2.plugins.sqlalchemy.plugin.SQLAlchemyPlugin + # ^ + # this part is using as name return module.__name__.split(".")[-2] return cls.__name__.lower() diff --git a/boxv2/plugins/logger/plugin.py b/boxv2/plugins/logger/plugin.py index 5d9cdfc..f2c54ac 100644 --- a/boxv2/plugins/logger/plugin.py +++ b/boxv2/plugins/logger/plugin.py @@ -2,12 +2,14 @@ import json import logging import sys +from copy import deepcopy from pprint import pformat -from typing import TYPE_CHECKING, List, Any +from typing import TYPE_CHECKING, Any + from loguru import logger from loguru._defaults import LOGURU_FORMAT # noqa: WPS436 + from boxv2.plugin import BasePlugin, BasePluginSettings -from copy import deepcopy if TYPE_CHECKING: from loguru import Record # noqa: WPS433 # pragma: no cover diff --git a/boxv2/plugins/sqlalchemy/plugin.py b/boxv2/plugins/sqlalchemy/plugin.py index 64c4767..b04c259 100644 --- a/boxv2/plugins/sqlalchemy/plugin.py +++ b/boxv2/plugins/sqlalchemy/plugin.py @@ -10,14 +10,14 @@ class Settings(BasePluginSettings): - """Settings for Tortoise ORM plugin.""" + """Settings for SQLAlchemy ORM plugin.""" POSTGRES_DSN: str SQL_DEBUG: bool = False class SQLAlchemyPlugin(BasePlugin): - """Tortoise ORM plugin.""" + """SQLAlchemy ORM plugin.""" settings_class = Settings _session: Optional[AsyncSession] diff --git a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py index c4e8a26..e0127a4 100644 --- a/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py +++ b/boxv2/plugins/sqlalchemy/template/{{cookiecutter.bot_name}}/app/db/models.py @@ -1,69 +1,18 @@ """Database models declarations.""" -from typing import Any, Generic, List, TypeVar - -from sqlalchemy import Column, Integer, String, insert, update as _update +from sqlalchemy import Column, Integer, String from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.future import select - -T = TypeVar("T") # noqa: WPS111 +# All models in project must be inherited from this class Base = declarative_base() -# This is an example of asynchronous usage of SQLAlchemy. -# You may rewrite this class as need for your models. -class CRUDMixin(Generic[T]): - """Mixin for CRUD operations for models.""" - - id: int # noqa: WPS125 - - @classmethod - async def create(cls, **kwargs: Any) -> None: - """Create object.""" - query = insert(cls).values(**kwargs) - session = get_session() - async with session.begin(): - await session.execute(query) - - @classmethod - async def update(cls, id: int, **kwargs: Any) -> None: # noqa: WPS125 - """Update object by id.""" - query = ( - _update(cls) - .where(cls.id == id) - .values(**kwargs) - .execution_options(synchronize_session="fetch") - ) - session = get_session() - async with session.begin(): - await session.execute(query) - - @classmethod - async def get(cls, id: int) -> T: # noqa: WPS125 - """Get object by id.""" - query = select(cls).where(cls.id == id) - session = get_session() - async with session.begin(): - rows = await session.execute(query) - return rows.scalars().one() - - @classmethod - async def all(cls) -> List[T]: # noqa: WPS125 - """Get all objects.""" - query = select(cls) - session = get_session() - async with session.begin(): - rows = await session.execute(query) - return rows.scalars().all() - - # This is an example model. You may rewrite it a you need or write any other models. # After it you may run `alembic revision --autogenerate` to generate migrations # and `alembic upgrade head` to apply it on database. # Migrations files will be stored at `app/db/migrations/versions` -class Record(Base, CRUDMixin): +class Record(Base): """Simple database model for example.""" __tablename__ = "record" From b9a288a32695903152a22c5eca8398286effeb62 Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Fri, 15 Oct 2021 16:39:08 +0300 Subject: [PATCH 6/7] tests: remove tortoise tests --- tests/test_plugins/test_tortoise/__init__.py | 0 tests/test_plugins/test_tortoise/conftest.py | 61 ------------------ .../test_tortoise/test_dependencies.py | 64 ------------------- .../test_plugins/test_tortoise/test_models.py | 45 ------------- tests/test_plugins/test_tortoise/test_repo.py | 63 ------------------ 5 files changed, 233 deletions(-) delete mode 100644 tests/test_plugins/test_tortoise/__init__.py delete mode 100644 tests/test_plugins/test_tortoise/conftest.py delete mode 100644 tests/test_plugins/test_tortoise/test_dependencies.py delete mode 100644 tests/test_plugins/test_tortoise/test_models.py delete mode 100644 tests/test_plugins/test_tortoise/test_repo.py diff --git a/tests/test_plugins/test_tortoise/__init__.py b/tests/test_plugins/test_tortoise/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_plugins/test_tortoise/conftest.py b/tests/test_plugins/test_tortoise/conftest.py deleted file mode 100644 index 4bb44e6..0000000 --- a/tests/test_plugins/test_tortoise/conftest.py +++ /dev/null @@ -1,61 +0,0 @@ -from os import environ - -import pytest -from asyncpg import connect -from fastapi import FastAPI -from tortoise import Tortoise - -from boxv2 import get_application -from boxv2.settings import BaseAppSettings -from boxv2.tests.fixtures import ( - bot, - builder, - chat_created_data, - credentials, - environment, - group_chat_id, - http_client, -) - -__all__ = [ - "builder", - "bot", - "chat_created_data", - "group_chat_id", - "credentials", - "environment", - "http_client", -] - -POSTGRES_DSN = environ["TEST_POSTGRES_DSN"] - - -async def clean_db(): - conn = await connect(dsn=POSTGRES_DSN) - await conn.fetch("DROP SCHEMA IF EXISTS public CASCADE;") - await conn.fetch("CREATE SCHEMA public;") - await conn.close() - - -@pytest.fixture(scope="function") -async def db_schema(): - await clean_db() - await Tortoise.init( - db_url=POSTGRES_DSN, - modules={"botx": ["boxv2.plugins.tortoise.models"]}, - ) - await Tortoise.generate_schemas() - yield - await Tortoise.close_connections() - await clean_db() - - -class AppSettings(BaseAppSettings): - # do not include plugin here as DB initialization replaced with db_schema() - # fixture to generate schemas - PLUGINS = [] - - -@pytest.fixture -def app(db_schema) -> FastAPI: - return get_application(AppSettings()) diff --git a/tests/test_plugins/test_tortoise/test_dependencies.py b/tests/test_plugins/test_tortoise/test_dependencies.py deleted file mode 100644 index 01e35b3..0000000 --- a/tests/test_plugins/test_tortoise/test_dependencies.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -from botx import Bot, ChatCreatedEvent, Message, SystemEvents -from botx.testing import MessageBuilder - -from boxv2.plugins.tortoise.dependencies import auto_models_update as amu_dep -from boxv2.plugins.tortoise.models import BotXBot, BotXChat, BotXCTS, BotXUser - -auto_models_update = amu_dep.dependency - - -@pytest.mark.usefixtures("db_schema") -@pytest.mark.asyncio -class TestAutoModelsUpdate: - async def test_with_user_in_personal_chat(self, builder: MessageBuilder, bot: Bot): - builder.user.is_admin = True - builder.user.is_creator = True - msg = Message.from_dict(builder.message.dict(), bot) - - await auto_models_update(msg) - - user: BotXUser = await BotXUser.get(user_huid=msg.user_huid) - await BotXChat.get(group_chat_id=msg.group_chat_id) - await BotXBot.get(bot_id=msg.bot_id) - await BotXCTS.get(host=msg.host) - - assert await user.administered_chats.filter( - group_chat_id=msg.group_chat_id - ).first() - assert await user.created_chats.filter(group_chat_id=msg.group_chat_id).first() - assert await user.chats.filter(group_chat_id=msg.group_chat_id).first() - - async def test_user_that_is_no_admin(self, builder: MessageBuilder, bot: Bot): - builder.user.is_admin = True - msg = Message.from_dict(builder.message.dict(), bot) - - await auto_models_update(msg) - - user: BotXUser = await BotXUser.get(user_huid=msg.user_huid) - - assert await user.administered_chats.filter( - group_chat_id=msg.group_chat_id - ).first() - - msg.user.is_admin = False - await auto_models_update(msg) - - assert not await user.administered_chats.filter( - group_chat_id=msg.group_chat_id - ).first() - - async def test_chat_created( - self, builder: MessageBuilder, bot: Bot, chat_created_data: ChatCreatedEvent - ): - builder.command_data = chat_created_data.dict() - builder.body = SystemEvents.chat_created.value - builder.user.user_huid = None - builder.user.ad_login = None - builder.user.ad_domain = None - builder.user.username = None - - builder.system_command = True - msg = Message.from_dict(builder.message.dict(), bot) - - await auto_models_update(msg) diff --git a/tests/test_plugins/test_tortoise/test_models.py b/tests/test_plugins/test_tortoise/test_models.py deleted file mode 100644 index 4f86297..0000000 --- a/tests/test_plugins/test_tortoise/test_models.py +++ /dev/null @@ -1,45 +0,0 @@ -import uuid - -import pytest -from botx import ChatTypes - -from boxv2.plugins.tortoise.models import BotXBot, BotXChat, BotXCTS, BotXUser - - -@pytest.mark.usefixtures("db_schema") -@pytest.mark.asyncio -class TestStringRepresentation: - async def test_cts(self): - cts = await BotXCTS.create(host="somehost") - - assert str(cts) == "" - - async def test_current_bot(self): - cts = await BotXCTS.create(host="somehost") - - bot_id = uuid.uuid4() - bot = await BotXBot.create(bot_id=bot_id, current_bot=True, cts=cts) - assert str(bot) == f"" - - async def test_simple_bot(self): - cts = await BotXCTS.create(host="somehost") - - bot_id = uuid.uuid4() - bot = await BotXBot.create(bot_id=bot_id, cts=cts) - assert str(bot) == f"" - - async def test_user(self): - cts = await BotXCTS.create(host="somehost") - - user_huid = uuid.uuid4() - user = await BotXUser.create(user_huid=user_huid, cts=cts) - assert str(user) == f"" - - async def test_chat(self): - cts = await BotXCTS.create(host="somehost") - - group_chat_id = uuid.uuid4() - chat = await BotXChat.create( - group_chat_id=group_chat_id, chat_type=ChatTypes.chat, cts=cts - ) - assert str(chat) == f"" diff --git a/tests/test_plugins/test_tortoise/test_repo.py b/tests/test_plugins/test_tortoise/test_repo.py deleted file mode 100644 index 863675a..0000000 --- a/tests/test_plugins/test_tortoise/test_repo.py +++ /dev/null @@ -1,63 +0,0 @@ -import uuid - -import pytest -from botx import ChatTypes - -from boxv2.plugins.tortoise.models import BotXBot, BotXChat, BotXCTS, BotXUser -from boxv2.plugins.tortoise.repo import ( - get_cts_of_current_bot_by_host, - get_cts_of_user_by_huid, - get_current_bot_by_host, - get_personal_chat_for_user, - get_user_by_huid, -) - - -@pytest.mark.usefixtures("db_schema") -@pytest.mark.asyncio -class TestRepo: - async def test_get_user_by_huid(self): - cts = await BotXCTS.create(host="host") - - huid = uuid.uuid4() - user = await BotXUser.create(user_huid=huid, cts=cts) - - assert await get_user_by_huid(huid) == user - - async def test_get_user_cts_by_huid(self): - cts = await BotXCTS.create(host="host") - - huid = uuid.uuid4() - await BotXUser.create(user_huid=huid, cts=cts) - - assert await get_cts_of_user_by_huid(huid) == cts - - async def test_get_personal_chat_for_user(self): - cts = await BotXCTS.create(host="host") - - huid, bot_id, chat_id = uuid.uuid4(), uuid.uuid4(), uuid.uuid4() - - await BotXBot.create(bot_id=bot_id, current_bot=True, cts=cts) - user = await BotXUser.create(user_huid=huid, cts=cts) - chat = await BotXChat.create( - group_chat_id=chat_id, chat_type=ChatTypes.chat, cts=cts - ) - await chat.members.add(user) - - assert await get_personal_chat_for_user(huid) == chat - - async def test_get_current_bot_by_cts_host(self): - cts = await BotXCTS.create(host="host") - - bot_id = uuid.uuid4() - await BotXBot.create(bot_id=bot_id, current_bot=True, cts=cts) - - assert await get_cts_of_current_bot_by_host(cts.host) == cts - - async def test_get_current_bot_by_host(self): - botx: BotXBot - cts = await BotXCTS.create(host="test") - bot = await BotXBot.create( - bot_id=uuid.uuid4(), name="test", current_bot=True, cts=cts - ) - assert await get_current_bot_by_host(cts.host) == bot From e2765e8084f2df946bdeb59bf8986f961b8d5574 Mon Sep 17 00:00:00 2001 From: vkarabanov Date: Mon, 18 Oct 2021 18:46:20 +0300 Subject: [PATCH 7/7] fix: review --- boxv2/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boxv2/plugin.py b/boxv2/plugin.py index 09c28bc..44463f3 100644 --- a/boxv2/plugin.py +++ b/boxv2/plugin.py @@ -61,7 +61,7 @@ def get_template_path(cls) -> Path: @classmethod def get_name(cls) -> str: - """Get plugin's name.""" + """Get plugin's lowercase name.""" module = inspect.getmodule(cls) if module is not None: #