import logging from dataclasses import dataclass, field from typing import ClassVar, List, Optional from sqlalchemy.engine import make_url from service.conf import settings from service.db import get_db_url from service.utils import shell logger = logging.getLogger(__name__) @dataclass class CommandOptions: parent_cmd: ClassVar[str] = "dbmate" cmd_name: str url: str migrations_dir: str dump_schema: bool schema_file: str disable_ssl: bool extra_args: List[str] = field(default_factory=list) @classmethod def build( cls, cmd_name: str, schema_name: str, schema_path: Optional[str] = None, dump_schema: bool = True, disable_ssl: bool = False, extra_args: Optional[List[str]] = None, ) -> "CommandOptions": schema_name = schema_name schema_path = schema_path or schema_name extra_args = extra_args or [] db_url = make_url(get_db_url(driver="postgresql")) query_dict = {"search_path": schema_name} if disable_ssl: query_dict.update({"sslmode": "disable"}) url = db_url.update_query_dict(query_dict) migrations_dir = settings.MIGRATIONS_ROOT / schema_path schema_file = migrations_dir / "schema.sql" return cls( cmd_name=cmd_name, url=str(url), migrations_dir=str(migrations_dir), dump_schema=dump_schema, schema_file=str(schema_file), disable_ssl=disable_ssl, extra_args=extra_args, ) @property def cmd(self) -> str: args = [ self.parent_cmd, "--url", f'"{self.url}"', "--migrations-dir", self.migrations_dir, ] if not self.dump_schema: args.append("--no-dump-schema") else: args.append(f"--schema-file {self.schema_file}") return " ".join(list(map(str, args)) + [self.cmd_name] + self.extra_args) def __str__(self) -> str: return self.cmd def migrate( schema_name: str, schema_path: Optional[str] = None, dump_schema: bool = True, disable_ssl: bool = False, ) -> None: options = CommandOptions.build( cmd_name="migrate", schema_name=schema_name, schema_path=schema_path, dump_schema=dump_schema, disable_ssl=disable_ssl, ) logging.info( f'Applying {options.migrations_dir.upper()} migrations to "{schema_name}" schema:' ) shell.run(options.cmd) def up( schema_name: str, schema_path: Optional[str] = None, disable_ssl: bool = False, ) -> None: builder = CommandOptions.build( cmd_name="up", schema_name=schema_name, schema_path=schema_path, dump_schema=False, disable_ssl=disable_ssl, ) shell.run(builder.cmd) def new( message: str, schema_name: str, schema_path: Optional[str] = None, disable_ssl: bool = False, ) -> None: builder = CommandOptions.build( cmd_name="new", schema_name=schema_name, schema_path=schema_path, dump_schema=False, disable_ssl=disable_ssl, extra_args=[message], ) shell.run(builder.cmd) def wait(disable_ssl: bool = False) -> None: builder = CommandOptions.build( cmd_name="wait", schema_name="public", dump_schema=False, disable_ssl=disable_ssl, ) shell.run(builder.cmd)