alembic 1.15.2__py3-none-any.whl → 1.16.1__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 -1
- alembic/autogenerate/compare.py +60 -7
- alembic/autogenerate/render.py +25 -4
- alembic/command.py +112 -37
- alembic/config.py +574 -222
- alembic/ddl/base.py +31 -7
- alembic/ddl/impl.py +23 -5
- alembic/ddl/mssql.py +3 -1
- alembic/ddl/mysql.py +8 -4
- alembic/ddl/postgresql.py +6 -2
- alembic/ddl/sqlite.py +1 -1
- alembic/op.pyi +24 -5
- alembic/operations/base.py +18 -3
- alembic/operations/ops.py +49 -8
- alembic/operations/toimpl.py +20 -3
- alembic/script/base.py +123 -136
- alembic/script/revision.py +1 -1
- alembic/script/write_hooks.py +20 -21
- alembic/templates/async/alembic.ini.mako +40 -16
- alembic/templates/generic/alembic.ini.mako +39 -17
- alembic/templates/multidb/alembic.ini.mako +42 -17
- alembic/templates/pyproject/README +1 -0
- alembic/templates/pyproject/alembic.ini.mako +44 -0
- alembic/templates/pyproject/env.py +78 -0
- alembic/templates/pyproject/pyproject.toml.mako +76 -0
- alembic/templates/pyproject/script.py.mako +28 -0
- alembic/testing/__init__.py +2 -0
- alembic/testing/assertions.py +4 -0
- alembic/testing/env.py +56 -1
- alembic/testing/fixtures.py +28 -1
- alembic/testing/suite/_autogen_fixtures.py +113 -0
- alembic/util/__init__.py +1 -0
- alembic/util/compat.py +56 -0
- alembic/util/messaging.py +4 -0
- alembic/util/pyfiles.py +56 -19
- {alembic-1.15.2.dist-info → alembic-1.16.1.dist-info}/METADATA +3 -3
- {alembic-1.15.2.dist-info → alembic-1.16.1.dist-info}/RECORD +41 -36
- {alembic-1.15.2.dist-info → alembic-1.16.1.dist-info}/WHEEL +1 -1
- {alembic-1.15.2.dist-info → alembic-1.16.1.dist-info}/entry_points.txt +0 -0
- {alembic-1.15.2.dist-info → alembic-1.16.1.dist-info}/licenses/LICENSE +0 -0
- {alembic-1.15.2.dist-info → alembic-1.16.1.dist-info}/top_level.txt +0 -0
alembic/script/base.py
CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
from contextlib import contextmanager
|
4
4
|
import datetime
|
5
5
|
import os
|
6
|
+
from pathlib import Path
|
6
7
|
import re
|
7
8
|
import shutil
|
8
9
|
import sys
|
@@ -11,7 +12,6 @@ from typing import Any
|
|
11
12
|
from typing import cast
|
12
13
|
from typing import Iterator
|
13
14
|
from typing import List
|
14
|
-
from typing import Mapping
|
15
15
|
from typing import Optional
|
16
16
|
from typing import Sequence
|
17
17
|
from typing import Set
|
@@ -25,6 +25,7 @@ from .. import util
|
|
25
25
|
from ..runtime import migration
|
26
26
|
from ..util import compat
|
27
27
|
from ..util import not_none
|
28
|
+
from ..util.pyfiles import _preserving_path_as_str
|
28
29
|
|
29
30
|
if TYPE_CHECKING:
|
30
31
|
from .revision import _GetRevArg
|
@@ -32,6 +33,7 @@ if TYPE_CHECKING:
|
|
32
33
|
from .revision import Revision
|
33
34
|
from ..config import Config
|
34
35
|
from ..config import MessagingOptions
|
36
|
+
from ..config import PostWriteHookConfig
|
35
37
|
from ..runtime.migration import RevisionStep
|
36
38
|
from ..runtime.migration import StampStep
|
37
39
|
|
@@ -50,9 +52,6 @@ _only_source_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)$")
|
|
50
52
|
_legacy_rev = re.compile(r"([a-f0-9]+)\.py$")
|
51
53
|
_slug_re = re.compile(r"\w+")
|
52
54
|
_default_file_template = "%(rev)s_%(slug)s"
|
53
|
-
_split_on_space_comma = re.compile(r", *|(?: +)")
|
54
|
-
|
55
|
-
_split_on_space_comma_colon = re.compile(r", *|(?: +)|\:")
|
56
55
|
|
57
56
|
|
58
57
|
class ScriptDirectory:
|
@@ -77,40 +76,55 @@ class ScriptDirectory:
|
|
77
76
|
|
78
77
|
def __init__(
|
79
78
|
self,
|
80
|
-
dir: str, # noqa
|
79
|
+
dir: Union[str, os.PathLike[str]], # noqa: A002
|
81
80
|
file_template: str = _default_file_template,
|
82
81
|
truncate_slug_length: Optional[int] = 40,
|
83
|
-
version_locations: Optional[
|
82
|
+
version_locations: Optional[
|
83
|
+
Sequence[Union[str, os.PathLike[str]]]
|
84
|
+
] = None,
|
84
85
|
sourceless: bool = False,
|
85
86
|
output_encoding: str = "utf-8",
|
86
87
|
timezone: Optional[str] = None,
|
87
|
-
|
88
|
+
hooks: list[PostWriteHookConfig] = [],
|
88
89
|
recursive_version_locations: bool = False,
|
89
90
|
messaging_opts: MessagingOptions = cast(
|
90
91
|
"MessagingOptions", util.EMPTY_DICT
|
91
92
|
),
|
92
93
|
) -> None:
|
93
|
-
self.dir = dir
|
94
|
+
self.dir = _preserving_path_as_str(dir)
|
95
|
+
self.version_locations = [
|
96
|
+
_preserving_path_as_str(p) for p in version_locations or ()
|
97
|
+
]
|
94
98
|
self.file_template = file_template
|
95
|
-
self.version_locations = version_locations
|
96
99
|
self.truncate_slug_length = truncate_slug_length or 40
|
97
100
|
self.sourceless = sourceless
|
98
101
|
self.output_encoding = output_encoding
|
99
102
|
self.revision_map = revision.RevisionMap(self._load_revisions)
|
100
103
|
self.timezone = timezone
|
101
|
-
self.
|
104
|
+
self.hooks = hooks
|
102
105
|
self.recursive_version_locations = recursive_version_locations
|
103
106
|
self.messaging_opts = messaging_opts
|
104
107
|
|
105
108
|
if not os.access(dir, os.F_OK):
|
106
109
|
raise util.CommandError(
|
107
|
-
"Path doesn't exist:
|
110
|
+
f"Path doesn't exist: {dir}. Please use "
|
108
111
|
"the 'init' command to create a new "
|
109
|
-
"scripts folder."
|
112
|
+
"scripts folder."
|
110
113
|
)
|
111
114
|
|
112
115
|
@property
|
113
116
|
def versions(self) -> str:
|
117
|
+
"""return a single version location based on the sole path passed
|
118
|
+
within version_locations.
|
119
|
+
|
120
|
+
If multiple version locations are configured, an error is raised.
|
121
|
+
|
122
|
+
|
123
|
+
"""
|
124
|
+
return str(self._singular_version_location)
|
125
|
+
|
126
|
+
@util.memoized_property
|
127
|
+
def _singular_version_location(self) -> Path:
|
114
128
|
loc = self._version_locations
|
115
129
|
if len(loc) > 1:
|
116
130
|
raise util.CommandError("Multiple version_locations present")
|
@@ -118,40 +132,31 @@ class ScriptDirectory:
|
|
118
132
|
return loc[0]
|
119
133
|
|
120
134
|
@util.memoized_property
|
121
|
-
def _version_locations(self) -> Sequence[
|
135
|
+
def _version_locations(self) -> Sequence[Path]:
|
122
136
|
if self.version_locations:
|
123
137
|
return [
|
124
|
-
|
138
|
+
util.coerce_resource_to_filename(location).absolute()
|
125
139
|
for location in self.version_locations
|
126
140
|
]
|
127
141
|
else:
|
128
|
-
return (
|
142
|
+
return [Path(self.dir, "versions").absolute()]
|
129
143
|
|
130
144
|
def _load_revisions(self) -> Iterator[Script]:
|
131
|
-
|
132
|
-
paths = [
|
133
|
-
vers
|
134
|
-
for vers in self._version_locations
|
135
|
-
if os.path.exists(vers)
|
136
|
-
]
|
137
|
-
else:
|
138
|
-
paths = [self.versions]
|
145
|
+
paths = [vers for vers in self._version_locations if vers.exists()]
|
139
146
|
|
140
147
|
dupes = set()
|
141
148
|
for vers in paths:
|
142
149
|
for file_path in Script._list_py_dir(self, vers):
|
143
|
-
real_path =
|
150
|
+
real_path = file_path.resolve()
|
144
151
|
if real_path in dupes:
|
145
152
|
util.warn(
|
146
|
-
"File
|
147
|
-
"version_locations is unique."
|
153
|
+
f"File {real_path} loaded twice! ignoring. "
|
154
|
+
"Please ensure version_locations is unique."
|
148
155
|
)
|
149
156
|
continue
|
150
157
|
dupes.add(real_path)
|
151
158
|
|
152
|
-
|
153
|
-
dir_name = os.path.dirname(real_path)
|
154
|
-
script = Script._from_filename(self, dir_name, filename)
|
159
|
+
script = Script._from_path(self, real_path)
|
155
160
|
if script is None:
|
156
161
|
continue
|
157
162
|
yield script
|
@@ -165,78 +170,38 @@ class ScriptDirectory:
|
|
165
170
|
present.
|
166
171
|
|
167
172
|
"""
|
168
|
-
script_location = config.
|
173
|
+
script_location = config.get_alembic_option("script_location")
|
169
174
|
if script_location is None:
|
170
175
|
raise util.CommandError(
|
171
|
-
"No 'script_location' key
|
176
|
+
"No 'script_location' key found in configuration."
|
172
177
|
)
|
173
178
|
truncate_slug_length: Optional[int]
|
174
|
-
tsl = config.
|
179
|
+
tsl = config.get_alembic_option("truncate_slug_length")
|
175
180
|
if tsl is not None:
|
176
181
|
truncate_slug_length = int(tsl)
|
177
182
|
else:
|
178
183
|
truncate_slug_length = None
|
179
184
|
|
180
|
-
|
181
|
-
version_locations: Optional[List[str]]
|
182
|
-
if version_locations_str:
|
183
|
-
version_path_separator = config.get_main_option(
|
184
|
-
"version_path_separator"
|
185
|
-
)
|
186
|
-
|
187
|
-
split_on_path = {
|
188
|
-
None: None,
|
189
|
-
"space": " ",
|
190
|
-
"newline": "\n",
|
191
|
-
"os": os.pathsep,
|
192
|
-
":": ":",
|
193
|
-
";": ";",
|
194
|
-
}
|
195
|
-
|
196
|
-
try:
|
197
|
-
split_char: Optional[str] = split_on_path[
|
198
|
-
version_path_separator
|
199
|
-
]
|
200
|
-
except KeyError as ke:
|
201
|
-
raise ValueError(
|
202
|
-
"'%s' is not a valid value for "
|
203
|
-
"version_path_separator; "
|
204
|
-
"expected 'space', 'newline', 'os', ':', ';'"
|
205
|
-
% version_path_separator
|
206
|
-
) from ke
|
207
|
-
else:
|
208
|
-
if split_char is None:
|
209
|
-
# legacy behaviour for backwards compatibility
|
210
|
-
version_locations = _split_on_space_comma.split(
|
211
|
-
version_locations_str
|
212
|
-
)
|
213
|
-
else:
|
214
|
-
version_locations = [
|
215
|
-
x.strip()
|
216
|
-
for x in version_locations_str.split(split_char)
|
217
|
-
if x
|
218
|
-
]
|
219
|
-
else:
|
220
|
-
version_locations = None
|
221
|
-
|
222
|
-
prepend_sys_path = config.get_main_option("prepend_sys_path")
|
185
|
+
prepend_sys_path = config.get_prepend_sys_paths_list()
|
223
186
|
if prepend_sys_path:
|
224
|
-
sys.path[:0] =
|
225
|
-
_split_on_space_comma_colon.split(prepend_sys_path)
|
226
|
-
)
|
187
|
+
sys.path[:0] = prepend_sys_path
|
227
188
|
|
228
|
-
rvl =
|
189
|
+
rvl = (
|
190
|
+
config.get_alembic_option("recursive_version_locations") == "true"
|
191
|
+
)
|
229
192
|
return ScriptDirectory(
|
230
193
|
util.coerce_resource_to_filename(script_location),
|
231
|
-
file_template=config.
|
194
|
+
file_template=config.get_alembic_option(
|
232
195
|
"file_template", _default_file_template
|
233
196
|
),
|
234
197
|
truncate_slug_length=truncate_slug_length,
|
235
|
-
sourceless=config.
|
236
|
-
output_encoding=config.
|
237
|
-
|
238
|
-
|
239
|
-
|
198
|
+
sourceless=config.get_alembic_option("sourceless") == "true",
|
199
|
+
output_encoding=config.get_alembic_option(
|
200
|
+
"output_encoding", "utf-8"
|
201
|
+
),
|
202
|
+
version_locations=config.get_version_locations_list(),
|
203
|
+
timezone=config.get_alembic_option("timezone"),
|
204
|
+
hooks=config.get_hooks_list(),
|
240
205
|
recursive_version_locations=rvl,
|
241
206
|
messaging_opts=config.messaging_opts,
|
242
207
|
)
|
@@ -587,23 +552,32 @@ class ScriptDirectory:
|
|
587
552
|
|
588
553
|
@property
|
589
554
|
def env_py_location(self) -> str:
|
590
|
-
return
|
555
|
+
return str(Path(self.dir, "env.py"))
|
556
|
+
|
557
|
+
def _append_template(self, src: Path, dest: Path, **kw: Any) -> None:
|
558
|
+
with util.status(
|
559
|
+
f"Appending to existing {dest.absolute()}",
|
560
|
+
**self.messaging_opts,
|
561
|
+
):
|
562
|
+
util.template_to_file(
|
563
|
+
src, dest, self.output_encoding, append=True, **kw
|
564
|
+
)
|
591
565
|
|
592
|
-
def _generate_template(self, src:
|
566
|
+
def _generate_template(self, src: Path, dest: Path, **kw: Any) -> None:
|
593
567
|
with util.status(
|
594
|
-
f"Generating {
|
568
|
+
f"Generating {dest.absolute()}", **self.messaging_opts
|
595
569
|
):
|
596
570
|
util.template_to_file(src, dest, self.output_encoding, **kw)
|
597
571
|
|
598
|
-
def _copy_file(self, src:
|
572
|
+
def _copy_file(self, src: Path, dest: Path) -> None:
|
599
573
|
with util.status(
|
600
|
-
f"Generating {
|
574
|
+
f"Generating {dest.absolute()}", **self.messaging_opts
|
601
575
|
):
|
602
576
|
shutil.copy(src, dest)
|
603
577
|
|
604
|
-
def _ensure_directory(self, path:
|
605
|
-
path =
|
606
|
-
if not
|
578
|
+
def _ensure_directory(self, path: Path) -> None:
|
579
|
+
path = path.absolute()
|
580
|
+
if not path.exists():
|
607
581
|
with util.status(
|
608
582
|
f"Creating directory {path}", **self.messaging_opts
|
609
583
|
):
|
@@ -628,11 +602,10 @@ class ScriptDirectory:
|
|
628
602
|
raise util.CommandError(
|
629
603
|
"Can't locate timezone: %s" % self.timezone
|
630
604
|
) from None
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
)
|
605
|
+
|
606
|
+
create_date = datetime.datetime.now(
|
607
|
+
tz=datetime.timezone.utc
|
608
|
+
).astimezone(tzinfo)
|
636
609
|
else:
|
637
610
|
create_date = datetime.datetime.now()
|
638
611
|
return create_date
|
@@ -644,7 +617,8 @@ class ScriptDirectory:
|
|
644
617
|
head: Optional[_RevIdType] = None,
|
645
618
|
splice: Optional[bool] = False,
|
646
619
|
branch_labels: Optional[_RevIdType] = None,
|
647
|
-
version_path:
|
620
|
+
version_path: Union[str, os.PathLike[str], None] = None,
|
621
|
+
file_template: Optional[str] = None,
|
648
622
|
depends_on: Optional[_RevIdType] = None,
|
649
623
|
**kw: Any,
|
650
624
|
) -> Optional[Script]:
|
@@ -697,7 +671,7 @@ class ScriptDirectory:
|
|
697
671
|
for head_ in heads:
|
698
672
|
if head_ is not None:
|
699
673
|
assert isinstance(head_, Script)
|
700
|
-
version_path =
|
674
|
+
version_path = head_._script_path.parent
|
701
675
|
break
|
702
676
|
else:
|
703
677
|
raise util.CommandError(
|
@@ -705,16 +679,19 @@ class ScriptDirectory:
|
|
705
679
|
"please specify --version-path"
|
706
680
|
)
|
707
681
|
else:
|
708
|
-
version_path = self.
|
682
|
+
version_path = self._singular_version_location
|
683
|
+
else:
|
684
|
+
version_path = Path(version_path)
|
709
685
|
|
710
|
-
|
686
|
+
assert isinstance(version_path, Path)
|
687
|
+
norm_path = version_path.absolute()
|
711
688
|
for vers_path in self._version_locations:
|
712
|
-
if
|
689
|
+
if vers_path.absolute() == norm_path:
|
713
690
|
break
|
714
691
|
else:
|
715
692
|
raise util.CommandError(
|
716
|
-
"Path
|
717
|
-
"version locations"
|
693
|
+
f"Path {version_path} is not represented in current "
|
694
|
+
"version locations"
|
718
695
|
)
|
719
696
|
|
720
697
|
if self.version_locations:
|
@@ -749,7 +726,7 @@ class ScriptDirectory:
|
|
749
726
|
resolved_depends_on = None
|
750
727
|
|
751
728
|
self._generate_template(
|
752
|
-
|
729
|
+
Path(self.dir, "script.py.mako"),
|
753
730
|
path,
|
754
731
|
up_revision=str(revid),
|
755
732
|
down_revision=revision.tuple_rev_as_scalar(
|
@@ -763,7 +740,7 @@ class ScriptDirectory:
|
|
763
740
|
**kw,
|
764
741
|
)
|
765
742
|
|
766
|
-
post_write_hooks = self.
|
743
|
+
post_write_hooks = self.hooks
|
767
744
|
if post_write_hooks:
|
768
745
|
write_hooks._run_hooks(path, post_write_hooks)
|
769
746
|
|
@@ -786,11 +763,11 @@ class ScriptDirectory:
|
|
786
763
|
|
787
764
|
def _rev_path(
|
788
765
|
self,
|
789
|
-
path: str,
|
766
|
+
path: Union[str, os.PathLike[str]],
|
790
767
|
rev_id: str,
|
791
768
|
message: Optional[str],
|
792
769
|
create_date: datetime.datetime,
|
793
|
-
) ->
|
770
|
+
) -> Path:
|
794
771
|
epoch = int(create_date.timestamp())
|
795
772
|
slug = "_".join(_slug_re.findall(message or "")).lower()
|
796
773
|
if len(slug) > self.truncate_slug_length:
|
@@ -809,7 +786,7 @@ class ScriptDirectory:
|
|
809
786
|
"second": create_date.second,
|
810
787
|
}
|
811
788
|
)
|
812
|
-
return
|
789
|
+
return Path(path) / filename
|
813
790
|
|
814
791
|
|
815
792
|
class Script(revision.Revision):
|
@@ -820,9 +797,14 @@ class Script(revision.Revision):
|
|
820
797
|
|
821
798
|
"""
|
822
799
|
|
823
|
-
def __init__(
|
800
|
+
def __init__(
|
801
|
+
self,
|
802
|
+
module: ModuleType,
|
803
|
+
rev_id: str,
|
804
|
+
path: Union[str, os.PathLike[str]],
|
805
|
+
):
|
824
806
|
self.module = module
|
825
|
-
self.path = path
|
807
|
+
self.path = _preserving_path_as_str(path)
|
826
808
|
super().__init__(
|
827
809
|
rev_id,
|
828
810
|
module.down_revision,
|
@@ -840,6 +822,10 @@ class Script(revision.Revision):
|
|
840
822
|
path: str
|
841
823
|
"""Filesystem path of the script."""
|
842
824
|
|
825
|
+
@property
|
826
|
+
def _script_path(self) -> Path:
|
827
|
+
return Path(self.path)
|
828
|
+
|
843
829
|
_db_current_indicator: Optional[bool] = None
|
844
830
|
"""Utility variable which when set will cause string output to indicate
|
845
831
|
this is a "current" version in some database"""
|
@@ -972,36 +958,33 @@ class Script(revision.Revision):
|
|
972
958
|
return util.format_as_comma(self._versioned_down_revisions)
|
973
959
|
|
974
960
|
@classmethod
|
975
|
-
def
|
976
|
-
cls, scriptdir: ScriptDirectory, path:
|
977
|
-
) ->
|
978
|
-
dir_, filename = os.path.split(path)
|
979
|
-
return cls._from_filename(scriptdir, dir_, filename)
|
980
|
-
|
981
|
-
@classmethod
|
982
|
-
def _list_py_dir(cls, scriptdir: ScriptDirectory, path: str) -> List[str]:
|
961
|
+
def _list_py_dir(
|
962
|
+
cls, scriptdir: ScriptDirectory, path: Path
|
963
|
+
) -> List[Path]:
|
983
964
|
paths = []
|
984
|
-
for root, dirs, files in
|
985
|
-
if root.endswith("__pycache__"):
|
965
|
+
for root, dirs, files in compat.path_walk(path, top_down=True):
|
966
|
+
if root.name.endswith("__pycache__"):
|
986
967
|
# a special case - we may include these files
|
987
968
|
# if a `sourceless` option is specified
|
988
969
|
continue
|
989
970
|
|
990
971
|
for filename in sorted(files):
|
991
|
-
paths.append(
|
972
|
+
paths.append(root / filename)
|
992
973
|
|
993
974
|
if scriptdir.sourceless:
|
994
975
|
# look for __pycache__
|
995
|
-
py_cache_path =
|
996
|
-
if
|
976
|
+
py_cache_path = root / "__pycache__"
|
977
|
+
if py_cache_path.exists():
|
997
978
|
# add all files from __pycache__ whose filename is not
|
998
979
|
# already in the names we got from the version directory.
|
999
980
|
# add as relative paths including __pycache__ token
|
1000
|
-
names = {
|
981
|
+
names = {
|
982
|
+
Path(filename).name.split(".")[0] for filename in files
|
983
|
+
}
|
1001
984
|
paths.extend(
|
1002
|
-
|
1003
|
-
for pyc in
|
1004
|
-
if pyc.split(".")[0] not in names
|
985
|
+
py_cache_path / pyc
|
986
|
+
for pyc in py_cache_path.iterdir()
|
987
|
+
if pyc.name.split(".")[0] not in names
|
1005
988
|
)
|
1006
989
|
|
1007
990
|
if not scriptdir.recursive_version_locations:
|
@@ -1016,9 +999,13 @@ class Script(revision.Revision):
|
|
1016
999
|
return paths
|
1017
1000
|
|
1018
1001
|
@classmethod
|
1019
|
-
def
|
1020
|
-
cls, scriptdir: ScriptDirectory,
|
1002
|
+
def _from_path(
|
1003
|
+
cls, scriptdir: ScriptDirectory, path: Union[str, os.PathLike[str]]
|
1021
1004
|
) -> Optional[Script]:
|
1005
|
+
|
1006
|
+
path = Path(path)
|
1007
|
+
dir_, filename = path.parent, path.name
|
1008
|
+
|
1022
1009
|
if scriptdir.sourceless:
|
1023
1010
|
py_match = _sourceless_rev_file.match(filename)
|
1024
1011
|
else:
|
@@ -1036,8 +1023,8 @@ class Script(revision.Revision):
|
|
1036
1023
|
is_c = is_o = False
|
1037
1024
|
|
1038
1025
|
if is_o or is_c:
|
1039
|
-
py_exists =
|
1040
|
-
pyc_exists =
|
1026
|
+
py_exists = (dir_ / py_filename).exists()
|
1027
|
+
pyc_exists = (dir_ / (py_filename + "c")).exists()
|
1041
1028
|
|
1042
1029
|
# prefer .py over .pyc because we'd like to get the
|
1043
1030
|
# source encoding; prefer .pyc over .pyo because we'd like to
|
@@ -1053,14 +1040,14 @@ class Script(revision.Revision):
|
|
1053
1040
|
m = _legacy_rev.match(filename)
|
1054
1041
|
if not m:
|
1055
1042
|
raise util.CommandError(
|
1056
|
-
"Could not determine revision id from
|
1043
|
+
"Could not determine revision id from "
|
1044
|
+
f"filename {filename}. "
|
1057
1045
|
"Be sure the 'revision' variable is "
|
1058
1046
|
"declared inside the script (please see 'Upgrading "
|
1059
1047
|
"from Alembic 0.1 to 0.2' in the documentation)."
|
1060
|
-
% filename
|
1061
1048
|
)
|
1062
1049
|
else:
|
1063
1050
|
revision = m.group(1)
|
1064
1051
|
else:
|
1065
1052
|
revision = module.revision
|
1066
|
-
return Script(module, revision,
|
1053
|
+
return Script(module, revision, dir_ / filename)
|
alembic/script/revision.py
CHANGED
alembic/script/write_hooks.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
+
import os
|
6
7
|
import shlex
|
7
8
|
import subprocess
|
8
9
|
import sys
|
@@ -10,13 +11,16 @@ from typing import Any
|
|
10
11
|
from typing import Callable
|
11
12
|
from typing import Dict
|
12
13
|
from typing import List
|
13
|
-
from typing import Mapping
|
14
14
|
from typing import Optional
|
15
|
+
from typing import TYPE_CHECKING
|
15
16
|
from typing import Union
|
16
17
|
|
17
18
|
from .. import util
|
18
19
|
from ..util import compat
|
20
|
+
from ..util.pyfiles import _preserving_path_as_str
|
19
21
|
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from ..config import PostWriteHookConfig
|
20
24
|
|
21
25
|
REVISION_SCRIPT_TOKEN = "REVISION_SCRIPT_FILENAME"
|
22
26
|
|
@@ -43,16 +47,19 @@ def register(name: str) -> Callable:
|
|
43
47
|
|
44
48
|
|
45
49
|
def _invoke(
|
46
|
-
name: str,
|
50
|
+
name: str,
|
51
|
+
revision_path: Union[str, os.PathLike[str]],
|
52
|
+
options: PostWriteHookConfig,
|
47
53
|
) -> Any:
|
48
54
|
"""Invokes the formatter registered for the given name.
|
49
55
|
|
50
56
|
:param name: The name of a formatter in the registry
|
51
|
-
:param revision:
|
57
|
+
:param revision: string path to the revision file
|
52
58
|
:param options: A dict containing kwargs passed to the
|
53
59
|
specified formatter.
|
54
60
|
:raises: :class:`alembic.util.CommandError`
|
55
61
|
"""
|
62
|
+
revision_path = _preserving_path_as_str(revision_path)
|
56
63
|
try:
|
57
64
|
hook = _registry[name]
|
58
65
|
except KeyError as ke:
|
@@ -60,36 +67,28 @@ def _invoke(
|
|
60
67
|
f"No formatter with name '{name}' registered"
|
61
68
|
) from ke
|
62
69
|
else:
|
63
|
-
return hook(
|
70
|
+
return hook(revision_path, options)
|
64
71
|
|
65
72
|
|
66
|
-
def _run_hooks(
|
73
|
+
def _run_hooks(
|
74
|
+
path: Union[str, os.PathLike[str]], hooks: list[PostWriteHookConfig]
|
75
|
+
) -> None:
|
67
76
|
"""Invoke hooks for a generated revision."""
|
68
77
|
|
69
|
-
|
70
|
-
|
71
|
-
names = _split_on_space_comma.split(hook_config.get("hooks", ""))
|
72
|
-
|
73
|
-
for name in names:
|
74
|
-
if not name:
|
75
|
-
continue
|
76
|
-
opts = {
|
77
|
-
key[len(name) + 1 :]: hook_config[key]
|
78
|
-
for key in hook_config
|
79
|
-
if key.startswith(name + ".")
|
80
|
-
}
|
81
|
-
opts["_hook_name"] = name
|
78
|
+
for hook in hooks:
|
79
|
+
name = hook["_hook_name"]
|
82
80
|
try:
|
83
|
-
type_ =
|
81
|
+
type_ = hook["type"]
|
84
82
|
except KeyError as ke:
|
85
83
|
raise util.CommandError(
|
86
|
-
f"Key {name}.type
|
84
|
+
f"Key '{name}.type' (or 'type' in toml) is required "
|
85
|
+
f"for post write hook {name!r}"
|
87
86
|
) from ke
|
88
87
|
else:
|
89
88
|
with util.status(
|
90
89
|
f"Running post write hook {name!r}", newline=True
|
91
90
|
):
|
92
|
-
_invoke(type_, path,
|
91
|
+
_invoke(type_, path, hook)
|
93
92
|
|
94
93
|
|
95
94
|
def _parse_cmdline_options(cmdline_options_str: str, path: str) -> List[str]:
|