From ca84389fd9f5b71fcb220959b52cc838edccb49d Mon Sep 17 00:00:00 2001 From: euri10 Date: Sun, 23 Nov 2025 08:56:24 +0100 Subject: [PATCH 1/6] docs: test usage migrations --- docs/examples/usage/usage_migrations_1.py | 52 ++++ docs/examples/usage/usage_migrations_10.py | 33 +++ docs/examples/usage/usage_migrations_2.py | 49 ++++ docs/examples/usage/usage_migrations_3.py | 31 +++ docs/examples/usage/usage_migrations_4.py | 19 ++ docs/examples/usage/usage_migrations_5.py | 26 ++ docs/examples/usage/usage_migrations_6.py | 36 +++ docs/examples/usage/usage_migrations_7.py | 21 ++ docs/examples/usage/usage_migrations_8.py | 14 ++ docs/examples/usage/usage_migrations_9.py | 16 ++ docs/usage/migrations.rst | 262 +++++---------------- 11 files changed, 357 insertions(+), 202 deletions(-) create mode 100644 docs/examples/usage/usage_migrations_1.py create mode 100644 docs/examples/usage/usage_migrations_10.py create mode 100644 docs/examples/usage/usage_migrations_2.py create mode 100644 docs/examples/usage/usage_migrations_3.py create mode 100644 docs/examples/usage/usage_migrations_4.py create mode 100644 docs/examples/usage/usage_migrations_5.py create mode 100644 docs/examples/usage/usage_migrations_6.py create mode 100644 docs/examples/usage/usage_migrations_7.py create mode 100644 docs/examples/usage/usage_migrations_8.py create mode 100644 docs/examples/usage/usage_migrations_9.py diff --git a/docs/examples/usage/usage_migrations_1.py b/docs/examples/usage/usage_migrations_1.py new file mode 100644 index 00000000..8c8249a7 --- /dev/null +++ b/docs/examples/usage/usage_migrations_1.py @@ -0,0 +1,52 @@ +__all__ = ("test_async_methods",) + + +from pytest_databases.docker.postgres import PostgresService + + +async def test_async_methods(postgres_service: PostgresService) -> None: + # start-example + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = ( + f"postgresql://{postgres_service.user}:{postgres_service.password}" + f"@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" + ) + config = AsyncpgConfig( + pool_config={"dsn": dsn}, migration_config={"enabled": True, "script_location": "migrations"} + ) + + # Apply migrations + await config.migrate_up("head") + # Or use the alias + await config.upgrade("head") + + # Rollback one revision + await config.migrate_down("-1") + # Or use the alias + await config.downgrade("-1") + + # Check current version + await config.get_current_migration(verbose=True) + # Create new migration + await config.create_migration("add users table", file_type="sql") + + # Initialize migrations directory + await config.init_migrations() + + # Stamp database to specific revision + await config.stamp_migration("0003") + + # Convert timestamp to sequential migrations + await config.fix_migrations(dry_run=False, update_database=True, yes=True) + # end-example + # These are just smoke tests for method presence, not actual DB calls + assert hasattr(config, "migrate_up") + assert hasattr(config, "upgrade") + assert hasattr(config, "migrate_down") + assert hasattr(config, "downgrade") + assert hasattr(config, "get_current_migration") + assert hasattr(config, "create_migration") + assert hasattr(config, "init_migrations") + assert hasattr(config, "stamp_migration") + assert hasattr(config, "fix_migrations") diff --git a/docs/examples/usage/usage_migrations_10.py b/docs/examples/usage/usage_migrations_10.py new file mode 100644 index 00000000..e2059849 --- /dev/null +++ b/docs/examples/usage/usage_migrations_10.py @@ -0,0 +1,33 @@ +__all__ = ("test_tracker_instance",) + + +from pytest_databases.docker.postgres import PostgresService + + +async def test_tracker_instance(postgres_service: PostgresService) -> None: + + # start-example + from sqlspec.adapters.asyncpg import AsyncpgConfig + from sqlspec.migrations.tracker import AsyncMigrationTracker + + tracker = AsyncMigrationTracker() + + config = AsyncpgConfig( + pool_config={ + "dsn": f"postgres://{postgres_service.user}:{postgres_service.password}@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" + }, + migration_config={ + "enabled": True, + "script_location": "migrations", + "version_table_name": "ddl_migrations", + "auto_sync": True, # Enable automatic version reconciliation + }, + ) + async with config.provide_session() as session: + driver = session._driver + + # Update version record + await tracker.update_version_record(driver, old_version="20251018120000", new_version="0003") + # end-example + # Just check that tracker is an instance of AsyncMigrationTracker + assert isinstance(tracker, AsyncMigrationTracker) diff --git a/docs/examples/usage/usage_migrations_2.py b/docs/examples/usage/usage_migrations_2.py new file mode 100644 index 00000000..44470e1e --- /dev/null +++ b/docs/examples/usage/usage_migrations_2.py @@ -0,0 +1,49 @@ +__all__ = ("test_sync_methods",) + + +# start-example +from sqlspec.adapters.sqlite import SqliteConfig + +config = SqliteConfig( + pool_config={"database": "myapp.db"}, migration_config={"enabled": True, "script_location": "migrations"} +) + +# Apply migrations (no await needed) +config.migrate_up("head") +# Or use the alias +config.upgrade("head") + +# Rollback one revision +config.migrate_down("-1") +# Or use the alias +config.downgrade("-1") + +# Check current version +current = config.get_current_migration(verbose=True) +print(current) + +# Create new migration +config.create_migration("add users table", file_type="sql") + +# Initialize migrations directory +config.init_migrations() + +# Stamp database to specific revision +config.stamp_migration("0003") + +# Convert timestamp to sequential migrations +config.fix_migrations(dry_run=False, update_database=True, yes=True) +# end-example + + +def test_sync_methods() -> None: + # Smoke tests for method presence, not actual DB calls + assert hasattr(config, "migrate_up") + assert hasattr(config, "upgrade") + assert hasattr(config, "migrate_down") + assert hasattr(config, "downgrade") + assert hasattr(config, "get_current_migration") + assert hasattr(config, "create_migration") + assert hasattr(config, "init_migrations") + assert hasattr(config, "stamp_migration") + assert hasattr(config, "fix_migrations") diff --git a/docs/examples/usage/usage_migrations_3.py b/docs/examples/usage/usage_migrations_3.py new file mode 100644 index 00000000..54acf538 --- /dev/null +++ b/docs/examples/usage/usage_migrations_3.py @@ -0,0 +1,31 @@ +__all__ = ("test_template_config",) + + +# start-example +migration_config = { + "default_format": "py", # CLI default when --format omitted + "title": "Acme Migration", # Shared title for all templates + "author": "env:SQLSPEC_AUTHOR", # Read from environment variable + "templates": { + "sql": { + "header": "-- {title} - {message}", + "metadata": ["-- Version: {version}", "-- Owner: {author}"], + "body": "-- custom SQL body", + }, + "py": { + "docstring": """{title}\nDescription: {description}""", + "imports": ["from typing import Iterable"], + "body": """def up(context: object | None = None) -> str | Iterable[str]:\n return \"SELECT 1\"\n\ndef down(context: object | None = None) -> str | Iterable[str]:\n return \"DROP TABLE example;\"\n""", + }, + }, +} +# end-example + + +def test_template_config() -> None: + # Check structure of migration_config + assert migration_config["default_format"] == "py" + assert "py" in migration_config["templates"] + assert "sql" in migration_config["templates"] + assert isinstance(migration_config["templates"]["py"], dict) + assert isinstance(migration_config["templates"]["sql"], dict) diff --git a/docs/examples/usage/usage_migrations_4.py b/docs/examples/usage/usage_migrations_4.py new file mode 100644 index 00000000..76b248ae --- /dev/null +++ b/docs/examples/usage/usage_migrations_4.py @@ -0,0 +1,19 @@ +async def test_async_command_class_methods() -> None: + + # start-example + from sqlspec.adapters.asyncpg import AsyncpgConfig + from sqlspec.migrations.commands import AsyncMigrationCommands + + config = AsyncpgConfig(pool_config={"dsn": "postgresql://..."}, migration_config={"script_location": "migrations"}) + + # Create commands instance + commands = AsyncMigrationCommands(config) + + # Use commands directly + await commands.upgrade("head") + # end-example + + # Smoke test for AsyncMigrationCommands method presence + assert hasattr(commands, "upgrade") + assert hasattr(commands, "downgrade") + assert hasattr(commands, "get_current_migration") diff --git a/docs/examples/usage/usage_migrations_5.py b/docs/examples/usage/usage_migrations_5.py new file mode 100644 index 00000000..128f7b92 --- /dev/null +++ b/docs/examples/usage/usage_migrations_5.py @@ -0,0 +1,26 @@ +from sqlspec.adapters.asyncpg import AsyncpgConfig + +__all__ = ("test_config_structure",) + + +# start-example +config = AsyncpgConfig( + pool_config={"dsn": "postgresql://user:pass@localhost/mydb"}, + migration_config={ + "enabled": True, + "script_location": "migrations", + "version_table_name": "ddl_migrations", + "auto_sync": True, # Enable automatic version reconciliation + }, +) +# end-example + + +def test_config_structure() -> None: + # Check config attributes + assert hasattr(config, "pool_config") + assert hasattr(config, "migration_config") + assert config.migration_config["enabled"] is True + assert config.migration_config["script_location"] == "migrations" + assert config.migration_config["version_table_name"] == "ddl_migrations" + assert config.migration_config["auto_sync"] is True diff --git a/docs/examples/usage/usage_migrations_6.py b/docs/examples/usage/usage_migrations_6.py new file mode 100644 index 00000000..e551a810 --- /dev/null +++ b/docs/examples/usage/usage_migrations_6.py @@ -0,0 +1,36 @@ +__all__ = ("downgrade", "test_upgrade_and_downgrade_strings", "upgrade") +# start-example +# migrations/0002_add_user_roles.py +"""Add user roles table + +Revision ID: 0002_add_user_roles +Created at: 2025-10-18 12:00:00 +""" + + +def upgrade() -> str: + """Apply migration.""" + return """ + CREATE TABLE user_roles ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + role VARCHAR(50) NOT NULL + ); + """ + + +def downgrade() -> str: + """Revert migration.""" + return """ + DROP TABLE user_roles; + """ + + +# end-example + + +def test_upgrade_and_downgrade_strings() -> None: + up_sql = upgrade() + down_sql = downgrade() + assert "CREATE TABLE user_roles" in up_sql + assert "DROP TABLE user_roles" in down_sql diff --git a/docs/examples/usage/usage_migrations_7.py b/docs/examples/usage/usage_migrations_7.py new file mode 100644 index 00000000..570b6ee1 --- /dev/null +++ b/docs/examples/usage/usage_migrations_7.py @@ -0,0 +1,21 @@ +__all__ = ("test_upgrade_returns_list", "upgrade") + + +# start-example +def upgrade() -> list[str]: + """Apply migration in multiple steps.""" + return [ + "CREATE TABLE products (id SERIAL PRIMARY KEY);", + "CREATE TABLE orders (id SERIAL PRIMARY KEY, product_id INTEGER);", + "CREATE INDEX idx_orders_product ON orders(product_id);", + ] + + +# end-example + + +def test_upgrade_returns_list() -> None: + stmts = upgrade() + assert isinstance(stmts, list) + assert any("products" in s for s in stmts) + assert any("orders" in s for s in stmts) diff --git a/docs/examples/usage/usage_migrations_8.py b/docs/examples/usage/usage_migrations_8.py new file mode 100644 index 00000000..33afbfde --- /dev/null +++ b/docs/examples/usage/usage_migrations_8.py @@ -0,0 +1,14 @@ +def test_version_comparison() -> None: + # start-example + from sqlspec.utils.version import parse_version + + v1 = parse_version("0001") + v2 = parse_version("20251018120000") + + # Sequential < Timestamp (by design) + assert v1 < v2 + + # Same type comparisons work naturally + assert parse_version("0001") < parse_version("0002") + assert parse_version("20251018120000") < parse_version("20251019120000") + # end-example diff --git a/docs/examples/usage/usage_migrations_9.py b/docs/examples/usage/usage_migrations_9.py new file mode 100644 index 00000000..27b2f28f --- /dev/null +++ b/docs/examples/usage/usage_migrations_9.py @@ -0,0 +1,16 @@ +from sqlspec.adapters.asyncpg import AsyncpgConfig + + +def test_extension_config() -> None: + # start-example + config = AsyncpgConfig( + pool_config={"dsn": "postgresql://..."}, + migration_config={ + "enabled": True, + "script_location": "migrations", + "include_extensions": ["litestar"], # Enable litestar extension migrations + }, + extension_config={"litestar": {"enable_repository_pattern": True, "enable_dto_generation": False}}, + ) + # end-example + assert config.extension_config diff --git a/docs/usage/migrations.rst b/docs/usage/migrations.rst index 17811446..4bf23a41 100644 --- a/docs/usage/migrations.rst +++ b/docs/usage/migrations.rst @@ -40,86 +40,24 @@ Async Adapters For async adapters (AsyncPG, Asyncmy, Aiosqlite, Psqlpy), migration methods return awaitables: -.. code-block:: python - - from sqlspec.adapters.asyncpg import AsyncpgConfig - - config = AsyncpgConfig( - pool_config={"dsn": "postgresql://user:pass@localhost/mydb"}, - migration_config={ - "enabled": True, - "script_location": "migrations", - } - ) - - # Apply migrations - await config.migrate_up("head") - # Or use the alias - await config.upgrade("head") - - # Rollback one revision - await config.migrate_down("-1") - # Or use the alias - await config.downgrade("-1") - - # Check current version - current = await config.get_current_migration(verbose=True) - print(current) - - # Create new migration - await config.create_migration("add users table", file_type="sql") - - # Initialize migrations directory - await config.init_migrations() - - # Stamp database to specific revision - await config.stamp_migration("0003") - - # Convert timestamp to sequential migrations - await config.fix_migrations(dry_run=False, update_database=True, yes=True) +.. literalinclude:: /examples/usage/usage_migrations_1.py + :language: python + :caption: `async adapters` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Sync Adapters ------------- For sync adapters (SQLite, DuckDB), migration methods execute immediately without await: -.. code-block:: python - - from sqlspec.adapters.sqlite import SqliteConfig - - config = SqliteConfig( - pool_config={"database": "myapp.db"}, - migration_config={ - "enabled": True, - "script_location": "migrations", - } - ) - - # Apply migrations (no await needed) - config.migrate_up("head") - # Or use the alias - config.upgrade("head") - - # Rollback one revision - config.migrate_down("-1") - # Or use the alias - config.downgrade("-1") - - # Check current version - current = config.get_current_migration(verbose=True) - print(current) - - # Create new migration - config.create_migration("add users table", file_type="sql") - - # Initialize migrations directory - config.init_migrations() - - # Stamp database to specific revision - config.stamp_migration("0003") - - # Convert timestamp to sequential migrations - config.fix_migrations(dry_run=False, update_database=True, yes=True) +.. literalinclude:: /examples/usage/usage_migrations_2.py + :language: python + :caption: `sync adapters` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Available Methods ----------------- @@ -146,30 +84,12 @@ Migrations inherit their header text, metadata comments, and default file format from ``migration_config["templates"]``. Each project can define multiple profiles and select one globally: -.. code-block:: python - - migration_config={ - "default_format": "py", # CLI default when --format omitted - "title": "Acme Migration", # Shared title for all templates - "author": "env:SQLSPEC_AUTHOR", # Read from environment variable - "templates": { - "sql": { - "header": "-- {title} - {message}", - "metadata": ["-- Version: {version}", "-- Owner: {author}"], - "body": "-- custom SQL body" - }, - "py": { - "docstring": """{title}\nDescription: {description}""", - "imports": ["from typing import Iterable"], - "body": """def up(context: object | None = None) -> str | Iterable[str]: - return "SELECT 1" - -def down(context: object | None = None) -> str | Iterable[str]: - return "DROP TABLE example;" -""" - } - } - } +.. literalinclude:: /examples/usage/usage_migrations_3.py + :language: python + :caption: `template profile` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Template fragments accept the following variables: @@ -231,21 +151,12 @@ Command Classes (Advanced) For advanced use cases requiring custom logic, you can still use command classes directly: -.. code-block:: python - - from sqlspec.migrations.commands import AsyncMigrationCommands, SyncMigrationCommands - from sqlspec.adapters.asyncpg import AsyncpgConfig - - config = AsyncpgConfig( - pool_config={"dsn": "postgresql://..."}, - migration_config={"script_location": "migrations"} - ) - - # Create commands instance - commands = AsyncMigrationCommands(config) - - # Use commands directly - await commands.upgrade("head") +.. literalinclude:: /examples/usage/usage_migrations_4.py + :language: python + :caption: `command classes` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example This approach is useful when: @@ -259,19 +170,12 @@ Configuration Enable migrations in your SQLSpec configuration: -.. code-block:: python - - from sqlspec.adapters.asyncpg import AsyncpgConfig - - config = AsyncpgConfig( - pool_config={"dsn": "postgresql://user:pass@localhost/mydb"}, - migration_config={ - "enabled": True, - "script_location": "migrations", - "version_table_name": "ddl_migrations", - "auto_sync": True, # Enable automatic version reconciliation - } - ) +.. literalinclude:: /examples/usage/usage_migrations_5.py + :language: python + :caption: `configuration` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Configuration Options --------------------- @@ -345,44 +249,24 @@ Python Migrations Python migrations provide more flexibility for complex operations: -.. code-block:: python - - # migrations/0002_add_user_roles.py - """Add user roles table - - Revision ID: 0002_add_user_roles - Created at: 2025-10-18 12:00:00 - """ - - def upgrade(): - """Apply migration.""" - return """ - CREATE TABLE user_roles ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id), - role VARCHAR(50) NOT NULL - ); - """ - - def downgrade(): - """Revert migration.""" - return """ - DROP TABLE user_roles; - """ +.. literalinclude:: /examples/usage/usage_migrations_6.py + :language: python + :caption: `python migrations` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example **Advanced Usage:** Python migrations can also return a list of SQL statements: -.. code-block:: python +.. literalinclude:: /examples/usage/usage_migrations_7.py + :language: python + :caption: `advanced usage` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example - def upgrade(): - """Apply migration in multiple steps.""" - return [ - "CREATE TABLE products (id SERIAL PRIMARY KEY);", - "CREATE TABLE orders (id SERIAL PRIMARY KEY, product_id INTEGER);", - "CREATE INDEX idx_orders_product ON orders(product_id);", - ] .. _hybrid-versioning-guide: @@ -475,19 +359,12 @@ Version Comparison SQLSpec uses type-aware version comparison: -.. code-block:: python - - from sqlspec.utils.version import parse_version - - v1 = parse_version("0001") - v2 = parse_version("20251018120000") - - # Sequential < Timestamp (by design) - assert v1 < v2 - - # Same type comparisons work naturally - assert parse_version("0001") < parse_version("0002") - assert parse_version("20251018120000") < parse_version("20251019120000") +.. literalinclude:: /examples/usage/usage_migrations_8.py + :language: python + :caption: `version comparison` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Migration Tracking ================== @@ -562,22 +439,12 @@ SQLSpec supports independent migration versioning for extensions and plugins. Configuration ------------- -.. code-block:: python - - config = AsyncpgConfig( - pool_config={"dsn": "postgresql://..."}, - migration_config={ - "enabled": True, - "script_location": "migrations", - "include_extensions": ["litestar"], # Enable litestar extension migrations - }, - extension_config={ - "litestar": { - "enable_repository_pattern": True, - "enable_dto_generation": False, - } - } - ) +.. literalinclude:: /examples/usage/usage_migrations_9.py + :language: python + :caption: `extension migrations` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Directory Structure ------------------- @@ -650,21 +517,12 @@ Manual Version Reconciliation If auto-sync is disabled, manually reconcile renamed migrations: -.. code-block:: python - - from sqlspec.migrations.tracker import AsyncMigrationTracker - - tracker = AsyncMigrationTracker() - - async with config.provide_session() as session: - driver = session._driver - - # Update version record - await tracker.update_version_record( - driver, - old_version="20251018120000", - new_version="0003" - ) +.. literalinclude:: /examples/usage/usage_migrations_10.py + :language: python + :caption: `manual version` + :dedent: 0 + :start-after: # start-example + :end-before: # end-example Troubleshooting =============== From ac8cbbbf03f04b15b6a3b846895ae5b800a652ff Mon Sep 17 00:00:00 2001 From: euri10 Date: Sun, 23 Nov 2025 09:22:42 +0100 Subject: [PATCH 2/6] removed inexisting reference --- docs/usage/migrations.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/usage/migrations.rst b/docs/usage/migrations.rst index 4bf23a41..e4d8885e 100644 --- a/docs/usage/migrations.rst +++ b/docs/usage/migrations.rst @@ -643,4 +643,3 @@ See Also - :doc:`../usage/cli` - Complete CLI command reference - :doc:`../usage/configuration` - Migration configuration options -- :doc:`../reference/migrations_api` - Migration API reference From 25f2f9b1e046f0dab2445566feba4153b7476c36 Mon Sep 17 00:00:00 2001 From: euri10 Date: Sun, 23 Nov 2025 09:32:29 +0100 Subject: [PATCH 3/6] lint --- docs/examples/usage/usage_migrations_4.py | 2 ++ docs/examples/usage/usage_migrations_8.py | 3 +++ docs/examples/usage/usage_migrations_9.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/docs/examples/usage/usage_migrations_4.py b/docs/examples/usage/usage_migrations_4.py index 76b248ae..1b100f8a 100644 --- a/docs/examples/usage/usage_migrations_4.py +++ b/docs/examples/usage/usage_migrations_4.py @@ -1,4 +1,6 @@ async def test_async_command_class_methods() -> None: +__all__ = ("test_async_command_class_methods", ) + # start-example from sqlspec.adapters.asyncpg import AsyncpgConfig diff --git a/docs/examples/usage/usage_migrations_8.py b/docs/examples/usage/usage_migrations_8.py index 33afbfde..c9a7facd 100644 --- a/docs/examples/usage/usage_migrations_8.py +++ b/docs/examples/usage/usage_migrations_8.py @@ -1,4 +1,7 @@ def test_version_comparison() -> None: +__all__ = ("test_version_comparison", ) + + # start-example from sqlspec.utils.version import parse_version diff --git a/docs/examples/usage/usage_migrations_9.py b/docs/examples/usage/usage_migrations_9.py index 27b2f28f..aa93ed28 100644 --- a/docs/examples/usage/usage_migrations_9.py +++ b/docs/examples/usage/usage_migrations_9.py @@ -1,5 +1,7 @@ from sqlspec.adapters.asyncpg import AsyncpgConfig +__all__ = ("test_extension_config", ) + def test_extension_config() -> None: # start-example From 6aee90e31d0af1fc809bfbe98ce1566aeff44862 Mon Sep 17 00:00:00 2001 From: euri10 Date: Sun, 23 Nov 2025 09:38:05 +0100 Subject: [PATCH 4/6] lint again --- docs/examples/usage/usage_migrations_9.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/usage/usage_migrations_9.py b/docs/examples/usage/usage_migrations_9.py index aa93ed28..544520f8 100644 --- a/docs/examples/usage/usage_migrations_9.py +++ b/docs/examples/usage/usage_migrations_9.py @@ -1,6 +1,6 @@ from sqlspec.adapters.asyncpg import AsyncpgConfig -__all__ = ("test_extension_config", ) +__all__ = ("test_extension_config",) def test_extension_config() -> None: From f6d2254c70b9cffd762d54c33ba47ba625ce76c5 Mon Sep 17 00:00:00 2001 From: euri10 Date: Sun, 23 Nov 2025 09:43:25 +0100 Subject: [PATCH 5/6] pre-commit is true shit --- docs/examples/usage/usage_migrations_4.py | 5 +++-- docs/examples/usage/usage_migrations_8.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/examples/usage/usage_migrations_4.py b/docs/examples/usage/usage_migrations_4.py index 1b100f8a..d88c4893 100644 --- a/docs/examples/usage/usage_migrations_4.py +++ b/docs/examples/usage/usage_migrations_4.py @@ -1,6 +1,7 @@ -async def test_async_command_class_methods() -> None: -__all__ = ("test_async_command_class_methods", ) +__all__ = ("test_async_command_class_methods",) + +async def test_async_command_class_methods() -> None: # start-example from sqlspec.adapters.asyncpg import AsyncpgConfig diff --git a/docs/examples/usage/usage_migrations_8.py b/docs/examples/usage/usage_migrations_8.py index c9a7facd..c80a582e 100644 --- a/docs/examples/usage/usage_migrations_8.py +++ b/docs/examples/usage/usage_migrations_8.py @@ -1,6 +1,7 @@ -def test_version_comparison() -> None: -__all__ = ("test_version_comparison", ) +__all__ = ("test_version_comparison",) + +def test_version_comparison() -> None: # start-example from sqlspec.utils.version import parse_version From 64e9b9c54a3f9a18f8756f8de18fe2b561d97aef Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Tue, 25 Nov 2025 04:31:34 +0000 Subject: [PATCH 6/6] feat: update examples to use environment variable for DSN and improve migration handling --- docs/examples/arrow/arrow_basic_usage.py | 5 +- .../configs/multi_adapter_registry.py | 9 +- docs/examples/usage/usage_migrations_1.py | 90 +++++++++---------- docs/examples/usage/usage_migrations_10.py | 72 +++++++++------ docs/examples/usage/usage_migrations_2.py | 69 +++++++------- docs/examples/usage/usage_migrations_4.py | 41 ++++++--- docs/examples/usage/usage_migrations_5.py | 5 +- docs/examples/usage/usage_migrations_9.py | 5 +- 8 files changed, 165 insertions(+), 131 deletions(-) diff --git a/docs/examples/arrow/arrow_basic_usage.py b/docs/examples/arrow/arrow_basic_usage.py index 04b4fa5a..3c5c6f70 100644 --- a/docs/examples/arrow/arrow_basic_usage.py +++ b/docs/examples/arrow/arrow_basic_usage.py @@ -75,11 +75,14 @@ async def example_adbc_native() -> None: # Example 2: PostgreSQL with Conversion Path async def example_postgres_conversion() -> None: """Demonstrate PostgreSQL adapter with dict → Arrow conversion.""" + import os + from sqlspec import SQLSpec from sqlspec.adapters.asyncpg import AsyncpgConfig + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") db_manager = SQLSpec() - asyncpg_db = db_manager.add_config(AsyncpgConfig(pool_config={"dsn": "postgresql://localhost/test"})) + asyncpg_db = db_manager.add_config(AsyncpgConfig(pool_config={"dsn": dsn})) async with db_manager.provide_session(asyncpg_db) as session: # Create test table with PostgreSQL-specific types diff --git a/docs/examples/patterns/configs/multi_adapter_registry.py b/docs/examples/patterns/configs/multi_adapter_registry.py index 2a322d9e..60a81823 100644 --- a/docs/examples/patterns/configs/multi_adapter_registry.py +++ b/docs/examples/patterns/configs/multi_adapter_registry.py @@ -1,5 +1,7 @@ """Show how to register multiple adapters on a single SQLSpec instance.""" +import os + from sqlspec import SQLSpec from sqlspec.adapters.aiosqlite import AiosqliteConfig from sqlspec.adapters.asyncpg import AsyncpgConfig, AsyncpgPoolConfig @@ -11,15 +13,12 @@ def build_registry() -> "SQLSpec": """Create a registry with both sync and async adapters.""" + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") registry = SQLSpec() registry.add_config(SqliteConfig(bind_key="sync_sqlite", pool_config={"database": ":memory:"})) registry.add_config(AiosqliteConfig(bind_key="async_sqlite", pool_config={"database": ":memory:"})) registry.add_config(DuckDBConfig(bind_key="duckdb_docs", pool_config={"database": ":memory:docs_duck"})) - registry.add_config( - AsyncpgConfig( - bind_key="asyncpg_docs", pool_config=AsyncpgPoolConfig(dsn="postgresql://user:pass@localhost:5432/db") - ) - ) + registry.add_config(AsyncpgConfig(bind_key="asyncpg_docs", pool_config=AsyncpgPoolConfig(dsn=dsn))) return registry diff --git a/docs/examples/usage/usage_migrations_1.py b/docs/examples/usage/usage_migrations_1.py index 8c8249a7..7ac7c9e1 100644 --- a/docs/examples/usage/usage_migrations_1.py +++ b/docs/examples/usage/usage_migrations_1.py @@ -1,52 +1,50 @@ -__all__ = ("test_async_methods",) +"""Async migration commands via config methods.""" +import tempfile +from pathlib import Path +import pytest from pytest_databases.docker.postgres import PostgresService +pytestmark = pytest.mark.xdist_group("postgres") + +__all__ = ("test_async_methods",) + async def test_async_methods(postgres_service: PostgresService) -> None: - # start-example - from sqlspec.adapters.asyncpg import AsyncpgConfig - - dsn = ( - f"postgresql://{postgres_service.user}:{postgres_service.password}" - f"@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" - ) - config = AsyncpgConfig( - pool_config={"dsn": dsn}, migration_config={"enabled": True, "script_location": "migrations"} - ) - - # Apply migrations - await config.migrate_up("head") - # Or use the alias - await config.upgrade("head") - - # Rollback one revision - await config.migrate_down("-1") - # Or use the alias - await config.downgrade("-1") - - # Check current version - await config.get_current_migration(verbose=True) - # Create new migration - await config.create_migration("add users table", file_type="sql") - - # Initialize migrations directory - await config.init_migrations() - - # Stamp database to specific revision - await config.stamp_migration("0003") - - # Convert timestamp to sequential migrations - await config.fix_migrations(dry_run=False, update_database=True, yes=True) - # end-example - # These are just smoke tests for method presence, not actual DB calls - assert hasattr(config, "migrate_up") - assert hasattr(config, "upgrade") - assert hasattr(config, "migrate_down") - assert hasattr(config, "downgrade") - assert hasattr(config, "get_current_migration") - assert hasattr(config, "create_migration") - assert hasattr(config, "init_migrations") - assert hasattr(config, "stamp_migration") - assert hasattr(config, "fix_migrations") + with tempfile.TemporaryDirectory() as temp_dir: + migration_dir = Path(temp_dir) / "migrations" + migration_dir.mkdir() + + # start-example + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = ( + f"postgresql://{postgres_service.user}:{postgres_service.password}" + f"@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" + ) + config = AsyncpgConfig( + pool_config={"dsn": dsn}, migration_config={"enabled": True, "script_location": str(migration_dir)} + ) + + # Initialize migrations directory (creates __init__.py if package=True) + await config.init_migrations() + + # Create new migration file + await config.create_migration("add users table", file_type="sql") + + # Apply migrations to head + await config.migrate_up("head") + + # Rollback one revision + await config.migrate_down("-1") + + # Check current version + await config.get_current_migration(verbose=True) + + # Stamp database to specific revision + await config.stamp_migration("0001") + + # Convert timestamp to sequential migrations + await config.fix_migrations(dry_run=True, update_database=False, yes=True) + # end-example diff --git a/docs/examples/usage/usage_migrations_10.py b/docs/examples/usage/usage_migrations_10.py index e2059849..40741acc 100644 --- a/docs/examples/usage/usage_migrations_10.py +++ b/docs/examples/usage/usage_migrations_10.py @@ -1,33 +1,51 @@ -__all__ = ("test_tracker_instance",) +"""Using AsyncMigrationTracker for version management.""" +import tempfile +from pathlib import Path +import pytest from pytest_databases.docker.postgres import PostgresService +pytestmark = pytest.mark.xdist_group("postgres") -async def test_tracker_instance(postgres_service: PostgresService) -> None: +__all__ = ("test_tracker_instance",) - # start-example - from sqlspec.adapters.asyncpg import AsyncpgConfig - from sqlspec.migrations.tracker import AsyncMigrationTracker - - tracker = AsyncMigrationTracker() - - config = AsyncpgConfig( - pool_config={ - "dsn": f"postgres://{postgres_service.user}:{postgres_service.password}@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" - }, - migration_config={ - "enabled": True, - "script_location": "migrations", - "version_table_name": "ddl_migrations", - "auto_sync": True, # Enable automatic version reconciliation - }, - ) - async with config.provide_session() as session: - driver = session._driver - - # Update version record - await tracker.update_version_record(driver, old_version="20251018120000", new_version="0003") - # end-example - # Just check that tracker is an instance of AsyncMigrationTracker - assert isinstance(tracker, AsyncMigrationTracker) + +async def test_tracker_instance(postgres_service: PostgresService) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + migration_dir = Path(temp_dir) / "migrations" + migration_dir.mkdir() + + # start-example + from sqlspec.adapters.asyncpg import AsyncpgConfig + from sqlspec.migrations.tracker import AsyncMigrationTracker + + # Create tracker with custom table name + tracker = AsyncMigrationTracker(version_table_name="ddl_migrations") + + dsn = ( + f"postgresql://{postgres_service.user}:{postgres_service.password}" + f"@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" + ) + config = AsyncpgConfig( + pool_config={"dsn": dsn}, + migration_config={ + "enabled": True, + "script_location": str(migration_dir), + "version_table_name": "ddl_migrations", + "auto_sync": True, # Enable automatic version reconciliation + }, + ) + + # Use the session to work with migrations + async with config.provide_session() as session: + # Ensure the tracking table exists + await tracker.ensure_tracking_table(session) + + # Get current version (None if no migrations applied) + current = await tracker.get_current_version(session) + print(f"Current version: {current}") + # end-example + + assert isinstance(tracker, AsyncMigrationTracker) + assert config.migration_config["version_table_name"] == "ddl_migrations" diff --git a/docs/examples/usage/usage_migrations_2.py b/docs/examples/usage/usage_migrations_2.py index 44470e1e..138fdaf7 100644 --- a/docs/examples/usage/usage_migrations_2.py +++ b/docs/examples/usage/usage_migrations_2.py @@ -1,49 +1,44 @@ -__all__ = ("test_sync_methods",) +"""Sync migration commands via config methods.""" +import tempfile +from pathlib import Path -# start-example -from sqlspec.adapters.sqlite import SqliteConfig +__all__ = ("test_sync_methods",) -config = SqliteConfig( - pool_config={"database": "myapp.db"}, migration_config={"enabled": True, "script_location": "migrations"} -) -# Apply migrations (no await needed) -config.migrate_up("head") -# Or use the alias -config.upgrade("head") +def test_sync_methods() -> None: + with tempfile.TemporaryDirectory() as temp_dir: + migration_dir = Path(temp_dir) / "migrations" + migration_dir.mkdir() + temp_db = Path(temp_dir) / "test.db" -# Rollback one revision -config.migrate_down("-1") -# Or use the alias -config.downgrade("-1") + # start-example + from sqlspec.adapters.sqlite import SqliteConfig -# Check current version -current = config.get_current_migration(verbose=True) -print(current) + config = SqliteConfig( + pool_config={"database": str(temp_db)}, + migration_config={"enabled": True, "script_location": str(migration_dir)}, + ) -# Create new migration -config.create_migration("add users table", file_type="sql") + # Initialize migrations directory (creates __init__.py if package=True) + config.init_migrations() -# Initialize migrations directory -config.init_migrations() + # Create new migration file + config.create_migration("add users table", file_type="sql") -# Stamp database to specific revision -config.stamp_migration("0003") + # Apply migrations to head (no await needed for sync) + config.migrate_up("head") -# Convert timestamp to sequential migrations -config.fix_migrations(dry_run=False, update_database=True, yes=True) -# end-example + # Rollback one revision + config.migrate_down("-1") + # Check current version + current = config.get_current_migration(verbose=True) + print(current) -def test_sync_methods() -> None: - # Smoke tests for method presence, not actual DB calls - assert hasattr(config, "migrate_up") - assert hasattr(config, "upgrade") - assert hasattr(config, "migrate_down") - assert hasattr(config, "downgrade") - assert hasattr(config, "get_current_migration") - assert hasattr(config, "create_migration") - assert hasattr(config, "init_migrations") - assert hasattr(config, "stamp_migration") - assert hasattr(config, "fix_migrations") + # Stamp database to specific revision + config.stamp_migration("0001") + + # Convert timestamp to sequential migrations + config.fix_migrations(dry_run=True, update_database=False, yes=True) + # end-example diff --git a/docs/examples/usage/usage_migrations_4.py b/docs/examples/usage/usage_migrations_4.py index d88c4893..326e03a0 100644 --- a/docs/examples/usage/usage_migrations_4.py +++ b/docs/examples/usage/usage_migrations_4.py @@ -1,22 +1,37 @@ +"""Using AsyncMigrationCommands directly.""" + +import os +import tempfile +from pathlib import Path + __all__ = ("test_async_command_class_methods",) async def test_async_command_class_methods() -> None: + with tempfile.TemporaryDirectory() as temp_dir: + migration_dir = Path(temp_dir) / "migrations" + migration_dir.mkdir() - # start-example - from sqlspec.adapters.asyncpg import AsyncpgConfig - from sqlspec.migrations.commands import AsyncMigrationCommands + # start-example + from sqlspec.adapters.asyncpg import AsyncpgConfig + from sqlspec.migrations.commands import AsyncMigrationCommands - config = AsyncpgConfig(pool_config={"dsn": "postgresql://..."}, migration_config={"script_location": "migrations"}) + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + config = AsyncpgConfig(pool_config={"dsn": dsn}, migration_config={"script_location": str(migration_dir)}) - # Create commands instance - commands = AsyncMigrationCommands(config) + # Create commands instance + commands = AsyncMigrationCommands(config) - # Use commands directly - await commands.upgrade("head") - # end-example + # Use commands directly + await commands.init(str(migration_dir)) + await commands.upgrade("head") + # end-example - # Smoke test for AsyncMigrationCommands method presence - assert hasattr(commands, "upgrade") - assert hasattr(commands, "downgrade") - assert hasattr(commands, "get_current_migration") + # Smoke test for AsyncMigrationCommands method presence + assert hasattr(commands, "upgrade") + assert hasattr(commands, "downgrade") + assert hasattr(commands, "current") + assert hasattr(commands, "revision") + assert hasattr(commands, "stamp") + assert hasattr(commands, "fix") + assert hasattr(commands, "init") diff --git a/docs/examples/usage/usage_migrations_5.py b/docs/examples/usage/usage_migrations_5.py index 128f7b92..9b85602e 100644 --- a/docs/examples/usage/usage_migrations_5.py +++ b/docs/examples/usage/usage_migrations_5.py @@ -1,11 +1,14 @@ +import os + from sqlspec.adapters.asyncpg import AsyncpgConfig __all__ = ("test_config_structure",) # start-example +dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") config = AsyncpgConfig( - pool_config={"dsn": "postgresql://user:pass@localhost/mydb"}, + pool_config={"dsn": dsn}, migration_config={ "enabled": True, "script_location": "migrations", diff --git a/docs/examples/usage/usage_migrations_9.py b/docs/examples/usage/usage_migrations_9.py index 544520f8..9d25fb19 100644 --- a/docs/examples/usage/usage_migrations_9.py +++ b/docs/examples/usage/usage_migrations_9.py @@ -1,3 +1,5 @@ +import os + from sqlspec.adapters.asyncpg import AsyncpgConfig __all__ = ("test_extension_config",) @@ -5,8 +7,9 @@ def test_extension_config() -> None: # start-example + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") config = AsyncpgConfig( - pool_config={"dsn": "postgresql://..."}, + pool_config={"dsn": dsn}, migration_config={ "enabled": True, "script_location": "migrations",