From 0cf4cb7a72f12678b2fb9f711c6ea586aaf6cdef Mon Sep 17 00:00:00 2001 From: rusty Date: Fri, 13 Dec 2024 19:18:00 +0400 Subject: [PATCH 1/2] `drop_unused_enums`, `detect_enum_values_changes` feature flags that can be disabled --- README.md | 33 ++++++++++++++----- alembic_postgresql_enum/compare_dispatch.py | 20 ++++++----- alembic_postgresql_enum/configuration.py | 2 ++ .../sql_commands/column_default.py | 1 - tests/sync_enum_values/test_render.py | 31 +++++++++++++++++ tests/test_enum_creation/test_drop_column.py | 31 +++++++++++++++++ 6 files changed, 100 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b8576fd..181edd1 100644 --- a/README.md +++ b/README.md @@ -45,18 +45,30 @@ alembic_postgresql_enum.set_configuration( ) ) ``` + +Default configuration: +```python +alembic_postgresql_enum.Config( + add_type_ignore = False, + include_name = lambda _: True, + drop_unused_enums = True, + detect_enum_values_changes = True, +) +``` + ## Features * [Creation of enums](#creation-of-enum) * [Deletion of unreferenced enums](#deletion-of-unreferenced-enum) -* [Creation of new enum values](#creation-of-new-enum-values) -* [Deletion of enums values](#deletion-of-enums-values) -* [Renaming of enum values](#rename-enum-value) +* [Detection of enum values changes](#detection-of-enum-values-changes) + * [Creation of new enum values](#creation-of-new-enum-values) + * [Deletion of enums values](#deletion-of-enums-values) + * [Renaming of enum values](#rename-enum-value) * [Omitting managing enums](#omitting-managing-enums) -### Creation of enum +## Creation of enum -#### When table is created +### When table is created ```python class MyEnum(enum.Enum): @@ -93,7 +105,7 @@ def downgrade(): # ### end Alembic commands ### ``` -#### When column is added +### When column is added ```python class MyEnum(enum.Enum): one = 1 @@ -125,7 +137,7 @@ def downgrade(): # ### end Alembic commands ### ``` -### Deletion of unreferenced enum +## Deletion of unreferenced enum If enum is defined in postgres schema, but its mentions removed from code - It will be automatically removed ```python class ExampleTable(BaseModel): @@ -148,6 +160,10 @@ def downgrade(): # ### end Alembic commands ### ``` +## Detection of enum values changes + +***Can be disabled with `detect_enum_values_changes` configuration flag turned off*** + ### Creation of new enum values If new enum value is defined sync_enum_values function call will be added to migration to account for it @@ -221,6 +237,7 @@ def downgrade(): # ### end Alembic commands ### ``` + ### Rename enum value In this case you must manually edit migration @@ -286,7 +303,7 @@ Do not forget to switch places old and new values for downgrade All defaults in postgres will be renamed automatically as well -### Omitting managing enums +## Omitting managing enums If configured `include_name` function returns `False` given enum will be not managed. ```python diff --git a/alembic_postgresql_enum/compare_dispatch.py b/alembic_postgresql_enum/compare_dispatch.py index 0099e9b..82d6337 100644 --- a/alembic_postgresql_enum/compare_dispatch.py +++ b/alembic_postgresql_enum/compare_dispatch.py @@ -76,12 +76,14 @@ def compare_enums( create_new_enums(definitions, declarations.enum_values, schema, upgrade_ops) - drop_unused_enums(definitions, declarations.enum_values, schema, upgrade_ops) - - sync_changed_enums( - definitions, - declarations.enum_values, - declarations.enum_table_references, - schema, - upgrade_ops, - ) + if configuration.drop_unused_enums: + drop_unused_enums(definitions, declarations.enum_values, schema, upgrade_ops) + + if configuration.detect_enum_values_changes: + sync_changed_enums( + definitions, + declarations.enum_values, + declarations.enum_table_references, + schema, + upgrade_ops, + ) diff --git a/alembic_postgresql_enum/configuration.py b/alembic_postgresql_enum/configuration.py index 4d73f78..63c8b7f 100644 --- a/alembic_postgresql_enum/configuration.py +++ b/alembic_postgresql_enum/configuration.py @@ -6,6 +6,8 @@ class Config: add_type_ignore: bool = False include_name: Callable[[str], bool] = lambda _: True + drop_unused_enums: bool = True + detect_enum_values_changes: bool = True _config = Config() diff --git a/alembic_postgresql_enum/sql_commands/column_default.py b/alembic_postgresql_enum/sql_commands/column_default.py index 0d21175..f191cc9 100644 --- a/alembic_postgresql_enum/sql_commands/column_default.py +++ b/alembic_postgresql_enum/sql_commands/column_default.py @@ -66,7 +66,6 @@ def rename_default_if_required( return column_default_value if default_value.endswith("[]"): - # remove old type postfix column_default_value = default_value[: default_value.find("::")] diff --git a/tests/sync_enum_values/test_render.py b/tests/sync_enum_values/test_render.py index c85eb8c..6b2d36a 100644 --- a/tests/sync_enum_values/test_render.py +++ b/tests/sync_enum_values/test_render.py @@ -26,6 +26,37 @@ from tests.utils.migration_context import create_migration_context +class TestDisabledSyncEnumValues(CompareAndRunTestCase): + """Check that enum variants are updated when new variant is added""" + + config = Config(detect_enum_values_changes=False) + + old_enum_variants = ["active", "passive"] + new_enum_variants = old_enum_variants + ["banned"] + + def get_database_schema(self) -> MetaData: + schema = get_schema_with_enum_variants(self.old_enum_variants) + return schema + + def get_target_schema(self) -> MetaData: + schema = get_schema_with_enum_variants(self.new_enum_variants) + return schema + + def get_expected_upgrade(self) -> str: + return f""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + """ + + def get_expected_downgrade(self) -> str: + return f""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + """ + + class TestAddNewEnumValueRender(CompareAndRunTestCase): """Check that enum variants are updated when new variant is added""" diff --git a/tests/test_enum_creation/test_drop_column.py b/tests/test_enum_creation/test_drop_column.py index e188247..849af79 100644 --- a/tests/test_enum_creation/test_drop_column.py +++ b/tests/test_enum_creation/test_drop_column.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from alembic_postgresql_enum import Config from alembic_postgresql_enum.operations import DropEnumOp from tests.base.run_migration_test_abc import CompareAndRunTestCase @@ -63,6 +64,36 @@ def get_expected_downgrade(self) -> str: """ +class TestDisabledDeleteEnumAfterDropColumn(CompareAndRunTestCase): + """Check that library correctly removes unused enum after drop_column""" + + config = Config( + drop_unused_enums=False, + ) + enum_variants_to_delete = ["active", "passive"] + + def get_database_schema(self) -> MetaData: + return get_schema_with_enum_variants(self.enum_variants_to_delete) + + def get_target_schema(self) -> MetaData: + return get_schema_without_enum() + + def get_expected_upgrade(self) -> str: + return f""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('{USER_TABLE_NAME}', '{USER_STATUS_COLUMN_NAME}') + # ### end Alembic commands ### + """ + + def get_expected_downgrade(self) -> str: + # For some reason alembic decided to add redundant autoincrement=False on downgrade + return f""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('{USER_TABLE_NAME}', sa.Column('{USER_STATUS_COLUMN_NAME}', postgresql.ENUM({', '.join(map(repr, self.enum_variants_to_delete))}, name='{USER_STATUS_ENUM_NAME}', create_type=False), autoincrement=False, nullable=True)) + # ### end Alembic commands ### + """ + + class TestDeleteEnumAfterDropColumnWithArray(CompareAndRunTestCase): """Check that library correctly removes unused enum after drop_column. Enum is used in ARRAY""" From a58930bad63b70b2807f0dd5a07efcf2f918fae2 Mon Sep 17 00:00:00 2001 From: rusty Date: Sat, 28 Dec 2024 23:24:44 +0400 Subject: [PATCH 2/2] Move Configuration section to the bottom of README.md --- README.md | 58 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 181edd1..7773200 100644 --- a/README.md +++ b/README.md @@ -30,32 +30,6 @@ import alembic_postgresql_enum To the top of your migrations/env.py file. -## Configuration - -You can configure this extension to disable parts of it, or to enable some feature flags - -To do so you need to call set_configuration function after the import: - -```python -import alembic_postgresql_enum - -alembic_postgresql_enum.set_configuration( - alembic_postgresql_enum.Config( - add_type_ignore=True, - ) -) -``` - -Default configuration: -```python -alembic_postgresql_enum.Config( - add_type_ignore = False, - include_name = lambda _: True, - drop_unused_enums = True, - detect_enum_values_changes = True, -) -``` - ## Features * [Creation of enums](#creation-of-enum) @@ -319,4 +293,34 @@ alembic_postgresql_enum.set_configuration( ) ``` -Feature is similar to [sqlalchemy feature for tables](https://alembic.sqlalchemy.org/en/latest/autogenerate.html#omitting-table-names-from-the-autogenerate-process) \ No newline at end of file +Feature is similar to [sqlalchemy feature for tables](https://alembic.sqlalchemy.org/en/latest/autogenerate.html#omitting-table-names-from-the-autogenerate-process) + +## Configuration + +You can configure this extension to disable parts of it, or to enable some feature flags + +To do so you need to call set_configuration function after the import: + +```python +import alembic_postgresql_enum + +alembic_postgresql_enum.set_configuration( + alembic_postgresql_enum.Config( + add_type_ignore=True, + ) +) +``` + +Available options: + +- `add_type_ignore` (`False` by default) - flag that can be turned on +to add `# type: ignore[attr-defined]` at the end of generated `op.sync_enum_values` calls. +This is helpful if you are using type checker such as `mypy`. +`type: ignore` is needed because there is no way to add new function to an existing alembic's `op`. + +- `include_name` (`lambda _: True` bby default) - it adds ability to ignore process enum by name in similar way alembic allows to define `include_name` function. +This property accepts function that takes enum name and returns whether it should be processed. + +- `drop_unused_enums` (`True` by default) - feature flag that can be turned off to disable clean up of undeclared enums + +- `detect_enum_values_changes` (`True` by default) - feature flag that can be turned off to disable generation of `op.sync_enum_values`.