Skip to content

Commit b76d33a

Browse files
zzzeekGerrit Code Review
authored andcommitted
Merge "organize into a "plugin" directory structure" into main
2 parents 9b4e7da + e532a7e commit b76d33a

30 files changed

Lines changed: 3001 additions & 957 deletions

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repos:
77
- id: black
88

99
- repo: https://github.com/sqlalchemyorg/zimports
10-
rev: v0.6.2
10+
rev: v0.7.0
1111
hooks:
1212
- id: zimports
1313
args:

alembic/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from . import context
22
from . import op
3+
from .runtime import plugins
34

4-
__version__ = "1.17.3"
5+
6+
__version__ = "1.18.0"

alembic/autogenerate/api.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import contextlib
4+
import logging
45
from typing import Any
56
from typing import Dict
67
from typing import Iterator
@@ -17,11 +18,9 @@
1718
from . import render
1819
from .. import util
1920
from ..operations import ops
21+
from ..runtime.plugins import Plugin
2022
from ..util import sqla_compat
2123

22-
"""Provide the 'autogenerate' feature which can produce migration operations
23-
automatically."""
24-
2524
if TYPE_CHECKING:
2625
from sqlalchemy.engine import Connection
2726
from sqlalchemy.engine import Dialect
@@ -42,6 +41,10 @@
4241
from ..script.base import Script
4342
from ..script.base import ScriptDirectory
4443
from ..script.revision import _GetRevArg
44+
from ..util import PriorityDispatcher
45+
46+
47+
log = logging.getLogger(__name__)
4548

4649

4750
def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any:
@@ -304,7 +307,7 @@ class AutogenContext:
304307
305308
"""
306309

307-
dialect: Optional[Dialect] = None
310+
dialect: Dialect
308311
"""The :class:`~sqlalchemy.engine.Dialect` object currently in use.
309312
310313
This is normally obtained from the
@@ -326,9 +329,11 @@ class AutogenContext:
326329
327330
"""
328331

329-
migration_context: MigrationContext = None # type: ignore[assignment]
332+
migration_context: MigrationContext
330333
"""The :class:`.MigrationContext` established by the ``env.py`` script."""
331334

335+
comparators: PriorityDispatcher
336+
332337
def __init__(
333338
self,
334339
migration_context: MigrationContext,
@@ -346,6 +351,19 @@ def __init__(
346351
"the database for schema information"
347352
)
348353

354+
# branch off from the "global" comparators. This collection
355+
# is empty in Alembic except that it is populated by third party
356+
# extensions that don't use the plugin system. so we will build
357+
# off of whatever is in there.
358+
if autogenerate:
359+
self.comparators = compare.comparators.branch()
360+
Plugin.populate_autogenerate_priority_dispatch(
361+
self.comparators,
362+
include_plugins=migration_context.opts.get(
363+
"autogenerate_plugins", ["alembic.autogenerate.*"]
364+
),
365+
)
366+
349367
if opts is None:
350368
opts = migration_context.opts
351369

@@ -380,9 +398,8 @@ def __init__(
380398
self._name_filters = name_filters
381399

382400
self.migration_context = migration_context
383-
if self.migration_context is not None:
384-
self.connection = self.migration_context.bind
385-
self.dialect = self.migration_context.dialect
401+
self.connection = self.migration_context.bind
402+
self.dialect = self.migration_context.dialect
386403

387404
self.imports = set()
388405
self.opts: Dict[str, Any] = opts
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import TYPE_CHECKING
5+
6+
from . import comments
7+
from . import constraints
8+
from . import schema
9+
from . import server_defaults
10+
from . import tables
11+
from . import types
12+
from ... import util
13+
from ...runtime.plugins import Plugin
14+
15+
if TYPE_CHECKING:
16+
from ..api import AutogenContext
17+
from ...operations.ops import MigrationScript
18+
from ...operations.ops import UpgradeOps
19+
20+
21+
log = logging.getLogger(__name__)
22+
23+
comparators = util.PriorityDispatcher()
24+
"""global registry which alembic keeps empty, but copies when creating
25+
a new AutogenContext.
26+
27+
This is to support a variety of third party plugins that hook their autogen
28+
functionality onto this collection.
29+
30+
"""
31+
32+
33+
def _populate_migration_script(
34+
autogen_context: AutogenContext, migration_script: MigrationScript
35+
) -> None:
36+
upgrade_ops = migration_script.upgrade_ops_list[-1]
37+
downgrade_ops = migration_script.downgrade_ops_list[-1]
38+
39+
_produce_net_changes(autogen_context, upgrade_ops)
40+
upgrade_ops.reverse_into(downgrade_ops)
41+
42+
43+
def _produce_net_changes(
44+
autogen_context: AutogenContext, upgrade_ops: UpgradeOps
45+
) -> None:
46+
assert autogen_context.dialect is not None
47+
48+
autogen_context.comparators.dispatch(
49+
"autogenerate", qualifier=autogen_context.dialect.name
50+
)(autogen_context, upgrade_ops)
51+
52+
53+
Plugin.setup_plugin_from_module(schema, "alembic.autogenerate.schemas")
54+
Plugin.setup_plugin_from_module(tables, "alembic.autogenerate.tables")
55+
Plugin.setup_plugin_from_module(types, "alembic.autogenerate.types")
56+
Plugin.setup_plugin_from_module(
57+
constraints, "alembic.autogenerate.constraints"
58+
)
59+
Plugin.setup_plugin_from_module(
60+
server_defaults, "alembic.autogenerate.defaults"
61+
)
62+
Plugin.setup_plugin_from_module(comments, "alembic.autogenerate.comments")
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import Any
5+
from typing import Optional
6+
from typing import TYPE_CHECKING
7+
from typing import Union
8+
9+
from ...operations import ops
10+
from ...util import PriorityDispatchResult
11+
12+
if TYPE_CHECKING:
13+
14+
from sqlalchemy.sql.elements import quoted_name
15+
from sqlalchemy.sql.schema import Column
16+
from sqlalchemy.sql.schema import Table
17+
18+
from ..api import AutogenContext
19+
from ...operations.ops import AlterColumnOp
20+
from ...operations.ops import ModifyTableOps
21+
from ...runtime.plugins import Plugin
22+
23+
log = logging.getLogger(__name__)
24+
25+
26+
def _compare_column_comment(
27+
autogen_context: AutogenContext,
28+
alter_column_op: AlterColumnOp,
29+
schema: Optional[str],
30+
tname: Union[quoted_name, str],
31+
cname: quoted_name,
32+
conn_col: Column[Any],
33+
metadata_col: Column[Any],
34+
) -> PriorityDispatchResult:
35+
assert autogen_context.dialect is not None
36+
if not autogen_context.dialect.supports_comments:
37+
return PriorityDispatchResult.CONTINUE
38+
39+
metadata_comment = metadata_col.comment
40+
conn_col_comment = conn_col.comment
41+
if conn_col_comment is None and metadata_comment is None:
42+
return PriorityDispatchResult.CONTINUE
43+
44+
alter_column_op.existing_comment = conn_col_comment
45+
46+
if conn_col_comment != metadata_comment:
47+
alter_column_op.modify_comment = metadata_comment
48+
log.info("Detected column comment '%s.%s'", tname, cname)
49+
50+
return PriorityDispatchResult.STOP
51+
else:
52+
return PriorityDispatchResult.CONTINUE
53+
54+
55+
def _compare_table_comment(
56+
autogen_context: AutogenContext,
57+
modify_table_ops: ModifyTableOps,
58+
schema: Optional[str],
59+
tname: Union[quoted_name, str],
60+
conn_table: Optional[Table],
61+
metadata_table: Optional[Table],
62+
) -> PriorityDispatchResult:
63+
assert autogen_context.dialect is not None
64+
if not autogen_context.dialect.supports_comments:
65+
return PriorityDispatchResult.CONTINUE
66+
67+
# if we're doing CREATE TABLE, comments will be created inline
68+
# with the create_table op.
69+
if conn_table is None or metadata_table is None:
70+
return PriorityDispatchResult.CONTINUE
71+
72+
if conn_table.comment is None and metadata_table.comment is None:
73+
return PriorityDispatchResult.CONTINUE
74+
75+
if metadata_table.comment is None and conn_table.comment is not None:
76+
modify_table_ops.ops.append(
77+
ops.DropTableCommentOp(
78+
tname, existing_comment=conn_table.comment, schema=schema
79+
)
80+
)
81+
return PriorityDispatchResult.STOP
82+
elif metadata_table.comment != conn_table.comment:
83+
modify_table_ops.ops.append(
84+
ops.CreateTableCommentOp(
85+
tname,
86+
metadata_table.comment,
87+
existing_comment=conn_table.comment,
88+
schema=schema,
89+
)
90+
)
91+
return PriorityDispatchResult.STOP
92+
93+
return PriorityDispatchResult.CONTINUE
94+
95+
96+
def setup(plugin: Plugin) -> None:
97+
plugin.add_autogenerate_comparator(
98+
_compare_column_comment,
99+
"column",
100+
"comments",
101+
)
102+
plugin.add_autogenerate_comparator(
103+
_compare_table_comment,
104+
"table",
105+
"comments",
106+
)

0 commit comments

Comments
 (0)