alembic 1.12.0__py3-none-any.whl → 1.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alembic/__init__.py +1 -3
- alembic/autogenerate/api.py +14 -6
- alembic/autogenerate/compare.py +129 -195
- alembic/autogenerate/render.py +42 -32
- alembic/autogenerate/rewriter.py +19 -19
- alembic/command.py +11 -9
- alembic/config.py +1 -1
- alembic/context.pyi +12 -5
- alembic/ddl/_autogen.py +323 -0
- alembic/ddl/impl.py +167 -41
- alembic/ddl/mssql.py +1 -4
- alembic/ddl/mysql.py +4 -3
- alembic/ddl/postgresql.py +157 -70
- alembic/op.pyi +9 -11
- alembic/operations/__init__.py +2 -0
- alembic/operations/base.py +10 -11
- alembic/operations/ops.py +14 -14
- alembic/operations/toimpl.py +5 -5
- alembic/runtime/environment.py +7 -5
- alembic/runtime/migration.py +4 -4
- alembic/script/base.py +25 -17
- alembic/script/revision.py +30 -25
- alembic/templates/async/alembic.ini.mako +3 -3
- alembic/templates/generic/alembic.ini.mako +3 -3
- alembic/templates/multidb/alembic.ini.mako +3 -3
- alembic/testing/requirements.py +12 -4
- alembic/testing/schemacompare.py +9 -0
- alembic/testing/suite/test_autogen_identity.py +23 -38
- alembic/util/compat.py +0 -1
- alembic/util/sqla_compat.py +58 -30
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/METADATA +8 -6
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/RECORD +36 -35
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/WHEEL +1 -1
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/LICENSE +0 -0
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/entry_points.txt +0 -0
- {alembic-1.12.0.dist-info → alembic-1.13.0.dist-info}/top_level.txt +0 -0
alembic/runtime/migration.py
CHANGED
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
|
|
40
40
|
from sqlalchemy.engine.base import Connection
|
41
41
|
from sqlalchemy.engine.base import Transaction
|
42
42
|
from sqlalchemy.engine.mock import MockConnection
|
43
|
-
from sqlalchemy.sql
|
43
|
+
from sqlalchemy.sql import Executable
|
44
44
|
|
45
45
|
from .environment import EnvironmentContext
|
46
46
|
from ..config import Config
|
@@ -651,7 +651,7 @@ class MigrationContext:
|
|
651
651
|
|
652
652
|
def execute(
|
653
653
|
self,
|
654
|
-
sql: Union[
|
654
|
+
sql: Union[Executable, str],
|
655
655
|
execution_options: Optional[dict] = None,
|
656
656
|
) -> None:
|
657
657
|
"""Execute a SQL construct or string statement.
|
@@ -1157,7 +1157,7 @@ class RevisionStep(MigrationStep):
|
|
1157
1157
|
self.to_revisions[0],
|
1158
1158
|
)
|
1159
1159
|
|
1160
|
-
def _unmerge_to_revisions(self, heads:
|
1160
|
+
def _unmerge_to_revisions(self, heads: Set[str]) -> Tuple[str, ...]:
|
1161
1161
|
other_heads = set(heads).difference([self.revision.revision])
|
1162
1162
|
if other_heads:
|
1163
1163
|
ancestors = {
|
@@ -1171,7 +1171,7 @@ class RevisionStep(MigrationStep):
|
|
1171
1171
|
return self.to_revisions
|
1172
1172
|
|
1173
1173
|
def unmerge_branch_idents(
|
1174
|
-
self, heads:
|
1174
|
+
self, heads: Set[str]
|
1175
1175
|
) -> Tuple[str, str, Tuple[str, ...]]:
|
1176
1176
|
to_revisions = self._unmerge_to_revisions(heads)
|
1177
1177
|
|
alembic/script/base.py
CHANGED
@@ -23,9 +23,11 @@ from . import revision
|
|
23
23
|
from . import write_hooks
|
24
24
|
from .. import util
|
25
25
|
from ..runtime import migration
|
26
|
+
from ..util import compat
|
26
27
|
from ..util import not_none
|
27
28
|
|
28
29
|
if TYPE_CHECKING:
|
30
|
+
from .revision import _GetRevArg
|
29
31
|
from .revision import _RevIdType
|
30
32
|
from .revision import Revision
|
31
33
|
from ..config import Config
|
@@ -34,9 +36,14 @@ if TYPE_CHECKING:
|
|
34
36
|
from ..runtime.migration import StampStep
|
35
37
|
|
36
38
|
try:
|
37
|
-
|
39
|
+
if compat.py39:
|
40
|
+
from zoneinfo import ZoneInfo
|
41
|
+
from zoneinfo import ZoneInfoNotFoundError
|
42
|
+
else:
|
43
|
+
from backports.zoneinfo import ZoneInfo # type: ignore[import-not-found,no-redef] # noqa: E501
|
44
|
+
from backports.zoneinfo import ZoneInfoNotFoundError # type: ignore[import-not-found,no-redef] # noqa: E501
|
38
45
|
except ImportError:
|
39
|
-
|
46
|
+
ZoneInfo = None # type: ignore[assignment, misc]
|
40
47
|
|
41
48
|
_sourceless_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)(c|o)?$")
|
42
49
|
_only_source_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)$")
|
@@ -296,7 +303,7 @@ class ScriptDirectory:
|
|
296
303
|
):
|
297
304
|
yield cast(Script, rev)
|
298
305
|
|
299
|
-
def get_revisions(self, id_:
|
306
|
+
def get_revisions(self, id_: _GetRevArg) -> Tuple[Optional[Script], ...]:
|
300
307
|
"""Return the :class:`.Script` instance with the given rev identifier,
|
301
308
|
symbolic name, or sequence of identifiers.
|
302
309
|
|
@@ -603,23 +610,26 @@ class ScriptDirectory:
|
|
603
610
|
|
604
611
|
def _generate_create_date(self) -> datetime.datetime:
|
605
612
|
if self.timezone is not None:
|
606
|
-
if
|
613
|
+
if ZoneInfo is None:
|
607
614
|
raise util.CommandError(
|
608
|
-
"
|
609
|
-
"
|
615
|
+
"Python >= 3.9 is required for timezone support or"
|
616
|
+
"the 'backports.zoneinfo' package must be installed."
|
610
617
|
)
|
611
618
|
# First, assume correct capitalization
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
tzinfo =
|
619
|
+
try:
|
620
|
+
tzinfo = ZoneInfo(self.timezone)
|
621
|
+
except ZoneInfoNotFoundError:
|
622
|
+
tzinfo = None
|
616
623
|
if tzinfo is None:
|
617
|
-
|
618
|
-
|
619
|
-
|
624
|
+
try:
|
625
|
+
tzinfo = ZoneInfo(self.timezone.upper())
|
626
|
+
except ZoneInfoNotFoundError:
|
627
|
+
raise util.CommandError(
|
628
|
+
"Can't locate timezone: %s" % self.timezone
|
629
|
+
) from None
|
620
630
|
create_date = (
|
621
631
|
datetime.datetime.utcnow()
|
622
|
-
.replace(tzinfo=
|
632
|
+
.replace(tzinfo=datetime.timezone.utc)
|
623
633
|
.astimezone(tzinfo)
|
624
634
|
)
|
625
635
|
else:
|
@@ -630,8 +640,7 @@ class ScriptDirectory:
|
|
630
640
|
self,
|
631
641
|
revid: str,
|
632
642
|
message: Optional[str],
|
633
|
-
head: Optional[
|
634
|
-
refresh: bool = False,
|
643
|
+
head: Optional[_RevIdType] = None,
|
635
644
|
splice: Optional[bool] = False,
|
636
645
|
branch_labels: Optional[_RevIdType] = None,
|
637
646
|
version_path: Optional[str] = None,
|
@@ -653,7 +662,6 @@ class ScriptDirectory:
|
|
653
662
|
:param splice: if True, allow the "head" version to not be an
|
654
663
|
actual head; otherwise, the selected head must be a head
|
655
664
|
(e.g. endpoint) revision.
|
656
|
-
:param refresh: deprecated.
|
657
665
|
|
658
666
|
"""
|
659
667
|
if head is None:
|
alembic/script/revision.py
CHANGED
@@ -29,13 +29,19 @@ from ..util import not_none
|
|
29
29
|
if TYPE_CHECKING:
|
30
30
|
from typing import Literal
|
31
31
|
|
32
|
-
_RevIdType = Union[str,
|
32
|
+
_RevIdType = Union[str, List[str], Tuple[str, ...]]
|
33
|
+
_GetRevArg = Union[
|
34
|
+
str,
|
35
|
+
Iterable[Optional[str]],
|
36
|
+
Iterable[str],
|
37
|
+
]
|
33
38
|
_RevisionIdentifierType = Union[str, Tuple[str, ...], None]
|
34
39
|
_RevisionOrStr = Union["Revision", str]
|
35
40
|
_RevisionOrBase = Union["Revision", "Literal['base']"]
|
36
41
|
_InterimRevisionMapType = Dict[str, "Revision"]
|
37
42
|
_RevisionMapType = Dict[Union[None, str, Tuple[()]], Optional["Revision"]]
|
38
|
-
_T = TypeVar("_T"
|
43
|
+
_T = TypeVar("_T")
|
44
|
+
_TR = TypeVar("_TR", bound=Optional[_RevisionOrStr])
|
39
45
|
|
40
46
|
_relative_destination = re.compile(r"(?:(.+?)@)?(\w+)?((?:\+|-)\d+)")
|
41
47
|
_revision_illegal_chars = ["@", "-", "+"]
|
@@ -501,7 +507,7 @@ class RevisionMap:
|
|
501
507
|
return self.filter_for_lineage(self.bases, identifier)
|
502
508
|
|
503
509
|
def get_revisions(
|
504
|
-
self, id_:
|
510
|
+
self, id_: Optional[_GetRevArg]
|
505
511
|
) -> Tuple[Optional[_RevisionOrBase], ...]:
|
506
512
|
"""Return the :class:`.Revision` instances with the given rev id
|
507
513
|
or identifiers.
|
@@ -523,9 +529,7 @@ class RevisionMap:
|
|
523
529
|
if isinstance(id_, (list, tuple, set, frozenset)):
|
524
530
|
return sum([self.get_revisions(id_elem) for id_elem in id_], ())
|
525
531
|
else:
|
526
|
-
resolved_id, branch_label = self._resolve_revision_number(
|
527
|
-
id_ # type:ignore [arg-type]
|
528
|
-
)
|
532
|
+
resolved_id, branch_label = self._resolve_revision_number(id_)
|
529
533
|
if len(resolved_id) == 1:
|
530
534
|
try:
|
531
535
|
rint = int(resolved_id[0])
|
@@ -590,7 +594,7 @@ class RevisionMap:
|
|
590
594
|
|
591
595
|
def _revision_for_ident(
|
592
596
|
self,
|
593
|
-
resolved_id: Union[str, Tuple[()]],
|
597
|
+
resolved_id: Union[str, Tuple[()], None],
|
594
598
|
check_branch: Optional[str] = None,
|
595
599
|
) -> Optional[Revision]:
|
596
600
|
branch_rev: Optional[Revision]
|
@@ -669,10 +673,10 @@ class RevisionMap:
|
|
669
673
|
|
670
674
|
def filter_for_lineage(
|
671
675
|
self,
|
672
|
-
targets: Iterable[
|
676
|
+
targets: Iterable[_TR],
|
673
677
|
check_against: Optional[str],
|
674
678
|
include_dependencies: bool = False,
|
675
|
-
) -> Tuple[
|
679
|
+
) -> Tuple[_TR, ...]:
|
676
680
|
id_, branch_label = self._resolve_revision_number(check_against)
|
677
681
|
|
678
682
|
shares = []
|
@@ -691,7 +695,7 @@ class RevisionMap:
|
|
691
695
|
|
692
696
|
def _shares_lineage(
|
693
697
|
self,
|
694
|
-
target: _RevisionOrStr,
|
698
|
+
target: Optional[_RevisionOrStr],
|
695
699
|
test_against_revs: Sequence[_RevisionOrStr],
|
696
700
|
include_dependencies: bool = False,
|
697
701
|
) -> bool:
|
@@ -728,7 +732,7 @@ class RevisionMap:
|
|
728
732
|
)
|
729
733
|
|
730
734
|
def _resolve_revision_number(
|
731
|
-
self, id_: Optional[
|
735
|
+
self, id_: Optional[_GetRevArg]
|
732
736
|
) -> Tuple[Tuple[str, ...], Optional[str]]:
|
733
737
|
branch_label: Optional[str]
|
734
738
|
if isinstance(id_, str) and "@" in id_:
|
@@ -1080,13 +1084,13 @@ class RevisionMap:
|
|
1080
1084
|
) -> Tuple[Optional[str], Optional[_RevisionOrBase]]:
|
1081
1085
|
"""
|
1082
1086
|
Parse downgrade command syntax :target to retrieve the target revision
|
1083
|
-
and branch label (if any) given the :
|
1087
|
+
and branch label (if any) given the :current_revisions stamp of the
|
1084
1088
|
database.
|
1085
1089
|
|
1086
1090
|
Returns a tuple (branch_label, target_revision) where branch_label
|
1087
1091
|
is a string from the command specifying the branch to consider (or
|
1088
1092
|
None if no branch given), and target_revision is a Revision object
|
1089
|
-
which the command refers to.
|
1093
|
+
which the command refers to. target_revisions is None if the command
|
1090
1094
|
refers to 'base'. The target may be specified in absolute form, or
|
1091
1095
|
relative to :current_revisions.
|
1092
1096
|
"""
|
@@ -1129,7 +1133,7 @@ class RevisionMap:
|
|
1129
1133
|
if not symbol_list:
|
1130
1134
|
# check the case where there are multiple branches
|
1131
1135
|
# but there is currently a single heads, since all
|
1132
|
-
# other branch heads are
|
1136
|
+
# other branch heads are dependent of the current
|
1133
1137
|
# single heads.
|
1134
1138
|
all_current = cast(
|
1135
1139
|
Set[Revision], self._get_all_current(cr_tuple)
|
@@ -1196,7 +1200,7 @@ class RevisionMap:
|
|
1196
1200
|
) -> Tuple[Optional[_RevisionOrBase], ...]:
|
1197
1201
|
"""
|
1198
1202
|
Parse upgrade command syntax :target to retrieve the target revision
|
1199
|
-
and given the :
|
1203
|
+
and given the :current_revisions stamp of the database.
|
1200
1204
|
|
1201
1205
|
Returns a tuple of Revision objects which should be iterated/upgraded
|
1202
1206
|
to. The target may be specified in absolute form, or relative to
|
@@ -1211,7 +1215,7 @@ class RevisionMap:
|
|
1211
1215
|
# No relative destination, target is absolute.
|
1212
1216
|
return self.get_revisions(target)
|
1213
1217
|
|
1214
|
-
current_revisions_tup: Union[str,
|
1218
|
+
current_revisions_tup: Union[str, Tuple[Optional[str], ...], None]
|
1215
1219
|
current_revisions_tup = util.to_tuple(current_revisions)
|
1216
1220
|
|
1217
1221
|
branch_label, symbol, relative_str = match.groups()
|
@@ -1224,7 +1228,8 @@ class RevisionMap:
|
|
1224
1228
|
start_revs = current_revisions_tup
|
1225
1229
|
if branch_label:
|
1226
1230
|
start_revs = self.filter_for_lineage(
|
1227
|
-
self.get_revisions(current_revisions_tup),
|
1231
|
+
self.get_revisions(current_revisions_tup), # type: ignore[arg-type] # noqa: E501
|
1232
|
+
branch_label,
|
1228
1233
|
)
|
1229
1234
|
if not start_revs:
|
1230
1235
|
# The requested branch is not a head, so we need to
|
@@ -1577,8 +1582,8 @@ class Revision:
|
|
1577
1582
|
|
1578
1583
|
self.verify_rev_id(revision)
|
1579
1584
|
self.revision = revision
|
1580
|
-
self.down_revision = tuple_rev_as_scalar(down_revision)
|
1581
|
-
self.dependencies = tuple_rev_as_scalar(dependencies)
|
1585
|
+
self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision))
|
1586
|
+
self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies))
|
1582
1587
|
self._orig_branch_labels = util.to_tuple(branch_labels, default=())
|
1583
1588
|
self.branch_labels = set(self._orig_branch_labels)
|
1584
1589
|
|
@@ -1676,20 +1681,20 @@ class Revision:
|
|
1676
1681
|
|
1677
1682
|
|
1678
1683
|
@overload
|
1679
|
-
def tuple_rev_as_scalar(
|
1680
|
-
rev: Optional[Sequence[str]],
|
1681
|
-
) -> Optional[Union[str, Sequence[str]]]:
|
1684
|
+
def tuple_rev_as_scalar(rev: None) -> None:
|
1682
1685
|
...
|
1683
1686
|
|
1684
1687
|
|
1685
1688
|
@overload
|
1686
1689
|
def tuple_rev_as_scalar(
|
1687
|
-
rev:
|
1688
|
-
) ->
|
1690
|
+
rev: Union[Tuple[_T, ...], List[_T]]
|
1691
|
+
) -> Union[_T, Tuple[_T, ...], List[_T]]:
|
1689
1692
|
...
|
1690
1693
|
|
1691
1694
|
|
1692
|
-
def tuple_rev_as_scalar(
|
1695
|
+
def tuple_rev_as_scalar(
|
1696
|
+
rev: Optional[Sequence[_T]],
|
1697
|
+
) -> Union[_T, Sequence[_T], None]:
|
1693
1698
|
if not rev:
|
1694
1699
|
return None
|
1695
1700
|
elif len(rev) == 1:
|
@@ -14,9 +14,9 @@ prepend_sys_path = .
|
|
14
14
|
|
15
15
|
# timezone to use when rendering the date within the migration file
|
16
16
|
# as well as the filename.
|
17
|
-
# If specified, requires the python
|
18
|
-
# installed by adding `alembic[tz]` to the pip requirements
|
19
|
-
# string value is passed to
|
17
|
+
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
18
|
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
19
|
+
# string value is passed to ZoneInfo()
|
20
20
|
# leave blank for localtime
|
21
21
|
# timezone =
|
22
22
|
|
@@ -16,9 +16,9 @@ prepend_sys_path = .
|
|
16
16
|
|
17
17
|
# timezone to use when rendering the date within the migration file
|
18
18
|
# as well as the filename.
|
19
|
-
# If specified, requires the python
|
20
|
-
# installed by adding `alembic[tz]` to the pip requirements
|
21
|
-
# string value is passed to
|
19
|
+
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
20
|
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
21
|
+
# string value is passed to ZoneInfo()
|
22
22
|
# leave blank for localtime
|
23
23
|
# timezone =
|
24
24
|
|
@@ -16,9 +16,9 @@ prepend_sys_path = .
|
|
16
16
|
|
17
17
|
# timezone to use when rendering the date within the migration file
|
18
18
|
# as well as the filename.
|
19
|
-
# If specified, requires the python
|
20
|
-
# installed by adding `alembic[tz]` to the pip requirements
|
21
|
-
# string value is passed to
|
19
|
+
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
20
|
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
21
|
+
# string value is passed to ZoneInfo()
|
22
22
|
# leave blank for localtime
|
23
23
|
# timezone =
|
24
24
|
|
alembic/testing/requirements.py
CHANGED
@@ -95,6 +95,18 @@ class SuiteRequirements(Requirements):
|
|
95
95
|
"SQLAlchemy 2.x test",
|
96
96
|
)
|
97
97
|
|
98
|
+
@property
|
99
|
+
def asyncio(self):
|
100
|
+
def go(config):
|
101
|
+
try:
|
102
|
+
import greenlet # noqa: F401
|
103
|
+
except ImportError:
|
104
|
+
return False
|
105
|
+
else:
|
106
|
+
return True
|
107
|
+
|
108
|
+
return self.sqlalchemy_14 + exclusions.only_if(go)
|
109
|
+
|
98
110
|
@property
|
99
111
|
def comments(self):
|
100
112
|
return exclusions.only_if(
|
@@ -196,7 +208,3 @@ class SuiteRequirements(Requirements):
|
|
196
208
|
return exclusions.only_if(
|
197
209
|
exclusions.BooleanPredicate(sqla_compat.has_identity)
|
198
210
|
)
|
199
|
-
|
200
|
-
@property
|
201
|
-
def supports_identity_on_null(self):
|
202
|
-
return exclusions.closed()
|
alembic/testing/schemacompare.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from itertools import zip_longest
|
2
2
|
|
3
3
|
from sqlalchemy import schema
|
4
|
+
from sqlalchemy.sql.elements import ClauseList
|
4
5
|
|
5
6
|
|
6
7
|
class CompareTable:
|
@@ -60,6 +61,14 @@ class CompareIndex:
|
|
60
61
|
def __ne__(self, other):
|
61
62
|
return not self.__eq__(other)
|
62
63
|
|
64
|
+
def __repr__(self):
|
65
|
+
expr = ClauseList(*self.index.expressions)
|
66
|
+
try:
|
67
|
+
expr_str = expr.compile().string
|
68
|
+
except Exception:
|
69
|
+
expr_str = str(expr)
|
70
|
+
return f"<CompareIndex {self.index.name}({expr_str})>"
|
71
|
+
|
63
72
|
|
64
73
|
class CompareCheckConstraint:
|
65
74
|
def __init__(self, constraint):
|
@@ -4,6 +4,7 @@ from sqlalchemy import Integer
|
|
4
4
|
from sqlalchemy import MetaData
|
5
5
|
from sqlalchemy import Table
|
6
6
|
|
7
|
+
from alembic.util import sqla_compat
|
7
8
|
from ._autogen_fixtures import AutogenFixtureTest
|
8
9
|
from ... import testing
|
9
10
|
from ...testing import config
|
@@ -78,16 +79,33 @@ class AutogenerateIdentityTest(AutogenFixtureTest, TestBase):
|
|
78
79
|
m2 = MetaData()
|
79
80
|
|
80
81
|
for m in (m1, m2):
|
81
|
-
|
82
|
-
|
83
|
-
m,
|
84
|
-
Column("id", Integer, sa.Identity(start=2)),
|
85
|
-
)
|
82
|
+
id_ = sa.Identity(start=2)
|
83
|
+
Table("user", m, Column("id", Integer, id_))
|
86
84
|
|
87
85
|
diffs = self._fixture(m1, m2)
|
88
86
|
|
89
87
|
eq_(diffs, [])
|
90
88
|
|
89
|
+
def test_dialect_kwargs_changes(self):
|
90
|
+
m1 = MetaData()
|
91
|
+
m2 = MetaData()
|
92
|
+
|
93
|
+
if sqla_compat.identity_has_dialect_kwargs:
|
94
|
+
args = {"oracle_on_null": True, "oracle_order": True}
|
95
|
+
else:
|
96
|
+
args = {"on_null": True, "order": True}
|
97
|
+
|
98
|
+
Table("user", m1, Column("id", Integer, sa.Identity(start=2)))
|
99
|
+
id_ = sa.Identity(start=2, **args)
|
100
|
+
Table("user", m2, Column("id", Integer, id_))
|
101
|
+
|
102
|
+
diffs = self._fixture(m1, m2)
|
103
|
+
if config.db.name == "oracle":
|
104
|
+
is_true(len(diffs), 1)
|
105
|
+
eq_(diffs[0][0][0], "modify_default")
|
106
|
+
else:
|
107
|
+
eq_(diffs, [])
|
108
|
+
|
91
109
|
@testing.combinations(
|
92
110
|
(None, dict(start=2)),
|
93
111
|
(dict(start=2), None),
|
@@ -206,36 +224,3 @@ class AutogenerateIdentityTest(AutogenFixtureTest, TestBase):
|
|
206
224
|
removed = diffs[5]
|
207
225
|
|
208
226
|
is_true(isinstance(removed, sa.Identity))
|
209
|
-
|
210
|
-
def test_identity_on_null(self):
|
211
|
-
m1 = MetaData()
|
212
|
-
m2 = MetaData()
|
213
|
-
|
214
|
-
Table(
|
215
|
-
"user",
|
216
|
-
m1,
|
217
|
-
Column("id", Integer, sa.Identity(start=2, on_null=True)),
|
218
|
-
Column("other", sa.Text),
|
219
|
-
)
|
220
|
-
|
221
|
-
Table(
|
222
|
-
"user",
|
223
|
-
m2,
|
224
|
-
Column("id", Integer, sa.Identity(start=2, on_null=False)),
|
225
|
-
Column("other", sa.Text),
|
226
|
-
)
|
227
|
-
|
228
|
-
diffs = self._fixture(m1, m2)
|
229
|
-
if not config.requirements.supports_identity_on_null.enabled:
|
230
|
-
eq_(diffs, [])
|
231
|
-
else:
|
232
|
-
eq_(len(diffs[0]), 1)
|
233
|
-
diffs = diffs[0][0]
|
234
|
-
eq_(diffs[0], "modify_default")
|
235
|
-
eq_(diffs[2], "user")
|
236
|
-
eq_(diffs[3], "id")
|
237
|
-
old = diffs[5]
|
238
|
-
new = diffs[6]
|
239
|
-
|
240
|
-
is_true(isinstance(old, sa.Identity))
|
241
|
-
is_true(isinstance(new, sa.Identity))
|
alembic/util/compat.py
CHANGED
alembic/util/sqla_compat.py
CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import contextlib
|
4
4
|
import re
|
5
5
|
from typing import Any
|
6
|
+
from typing import Dict
|
6
7
|
from typing import Iterable
|
7
8
|
from typing import Iterator
|
8
9
|
from typing import Mapping
|
@@ -22,6 +23,7 @@ from sqlalchemy.schema import CheckConstraint
|
|
22
23
|
from sqlalchemy.schema import Column
|
23
24
|
from sqlalchemy.schema import ForeignKeyConstraint
|
24
25
|
from sqlalchemy.sql import visitors
|
26
|
+
from sqlalchemy.sql.base import DialectKWArgs
|
25
27
|
from sqlalchemy.sql.elements import BindParameter
|
26
28
|
from sqlalchemy.sql.elements import ColumnClause
|
27
29
|
from sqlalchemy.sql.elements import quoted_name
|
@@ -80,9 +82,10 @@ class _Unsupported:
|
|
80
82
|
try:
|
81
83
|
from sqlalchemy import Computed
|
82
84
|
except ImportError:
|
85
|
+
if not TYPE_CHECKING:
|
83
86
|
|
84
|
-
|
85
|
-
|
87
|
+
class Computed(_Unsupported):
|
88
|
+
pass
|
86
89
|
|
87
90
|
has_computed = False
|
88
91
|
has_computed_reflection = False
|
@@ -93,26 +96,54 @@ else:
|
|
93
96
|
try:
|
94
97
|
from sqlalchemy import Identity
|
95
98
|
except ImportError:
|
99
|
+
if not TYPE_CHECKING:
|
96
100
|
|
97
|
-
|
98
|
-
|
101
|
+
class Identity(_Unsupported):
|
102
|
+
pass
|
99
103
|
|
100
104
|
has_identity = False
|
101
105
|
else:
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
106
|
+
identity_has_dialect_kwargs = issubclass(Identity, DialectKWArgs)
|
107
|
+
|
108
|
+
def _get_identity_options_dict(
|
109
|
+
identity: Union[Identity, schema.Sequence, None],
|
110
|
+
dialect_kwargs: bool = False,
|
111
|
+
) -> Dict[str, Any]:
|
112
|
+
if identity is None:
|
113
|
+
return {}
|
114
|
+
elif identity_has_dialect_kwargs:
|
115
|
+
as_dict = identity._as_dict() # type: ignore
|
116
|
+
if dialect_kwargs:
|
117
|
+
assert isinstance(identity, DialectKWArgs)
|
118
|
+
as_dict.update(identity.dialect_kwargs)
|
119
|
+
else:
|
120
|
+
as_dict = {}
|
121
|
+
if isinstance(identity, Identity):
|
122
|
+
# always=None means something different than always=False
|
123
|
+
as_dict["always"] = identity.always
|
124
|
+
if identity.on_null is not None:
|
125
|
+
as_dict["on_null"] = identity.on_null
|
126
|
+
# attributes common to Identity and Sequence
|
127
|
+
attrs = (
|
128
|
+
"start",
|
129
|
+
"increment",
|
130
|
+
"minvalue",
|
131
|
+
"maxvalue",
|
132
|
+
"nominvalue",
|
133
|
+
"nomaxvalue",
|
134
|
+
"cycle",
|
135
|
+
"cache",
|
136
|
+
"order",
|
137
|
+
)
|
138
|
+
as_dict.update(
|
139
|
+
{
|
140
|
+
key: getattr(identity, key, None)
|
141
|
+
for key in attrs
|
142
|
+
if getattr(identity, key, None) is not None
|
143
|
+
}
|
144
|
+
)
|
145
|
+
return as_dict
|
146
|
+
|
116
147
|
has_identity = True
|
117
148
|
|
118
149
|
if sqla_2:
|
@@ -493,14 +524,6 @@ def _render_literal_bindparam(
|
|
493
524
|
return compiler.render_literal_bindparam(element, **kw)
|
494
525
|
|
495
526
|
|
496
|
-
def _get_index_expressions(idx):
|
497
|
-
return list(idx.expressions)
|
498
|
-
|
499
|
-
|
500
|
-
def _get_index_column_names(idx):
|
501
|
-
return [getattr(exp, "name", None) for exp in _get_index_expressions(idx)]
|
502
|
-
|
503
|
-
|
504
527
|
def _column_kwargs(col: Column) -> Mapping:
|
505
528
|
if sqla_13:
|
506
529
|
return col.kwargs
|
@@ -599,10 +622,15 @@ else:
|
|
599
622
|
|
600
623
|
|
601
624
|
def is_expression_index(index: Index) -> bool:
|
602
|
-
expr: Any
|
603
625
|
for expr in index.expressions:
|
604
|
-
|
605
|
-
expr = expr.element
|
606
|
-
if not isinstance(expr, ColumnClause) or expr.is_literal:
|
626
|
+
if is_expression(expr):
|
607
627
|
return True
|
608
628
|
return False
|
629
|
+
|
630
|
+
|
631
|
+
def is_expression(expr: Any) -> bool:
|
632
|
+
while isinstance(expr, UnaryExpression):
|
633
|
+
expr = expr.element
|
634
|
+
if not isinstance(expr, ColumnClause) or expr.is_literal:
|
635
|
+
return True
|
636
|
+
return False
|
@@ -1,13 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: alembic
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.13.0
|
4
4
|
Summary: A database migration tool for SQLAlchemy.
|
5
5
|
Home-page: https://alembic.sqlalchemy.org
|
6
6
|
Author: Mike Bayer
|
7
7
|
Author-email: mike_mp@zzzcomputing.com
|
8
8
|
License: MIT
|
9
|
-
Project-URL: Source, https://github.com/sqlalchemy/alembic/
|
10
9
|
Project-URL: Documentation, https://alembic.sqlalchemy.org/en/latest/
|
10
|
+
Project-URL: Changelog, https://alembic.sqlalchemy.org/en/latest/changelog.html
|
11
|
+
Project-URL: Source, https://github.com/sqlalchemy/alembic/
|
11
12
|
Project-URL: Issue Tracker, https://github.com/sqlalchemy/alembic/issues/
|
12
13
|
Classifier: Development Status :: 5 - Production/Stable
|
13
14
|
Classifier: Intended Audience :: Developers
|
@@ -16,14 +17,15 @@ Classifier: License :: OSI Approved :: MIT License
|
|
16
17
|
Classifier: Operating System :: OS Independent
|
17
18
|
Classifier: Programming Language :: Python
|
18
19
|
Classifier: Programming Language :: Python :: 3
|
19
|
-
Classifier: Programming Language :: Python :: 3.7
|
20
20
|
Classifier: Programming Language :: Python :: 3.8
|
21
21
|
Classifier: Programming Language :: Python :: 3.9
|
22
22
|
Classifier: Programming Language :: Python :: 3.10
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
23
25
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
24
26
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
25
27
|
Classifier: Topic :: Database :: Front-Ends
|
26
|
-
Requires-Python: >=3.
|
28
|
+
Requires-Python: >=3.8
|
27
29
|
Description-Content-Type: text/x-rst
|
28
30
|
License-File: LICENSE
|
29
31
|
Requires-Dist: SQLAlchemy >=1.3.0
|
@@ -32,7 +34,7 @@ Requires-Dist: typing-extensions >=4
|
|
32
34
|
Requires-Dist: importlib-metadata ; python_version < "3.9"
|
33
35
|
Requires-Dist: importlib-resources ; python_version < "3.9"
|
34
36
|
Provides-Extra: tz
|
35
|
-
Requires-Dist:
|
37
|
+
Requires-Dist: backports.zoneinfo ; (python_version < "3.9") and extra == 'tz'
|
36
38
|
|
37
39
|
Alembic is a database migrations tool written by the author
|
38
40
|
of `SQLAlchemy <http://www.sqlalchemy.org>`_. A migrations tool
|
@@ -101,7 +103,7 @@ The goals of Alembic are:
|
|
101
103
|
* Provide a library of ALTER constructs that can be used by any SQLAlchemy
|
102
104
|
application. The DDL constructs build upon SQLAlchemy's own DDLElement base
|
103
105
|
and can be used standalone by any application or script.
|
104
|
-
* At long last, bring SQLite and its
|
106
|
+
* At long last, bring SQLite and its inability to ALTER things into the fold,
|
105
107
|
but in such a way that SQLite's very special workflow needs are accommodated
|
106
108
|
in an explicit way that makes the most of a bad situation, through the
|
107
109
|
concept of a "batch" migration, where multiple changes to a table can
|