Alembic Utils

repository·master·Indexed 43 days ago

https://github.com/olirice/alembic_utils

A SQLAlchemy/Alembic extension (version 0.8.8) that provides autogeneration support for PostgreSQL-specific entities, including functions, views, materialized views, triggers, policies, and extensions. It allows developers to define these entities using classes like PGFunction and PGView, and automatically detect changes via the `alembic revision --autogenerate` command to populate migration scripts with `op.create_entity`, `op.drop_entity`, and `op.replace_entity` calls.

Tokens
14.4K
Snippets
56
Records
67
Agent score
54%

What's inside Alembic Utils

  1. Overview of Alembic Utils

    master

    Alembic Utils is an extension for Alembic that adds autogenerate support for various PostgreSQL entity types. While standard Alembic can detect changes to SQLAlchemy models, Alembic Utils extends this capability to include:

    • Functions
    • Views
    • Materialized Views
    • Triggers
    • Policies

    When you use Alembic Utils, the alembic revision --autogenerate command will automatically detect if these entities are new, updated, or removed, and populate the upgrade and downgrade sections of your migration scripts accordingly.

  2. Autogenerate migrations for functions and views

    master

    Once your entities are defined and registered in env.py, run the standard Alembic autogenerate command. alembic_utils will detect if your functions or views are new, updated, or removed and automatically populate the upgrade and downgrade sections of the generated revision.

    alembic revision --autogenerate -m 'some message'
  3. Register replaceable entities in migrations/env.py

    master

    To enable automatic management of database entities (like functions or views) via Alembic, you must call register_entities within your migrations/env.py file. This allows alembic_utils to track changes to these entities and generate migrations automatically.

    Pass a list of entity objects to the entities argument. You can optionally use exclude_schema to prevent tracking entities within specific schemas.

    # migrations/env.py
    
    from alembic_utils.replaceable_entity import register_entities
    from app.functions import my_function
    from app.views import my_view
    
    register_entities(entities=[my_function, my_view], exclude_schema=['audit'])
  4. Register entities in migrations/env.py

    master

    To allow Alembic to detect changes in your functions and views, you must register them in your <migrations_folder>/env.py file using register_entities from alembic_utils.replaceable_entity.

    # <migrations_folder>/env.py
    from alembic_utils.replaceable_entity import register_entities
    
    from my_function import to_upper
    from my_view import first_view
    
    register_entities([to_upper, first_view])
  5. Register PostgreSQL entities in migrations/env.py

    master

    To enable autogeneration for PostgreSQL entities (like functions or views), you must register them in your Alembic env.py file using register_entities.

    1. Define your entity using the appropriate class (e.g., PGFunction).
    2. Call register_entities with a list containing your defined entities.
    # migrations/env.py
    from alembic_utils.pg_function import PGFunction
    from alembic_utils.replaceable_entity import register_entities
    
    to_upper = PGFunction(
      schema='public',
      signature='to_upper(some_text text)',
      definition="""
      RETURNS text as
      $$
        SELECT upper(some_text)
      $$
      $$ language SQL;
      """
    )
    
    register_entities([to_upper])
  6. Filter autogenerated migrations using include_name and include_object

    master

    By default, Alembic includes all schemas in the target database during an --autogenerate revision. To selectively include or exclude specific objects or schemas, you must implement and pass include_name and include_object callables to context.configure() within your env.py file.

    • Use include_name to filter by name and type (e.g., filtering schemas).
    • Use include_object to filter by the actual object instance (e.g., excluding specific entity types like PGFunction).
  7. How ReplaceableEntity and Alembic autogenerate work together

    master

    The ReplaceableEntity system integrates with Alembic's autogenerate lifecycle via a custom comparator dispatched to the schema event.

    1. Resolution Order: The system uses solve_resolution_order to ensure entities are processed in the correct dependency order (e.g., creating a function before a view that uses it).
    2. Detection: For each registered entity, the system compares the local definition against the live database state using get_required_migration_op.
      • If the entity doesn't exist in the DB: A CreateOp is generated.
      • If the entity exists but the definition differs: A ReplaceOp is generated.
      • If the entity exists and matches: No operation is generated.
    3. Cleanup: The system also checks for entities present in the database that are no longer present in your local code. If they pass the AutogenContext filters, a DropOp is generated.
    4. Filtering: The system respects standard Alembic include_name and include_object filters by mapping ReplaceableEntity attributes to the expected AutogenContext format.
  8. Configure alembic.ini for alembic_utils logging

    master

    To enable logging for alembic_utils, add alembic_utils to the keys in the [loggers] section of your alembic.ini file, and define a [logger_alembic_utils] section.

    [loggers]
    keys=root,sqlalchemy,alembic,alembic_utils
    
    [logger_alembic_utils]
    level = INFO
    handlers =
    qualname = alembic_utils
  9. Manage PostgreSQL views with PGView

    master

    The PGView class allows you to manage PostgreSQL views within Alembic migrations. It is compatible with alembic revision --autogenerate because it inherits from ReplaceableEntity.

    You can instantiate a PGView manually, parse it from a raw CREATE VIEW SQL string, or fetch existing views directly from a database session.

    from alembic_utils.pg_view import PGView
    
    # Manual instantiation
    view = PGView(schema="public", signature="my_view", definition="SELECT * FROM my_table")
    
    # Create from a SQL string
    sql = 'CREATE VIEW public.my_view AS SELECT * FROM my_table;'
    view_from_sql = PGView.from_sql(sql)
  10. Manage PostgreSQL Row Level Security with PGPolicy

    master

    The PGPolicy class allows you to manage PostgreSQL Row Level Security (RLS) policies within Alembic migrations. It is compatible with alembic revision --autogenerate, enabling automatic detection and management of policies. You can instantiate policies from raw SQL strings, fetch existing policies directly from a database connection, and generate SQL statements for creating or dropping policies.

    from alembic_utils.pg_policy import PGPolicy
    
    # Create from SQL
    policy = PGPolicy.from_sql("CREATE POLICY my_policy ON public.my_table FOR SELECT USING (user_id = current_user_id())")
    
    # Generate SQL statements
    print(policy.to_sql_statement_create())
    print(policy.to_sql_statement_drop())
    
    # Fetch from database
    # policies = PGPolicy.from_database(connection, 'public')
  11. Whitelist specific schemas in env.py

    master

    To restrict autogeneration to only specific schemas (for example, only public and bi), implement an include_name function. This function checks if the object type is schema and validates the name against your allowed list. You must also set include_schemas = True in context.configure().

    # env.py
    
    def include_name(name, type_, parent_names) -> bool:
        if type_ == "schema":
            return name in ["public", "bi"]
        return True
    
    context.configure(
        # ...
        include_schemas = True,
        include_name = include_name
    )