From 4518c65a455a30408fc9b112762bf635aeceb2b4 Mon Sep 17 00:00:00 2001 From: Ezeudoh Tochukwu Date: Sat, 4 Oct 2025 08:15:07 +0100 Subject: [PATCH 1/5] docs correction --- docs/multiple/index.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/multiple/index.md b/docs/multiple/index.md index af90099..7a0a7d5 100644 --- a/docs/multiple/index.md +++ b/docs/multiple/index.md @@ -28,6 +28,23 @@ EllarSQLModule.setup( ) ``` +## **Configuring Async Databases** +When configuring async databases, +you need to specify the `+asyncpg` or `+aiosqlite` suffix to the database URL in case of SQLite and PostgreSQL respectively. + +For example, to configure an async database, you can use the following configuration: +```python +from ellar_sql import EllarSQLModule + +EllarSQLModule.setup( + databases={ + "default": "postgresql+asyncpg:///main", + "meta": "sqlite+aiosqlite:////path/to/meta.db", + }, + migration_options={'directory': 'migrations'} +) +``` + ## **Defining Models and Tables with Different Databases** **EllarSQL** creates **Metadata** and an **Engine** for each configured database. From 7e8c02415c799ceff0c334eb56c8ed1a809bf2a5 Mon Sep 17 00:00:00 2001 From: Ezeudoh Tochukwu Date: Sat, 4 Oct 2025 08:19:49 +0100 Subject: [PATCH 2/5] fixed mypy and ruff lint errors --- ellar_sql/cli/commands.py | 30 +++++++++++----------- ellar_sql/model/base.py | 6 ++--- ellar_sql/model/typeDecorator/file/file.py | 9 +++++++ ellar_sql/pagination/decorator.py | 6 ++--- ellar_sql/services/base.py | 6 ++--- samples/db-learning/db_learning/command.py | 4 ++- samples/index-script/main.py | 4 ++- 7 files changed, 39 insertions(+), 26 deletions(-) diff --git a/ellar_sql/cli/commands.py b/ellar_sql/cli/commands.py index 1218e02..81cfbf2 100644 --- a/ellar_sql/cli/commands.py +++ b/ellar_sql/cli/commands.py @@ -36,12 +36,12 @@ def db(): @click.option( "--sql", is_flag=True, - help="Don't emit SQL to database - dump to standard output " "instead", + help="Don't emit SQL to database - dump to standard output instead", ) @click.option( "--head", default="head", - help="Specify head revision or @head to base new " "revision on", + help="Specify head revision or @head to base new revision on", ) @click.option( "--splice", @@ -61,7 +61,7 @@ def db(): @click.option( "--rev-id", default=None, - help="Specify a hardcoded revision id instead of generating " "one", + help="Specify a hardcoded revision id instead of generating one", ) @click.pass_context def revision( @@ -102,12 +102,12 @@ def revision( @click.option( "--sql", is_flag=True, - help="Don't emit SQL to database - dump to standard output " "instead", + help="Don't emit SQL to database - dump to standard output instead", ) @click.option( "--head", default="head", - help="Specify head revision or @head to base new " "revision on", + help="Specify head revision or @head to base new revision on", ) @click.option( "--splice", @@ -127,7 +127,7 @@ def revision( @click.option( "--rev-id", default=None, - help="Specify a hardcoded revision id instead of generating " "one", + help="Specify a hardcoded revision id instead of generating one", ) @click.option( "-x", @@ -195,7 +195,7 @@ def edit(ctx, directory, revision): @click.option( "--rev-id", default=None, - help="Specify a hardcoded revision id instead of generating " "one", + help="Specify a hardcoded revision id instead of generating one", ) @click.argument("revisions", nargs=-1) @click.pass_context @@ -215,12 +215,12 @@ def merge(ctx, directory, message, branch_label, rev_id, revisions): @click.option( "--sql", is_flag=True, - help="Don't emit SQL to database - dump to standard output " "instead", + help="Don't emit SQL to database - dump to standard output instead", ) @click.option( "--tag", default=None, - help='Arbitrary "tag" name - can be used by custom env.py ' "scripts", + help='Arbitrary "tag" name - can be used by custom env.py scripts', ) @click.option( "-x", @@ -246,12 +246,12 @@ def upgrade(ctx, directory, sql, tag, x_arg, revision): @click.option( "--sql", is_flag=True, - help="Don't emit SQL to database - dump to standard output " "instead", + help="Don't emit SQL to database - dump to standard output instead", ) @click.option( "--tag", default=None, - help='Arbitrary "tag" name - can be used by custom env.py ' "scripts", + help='Arbitrary "tag" name - can be used by custom env.py scripts', ) @click.option( "-x", @@ -300,7 +300,7 @@ def show(ctx: click.Context, directory, revision): "-i", "--indicate-current", is_flag=True, - help="Indicate current version (Alembic 0.9.9 or greater is " "required)", + help="Indicate current version (Alembic 0.9.9 or greater is required)", ) @click.pass_context def history(ctx: click.Context, directory, rev_range, verbose, indicate_current): @@ -369,12 +369,12 @@ def current(ctx: click.Context, directory, verbose): @click.option( "--sql", is_flag=True, - help="Don't emit SQL to database - dump to standard output " "instead", + help="Don't emit SQL to database - dump to standard output instead", ) @click.option( "--tag", default=None, - help='Arbitrary "tag" name - can be used by custom env.py ' "scripts", + help='Arbitrary "tag" name - can be used by custom env.py scripts', ) @click.argument("revision", default="head") @click.pass_context @@ -416,7 +416,7 @@ def check(ctx: click.Context, directory): @click.option( "--package", is_flag=True, - help="Write empty __init__.py files to the environment and " "version locations", + help="Write empty __init__.py files to the environment and version locations", ) @click.pass_context def init(ctx: click.Context, directory, multiple, package): diff --git a/ellar_sql/model/base.py b/ellar_sql/model/base.py index 8e9c0b0..1b09f6f 100644 --- a/ellar_sql/model/base.py +++ b/ellar_sql/model/base.py @@ -62,9 +62,9 @@ def __new__( ) if isinstance(options, dict): options = ModelBaseConfig(**options) - assert isinstance( - options, ModelBaseConfig - ), f"{options.__class__} is not a support ModelMetaOptions" + assert isinstance(options, ModelBaseConfig), ( + f"{options.__class__} is not a support ModelMetaOptions" + ) if options.as_base: declarative_bases = _get_declarative_bases(options.use_bases) diff --git a/ellar_sql/model/typeDecorator/file/file.py b/ellar_sql/model/typeDecorator/file/file.py index f1d56ef..25e15b5 100644 --- a/ellar_sql/model/typeDecorator/file/file.py +++ b/ellar_sql/model/typeDecorator/file/file.py @@ -48,6 +48,15 @@ class File(BaseFile, AttributeDictAccessMixin): files: t.List[str] + # Type hints for dict-like methods from parent classes + if t.TYPE_CHECKING: + + def get(self, __key: str, __default: t.Any = None) -> t.Any: ... + + def __setitem__(self, __key: str, __value: t.Any) -> None: ... + + def __getitem__(self, __key: str) -> t.Any: ... + def __init__( self, content: t.Any = None, diff --git a/ellar_sql/pagination/decorator.py b/ellar_sql/pagination/decorator.py index 05bd4ce..c5ada8e 100644 --- a/ellar_sql/pagination/decorator.py +++ b/ellar_sql/pagination/decorator.py @@ -74,9 +74,9 @@ def _prepare_template_response( ]: if isinstance(res, tuple): filter_query, extra_context = res - assert isinstance( - extra_context, dict - ), "When using as `template_context`, route function should return a tuple(select, {})" + assert isinstance(extra_context, dict), ( + "When using as `template_context`, route function should return a tuple(select, {})" + ) elif isinstance(res, dict): filter_query = None diff --git a/ellar_sql/services/base.py b/ellar_sql/services/base.py index 5791387..5983e86 100644 --- a/ellar_sql/services/base.py +++ b/ellar_sql/services/base.py @@ -73,9 +73,9 @@ def engines(self) -> t.Dict[str, sa.Engine]: @property def engine(self) -> sa.Engine: - assert self._engines[self].get( - DEFAULT_KEY - ), f"{self.__class__.__name__} configuration is not ready" + assert self._engines[self].get(DEFAULT_KEY), ( + f"{self.__class__.__name__} configuration is not ready" + ) return self._engines[self][DEFAULT_KEY] def _setup( diff --git a/samples/db-learning/db_learning/command.py b/samples/db-learning/db_learning/command.py index ee43d0b..a9b8a2d 100644 --- a/samples/db-learning/db_learning/command.py +++ b/samples/db-learning/db_learning/command.py @@ -12,7 +12,9 @@ def seed_user(): session = db_service.session_factory() for i in range(300): - session.add(User(username=f"username-{i+1}", email=f"user{i+1}doe@example.com")) + session.add( + User(username=f"username-{i + 1}", email=f"user{i + 1}doe@example.com") + ) session.commit() db_service.session_factory.remove() diff --git a/samples/index-script/main.py b/samples/index-script/main.py index c22f66b..1c226c8 100644 --- a/samples/index-script/main.py +++ b/samples/index-script/main.py @@ -20,7 +20,9 @@ def main(): session = db_service.session_factory() for i in range(50): - session.add(User(username=f"username-{i+1}", email=f"user{i+1}doe@example.com")) + session.add( + User(username=f"username-{i + 1}", email=f"user{i + 1}doe@example.com") + ) session.commit() rows = session.execute(model.select(User)).scalars() From b2fac2fe2681f73f58499dea35a3a05bfa56051d Mon Sep 17 00:00:00 2001 From: Ezeudoh Tochukwu Date: Sat, 4 Oct 2025 08:21:57 +0100 Subject: [PATCH 3/5] dropped py3.8 and py3.9 --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/test_full.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ac2338c..cc19890 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.12' - name: Install Flit run: pip install flit - name: Install Dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d9bcff..dbbbc89 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.12' - name: Install Flit run: pip install flit - name: Install Dependencies diff --git a/.github/workflows/test_full.yml b/.github/workflows/test_full.yml index fb64362..5014e53 100644 --- a/.github/workflows/test_full.yml +++ b/.github/workflows/test_full.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 From 2fc547bd596ba9fb23c95e9eff827c5e90d6a87c Mon Sep 17 00:00:00 2001 From: Ezeudoh Tochukwu Date: Sat, 4 Oct 2025 08:22:10 +0100 Subject: [PATCH 4/5] dropped py3.8 and py3.9 --- .github/workflows/test_full.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_full.yml b/.github/workflows/test_full.yml index 5014e53..59f03ce 100644 --- a/.github/workflows/test_full.yml +++ b/.github/workflows/test_full.yml @@ -32,7 +32,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.12' - name: Install Flit run: pip install flit - name: Install Dependencies From 370134eb7138fbe2cc56bf0d3bedef4e40a31815 Mon Sep 17 00:00:00 2001 From: Ezeudoh Tochukwu Date: Sat, 4 Oct 2025 08:49:09 +0100 Subject: [PATCH 5/5] fix failing tests --- Makefile | 6 +++--- ellar_sql/factory/base.py | 5 +++++ tests/test_migrations/test_migrations_commands.py | 12 +++--------- tests/test_migrations/test_multiple_database.py | 8 ++------ 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 5cca96f..54dd531 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ help: @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' clean: ## Removing cached python compiled files - find . -name \*pyc | xargs rm -fv - find . -name \*pyo | xargs rm -fv - find . -name \*~ | xargs rm -fv + find . -name "*.pyc" -type f -delete + find . -name "*.pyo" -type f -delete + find . -name "*~" -type f -delete find . -name __pycache__ | xargs rm -rfv find . -name .pytest_cache | xargs rm -rfv find . -name .ruff_cache | xargs rm -rfv diff --git a/ellar_sql/factory/base.py b/ellar_sql/factory/base.py index b1f143d..6fff860 100644 --- a/ellar_sql/factory/base.py +++ b/ellar_sql/factory/base.py @@ -19,6 +19,10 @@ class EllarSQLOptions(SQLAlchemyOptions): + # Type hints for SQLAlchemy-specific attributes + sqlalchemy_get_or_create: t.Tuple[str, ...] + sqlalchemy_session_persistence: str + @staticmethod def _check_has_sqlalchemy_session_set(meta, value): if value and hasattr(meta, "sqlalchemy_session"): @@ -31,6 +35,7 @@ class EllarSQLFactory(SQLAlchemyModelFactory): """Factory for EllarSQL models.""" _options_class = EllarSQLOptions + _meta: EllarSQLOptions class Meta: abstract = True diff --git a/tests/test_migrations/test_migrations_commands.py b/tests/test_migrations/test_migrations_commands.py index fa5555a..30e9aaa 100644 --- a/tests/test_migrations/test_migrations_commands.py +++ b/tests/test_migrations/test_migrations_commands.py @@ -5,10 +5,7 @@ def test_migrate_upgrade(): result = run_command("default.py db init") assert result.returncode == 0 - assert ( - b"tests/dumbs/default/migrations/alembic.ini' before proceeding." - in result.stdout - ) + assert b"default/migrations/alembic.ini before proceeding." in result.stdout result = run_command("default.py db check") assert result.returncode == 1 @@ -36,7 +33,7 @@ def test_migrate_upgrade_custom_directory(): result = run_command("custom_directory.py db init") assert result.returncode == 0 assert ( - b"tests/dumbs/custom_directory/temp_migrations/alembic.ini' before proceeding." + b"custom_directory/temp_migrations/alembic.ini before proceeding." in result.stdout ) @@ -138,10 +135,7 @@ def test_other_alembic_commands(): def test_migrate_upgrade_async(): result = run_command("default_async.py db init") assert result.returncode == 0 - assert ( - b"tests/dumbs/default_async/migrations/alembic.ini' before proceeding." - in result.stdout - ) + assert b"default_async/migrations/alembic.ini before proceeding." in result.stdout result = run_command("default_async.py db check") assert result.returncode == 1 diff --git a/tests/test_migrations/test_multiple_database.py b/tests/test_migrations/test_multiple_database.py index dc5fb7f..5b1f561 100644 --- a/tests/test_migrations/test_multiple_database.py +++ b/tests/test_migrations/test_multiple_database.py @@ -6,10 +6,7 @@ def test_migrate_upgrade_for_multiple_database(): with set_env_variable("multiple_db", "true"): result = run_command("multiple_database.py db init -m") assert result.returncode == 0 - assert ( - b"tests/dumbs/multiple/migrations/alembic.ini' before proceeding." - in result.stdout - ) + assert b"multiple/migrations/alembic.ini before proceeding." in result.stdout result = run_command("multiple_database.py db check") assert result.returncode == 1 @@ -62,8 +59,7 @@ def test_migrate_upgrade_for_multiple_database_async(): result = run_command("multiple_database_async.py db init -m") assert result.returncode == 0 assert ( - b"tests/dumbs/multiple_async/migrations/alembic.ini' before proceeding." - in result.stdout + b"multiple_async/migrations/alembic.ini before proceeding." in result.stdout ) result = run_command("multiple_database_async.py db check")