lamindb_setup 1.18.2__py3-none-any.whl → 1.19.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.
- lamindb_setup/__init__.py +4 -19
- lamindb_setup/_cache.py +87 -87
- lamindb_setup/_check.py +7 -7
- lamindb_setup/_check_setup.py +131 -131
- lamindb_setup/_connect_instance.py +443 -438
- lamindb_setup/_delete.py +155 -151
- lamindb_setup/_disconnect.py +38 -38
- lamindb_setup/_django.py +39 -39
- lamindb_setup/_entry_points.py +19 -19
- lamindb_setup/_init_instance.py +423 -429
- lamindb_setup/_migrate.py +331 -327
- lamindb_setup/_register_instance.py +32 -32
- lamindb_setup/_schema.py +27 -27
- lamindb_setup/_schema_metadata.py +451 -451
- lamindb_setup/_set_managed_storage.py +81 -80
- lamindb_setup/_setup_user.py +198 -198
- lamindb_setup/_silence_loggers.py +46 -46
- lamindb_setup/core/__init__.py +25 -34
- lamindb_setup/core/_aws_options.py +276 -266
- lamindb_setup/core/_aws_storage.py +57 -55
- lamindb_setup/core/_clone.py +50 -50
- lamindb_setup/core/_deprecated.py +62 -62
- lamindb_setup/core/_docs.py +14 -14
- lamindb_setup/core/_hub_client.py +288 -294
- lamindb_setup/core/_hub_core.py +0 -2
- lamindb_setup/core/_hub_crud.py +247 -247
- lamindb_setup/core/_hub_utils.py +100 -100
- lamindb_setup/core/_private_django_api.py +80 -80
- lamindb_setup/core/_settings.py +440 -434
- lamindb_setup/core/_settings_instance.py +32 -7
- lamindb_setup/core/_settings_load.py +162 -159
- lamindb_setup/core/_settings_save.py +108 -96
- lamindb_setup/core/_settings_storage.py +433 -433
- lamindb_setup/core/_settings_store.py +162 -92
- lamindb_setup/core/_settings_user.py +55 -55
- lamindb_setup/core/_setup_bionty_sources.py +44 -44
- lamindb_setup/core/cloud_sqlite_locker.py +240 -240
- lamindb_setup/core/django.py +414 -413
- lamindb_setup/core/exceptions.py +1 -1
- lamindb_setup/core/hashing.py +134 -134
- lamindb_setup/core/types.py +1 -1
- lamindb_setup/core/upath.py +1031 -1028
- lamindb_setup/errors.py +72 -70
- lamindb_setup/io.py +423 -416
- lamindb_setup/types.py +17 -17
- {lamindb_setup-1.18.2.dist-info → lamindb_setup-1.19.1.dist-info}/METADATA +4 -2
- lamindb_setup-1.19.1.dist-info/RECORD +51 -0
- {lamindb_setup-1.18.2.dist-info → lamindb_setup-1.19.1.dist-info}/WHEEL +1 -1
- {lamindb_setup-1.18.2.dist-info → lamindb_setup-1.19.1.dist-info/licenses}/LICENSE +201 -201
- lamindb_setup-1.18.2.dist-info/RECORD +0 -51
lamindb_setup/_migrate.py
CHANGED
|
@@ -1,327 +1,331 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
from .
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
installed_version
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
isettings
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
from lamindb_setup.
|
|
131
|
-
from lamindb_setup.
|
|
132
|
-
from lamindb_setup.core.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
connect
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
stdout
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
@
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
@
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
name
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
out
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from django.db import connection
|
|
6
|
+
from lamin_utils import logger
|
|
7
|
+
from packaging import version
|
|
8
|
+
|
|
9
|
+
from ._check_setup import _check_instance_setup, disable_auto_connect
|
|
10
|
+
from .core._settings import settings
|
|
11
|
+
from .core.django import setup_django
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# for the django-based synching code, see laminhub_rest
|
|
15
|
+
def check_whether_migrations_in_sync(db_version_str: str):
|
|
16
|
+
from importlib import metadata
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
installed_version_str = metadata.version("lamindb")
|
|
20
|
+
except metadata.PackageNotFoundError:
|
|
21
|
+
return None
|
|
22
|
+
if db_version_str is None:
|
|
23
|
+
logger.warning("no lamindb version stored to compare with installed version")
|
|
24
|
+
return None
|
|
25
|
+
installed_version = version.parse(installed_version_str)
|
|
26
|
+
db_version = version.parse(db_version_str)
|
|
27
|
+
if installed_version.major < db_version.major:
|
|
28
|
+
logger.warning(
|
|
29
|
+
f"the database ({db_version_str}) is far ahead of your installed lamindb package ({installed_version_str})"
|
|
30
|
+
)
|
|
31
|
+
logger.important(
|
|
32
|
+
f"please update lamindb: pip install lamindb>={db_version.major}"
|
|
33
|
+
)
|
|
34
|
+
elif (
|
|
35
|
+
installed_version.major == db_version.major
|
|
36
|
+
and installed_version.minor < db_version.minor
|
|
37
|
+
):
|
|
38
|
+
db_version_lower = f"{db_version.major}.{db_version.minor}"
|
|
39
|
+
logger.important(
|
|
40
|
+
f"the database ({db_version_str}) is ahead of your installed lamindb"
|
|
41
|
+
f" package ({installed_version_str})"
|
|
42
|
+
)
|
|
43
|
+
logger.important(
|
|
44
|
+
f"consider updating lamindb: pip install lamindb>={db_version_lower}"
|
|
45
|
+
)
|
|
46
|
+
elif installed_version.major > db_version.major:
|
|
47
|
+
logger.warning(
|
|
48
|
+
f"the database ({db_version_str}) is far behind your installed lamindb package"
|
|
49
|
+
f" ({installed_version_str})"
|
|
50
|
+
)
|
|
51
|
+
logger.important(
|
|
52
|
+
"if you are an admin, migrate your database: lamin migrate deploy"
|
|
53
|
+
)
|
|
54
|
+
elif (
|
|
55
|
+
installed_version.major == db_version.major
|
|
56
|
+
and installed_version.minor > db_version.minor
|
|
57
|
+
):
|
|
58
|
+
pass
|
|
59
|
+
# if the database is behind by a minor version, we don't want to spam the user
|
|
60
|
+
# logger.important(
|
|
61
|
+
# f"the database ({db_version_str}) is behind your installed lamindb package"
|
|
62
|
+
# f" ({installed_version_str})"
|
|
63
|
+
# )
|
|
64
|
+
# logger.important("consider migrating your database: lamin migrate deploy")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class migrate:
|
|
68
|
+
"""Manage database migrations.
|
|
69
|
+
|
|
70
|
+
Unless you maintain your own schema modules with your own Django models, you won't need this.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
|
|
74
|
+
Create a migration::
|
|
75
|
+
|
|
76
|
+
import lamindb as ln
|
|
77
|
+
|
|
78
|
+
ln.setup.migrate.create()
|
|
79
|
+
|
|
80
|
+
Deploy a migration::
|
|
81
|
+
|
|
82
|
+
ln.setup.migrate.deploy()
|
|
83
|
+
|
|
84
|
+
Check migration consistency::
|
|
85
|
+
|
|
86
|
+
ln.setup.migrate.check()
|
|
87
|
+
|
|
88
|
+
See Also:
|
|
89
|
+
Migrate an instance via the CLI, see `here <https://docs.lamin.ai/cli#migrate>`__.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
@disable_auto_connect
|
|
95
|
+
def create(cls) -> None:
|
|
96
|
+
"""Create a migration."""
|
|
97
|
+
setup_django(settings.instance, create_migrations=True)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def deploy(cls, package_name: str | None = None, number: int | None = None) -> None:
|
|
101
|
+
assert settings._instance_exists, (
|
|
102
|
+
"Not connected to an instance, please connect to migrate."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# NOTE: this is a temporary solution to avoid breaking tests
|
|
106
|
+
LAMIN_MIGRATE_ON_LAMBDA = (
|
|
107
|
+
os.getenv("LAMIN_MIGRATE_ON_LAMBDA", "false") == "true"
|
|
108
|
+
)
|
|
109
|
+
isettings = settings.instance
|
|
110
|
+
|
|
111
|
+
if isettings.is_on_hub and LAMIN_MIGRATE_ON_LAMBDA:
|
|
112
|
+
# dynamic import to avoid importing the heavy httpx at root
|
|
113
|
+
import httpx
|
|
114
|
+
|
|
115
|
+
response = httpx.post(
|
|
116
|
+
f"{isettings.api_url}/instances/{isettings._id}/migrate",
|
|
117
|
+
headers={"Authorization": f"Bearer {settings.user.access_token}"},
|
|
118
|
+
timeout=None, # this can take time
|
|
119
|
+
)
|
|
120
|
+
if response.status_code != 200:
|
|
121
|
+
raise Exception(f"Failed to migrate instance: {response.text}")
|
|
122
|
+
else:
|
|
123
|
+
cls._deploy(package_name=package_name, number=number)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def _deploy(
|
|
127
|
+
cls, package_name: str | None = None, number: int | None = None
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Deploy a migration."""
|
|
130
|
+
from lamindb_setup._connect_instance import connect
|
|
131
|
+
from lamindb_setup._schema_metadata import update_schema_in_hub
|
|
132
|
+
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
133
|
+
from lamindb_setup.core._hub_crud import (
|
|
134
|
+
update_instance,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
isettings = settings.instance
|
|
138
|
+
is_managed_by_hub = isettings.is_managed_by_hub
|
|
139
|
+
is_on_hub = is_managed_by_hub or isettings.is_on_hub
|
|
140
|
+
|
|
141
|
+
if is_managed_by_hub and "root" not in isettings.db:
|
|
142
|
+
# ensure we connect with the root user
|
|
143
|
+
connect(use_root_db_user=True)
|
|
144
|
+
assert "root" in (instance_db := settings.instance.db), instance_db
|
|
145
|
+
if is_on_hub:
|
|
146
|
+
# we need lamindb to be installed, otherwise we can't populate the version
|
|
147
|
+
# information in the hub
|
|
148
|
+
# this also connects
|
|
149
|
+
import lamindb
|
|
150
|
+
# this is needed to avoid connecting on importing apps inside setup_django process
|
|
151
|
+
setup_django_disable_autoconnect = disable_auto_connect(setup_django)
|
|
152
|
+
# this sets up django and deploys the migrations
|
|
153
|
+
if package_name is not None and number is not None:
|
|
154
|
+
setup_django_disable_autoconnect(
|
|
155
|
+
isettings,
|
|
156
|
+
deploy_migrations=True,
|
|
157
|
+
appname_number=(package_name, number),
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
setup_django_disable_autoconnect(isettings, deploy_migrations=True)
|
|
161
|
+
# this populates the hub
|
|
162
|
+
if is_on_hub:
|
|
163
|
+
logger.important(f"updating lamindb version in hub: {lamindb.__version__}")
|
|
164
|
+
if isettings.dialect != "sqlite":
|
|
165
|
+
update_schema_in_hub()
|
|
166
|
+
call_with_fallback_auth(
|
|
167
|
+
update_instance,
|
|
168
|
+
instance_id=isettings._id.hex,
|
|
169
|
+
instance_fields={"lamindb_version": lamindb.__version__},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
@disable_auto_connect
|
|
174
|
+
def check(cls) -> bool:
|
|
175
|
+
"""Check whether Registry definitions are in sync with migrations."""
|
|
176
|
+
import io
|
|
177
|
+
|
|
178
|
+
from django.core.management import call_command
|
|
179
|
+
|
|
180
|
+
setup_django(settings.instance)
|
|
181
|
+
|
|
182
|
+
# Capture stdout/stderr to show what migrations are needed if check fails
|
|
183
|
+
stdout = io.StringIO()
|
|
184
|
+
stderr = io.StringIO()
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
call_command(
|
|
188
|
+
"makemigrations", check_changes=True, stdout=stdout, stderr=stderr
|
|
189
|
+
)
|
|
190
|
+
except SystemExit:
|
|
191
|
+
logger.error(
|
|
192
|
+
"migrations are not in sync with ORMs, please create a migration: lamin"
|
|
193
|
+
" migrate create"
|
|
194
|
+
)
|
|
195
|
+
# Print captured output from the check
|
|
196
|
+
if stdout.getvalue():
|
|
197
|
+
logger.error(f"makemigrations --check stdout:\n{stdout.getvalue()}")
|
|
198
|
+
if stderr.getvalue():
|
|
199
|
+
logger.error(f"makemigrations --check stderr:\n{stderr.getvalue()}")
|
|
200
|
+
|
|
201
|
+
# Run makemigrations --dry-run to show what would be created
|
|
202
|
+
stdout2 = io.StringIO()
|
|
203
|
+
stderr2 = io.StringIO()
|
|
204
|
+
try:
|
|
205
|
+
call_command(
|
|
206
|
+
"makemigrations", dry_run=True, stdout=stdout2, stderr=stderr2
|
|
207
|
+
)
|
|
208
|
+
except SystemExit:
|
|
209
|
+
pass
|
|
210
|
+
if stdout2.getvalue():
|
|
211
|
+
logger.error(f"makemigrations --dry-run stdout:\n{stdout2.getvalue()}")
|
|
212
|
+
if stderr2.getvalue():
|
|
213
|
+
logger.error(f"makemigrations --dry-run stderr:\n{stderr2.getvalue()}")
|
|
214
|
+
|
|
215
|
+
return False
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
@disable_auto_connect
|
|
220
|
+
def squash(
|
|
221
|
+
cls, package_name, migration_nr, start_migration_nr: str | None = None
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Squash migrations."""
|
|
224
|
+
from django.core.management import call_command
|
|
225
|
+
|
|
226
|
+
setup_django(settings.instance)
|
|
227
|
+
if start_migration_nr is not None:
|
|
228
|
+
call_command(
|
|
229
|
+
"squashmigrations", package_name, start_migration_nr, migration_nr
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
call_command("squashmigrations", package_name, migration_nr)
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
@disable_auto_connect
|
|
236
|
+
def show(cls) -> None:
|
|
237
|
+
"""Show migrations."""
|
|
238
|
+
from django.core.management import call_command
|
|
239
|
+
|
|
240
|
+
setup_django(settings.instance)
|
|
241
|
+
call_command("showmigrations")
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def defined_migrations(cls, latest: bool = False):
|
|
245
|
+
from io import StringIO
|
|
246
|
+
|
|
247
|
+
from django.core.management import call_command
|
|
248
|
+
|
|
249
|
+
def parse_migration_output(output):
|
|
250
|
+
"""Parse the output of the showmigrations command to get migration names."""
|
|
251
|
+
lines = output.splitlines()
|
|
252
|
+
|
|
253
|
+
# Initialize an empty dict to store migration names of each module
|
|
254
|
+
migration_names = {}
|
|
255
|
+
|
|
256
|
+
# Process each line
|
|
257
|
+
for line in lines:
|
|
258
|
+
if " " not in line:
|
|
259
|
+
# CLI displays the module name in bold
|
|
260
|
+
name = line.strip().replace("\x1b[1m", "")
|
|
261
|
+
migration_names[name] = []
|
|
262
|
+
continue
|
|
263
|
+
# Strip whitespace and split the line into status and migration name
|
|
264
|
+
migration_name = line.strip().split("] ")[-1].split(" ")[0]
|
|
265
|
+
# The second part is the migration name
|
|
266
|
+
migration_names[name].append(migration_name)
|
|
267
|
+
|
|
268
|
+
return migration_names
|
|
269
|
+
|
|
270
|
+
out = StringIO()
|
|
271
|
+
call_command("showmigrations", stdout=out)
|
|
272
|
+
out.seek(0)
|
|
273
|
+
output = out.getvalue()
|
|
274
|
+
if latest:
|
|
275
|
+
return {k: v[-1] for k, v in parse_migration_output(output).items()}
|
|
276
|
+
else:
|
|
277
|
+
return parse_migration_output(output)
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def deployed_migrations(cls, latest: bool = False):
|
|
281
|
+
"""Get the list of deployed migrations from Migration table in DB."""
|
|
282
|
+
if latest:
|
|
283
|
+
latest_migrations = {}
|
|
284
|
+
with connection.cursor() as cursor:
|
|
285
|
+
# query to get the latest migration for each app that is not squashed
|
|
286
|
+
cursor.execute(
|
|
287
|
+
"""
|
|
288
|
+
SELECT app, name
|
|
289
|
+
FROM django_migrations
|
|
290
|
+
WHERE id IN (
|
|
291
|
+
SELECT MAX(id)
|
|
292
|
+
FROM django_migrations
|
|
293
|
+
WHERE name NOT LIKE '%%_squashed_%%'
|
|
294
|
+
GROUP BY app
|
|
295
|
+
)
|
|
296
|
+
"""
|
|
297
|
+
)
|
|
298
|
+
# fetch all the results
|
|
299
|
+
for app, name in cursor.fetchall():
|
|
300
|
+
latest_migrations[app] = name
|
|
301
|
+
|
|
302
|
+
return latest_migrations
|
|
303
|
+
else:
|
|
304
|
+
# import dynamically to avoid importing the heavy django.db.migrations at the root
|
|
305
|
+
from django.db.migrations.loader import MigrationLoader
|
|
306
|
+
|
|
307
|
+
# Load all migrations using Django's migration loader
|
|
308
|
+
loader = MigrationLoader(connection)
|
|
309
|
+
squashed_replacements = set()
|
|
310
|
+
for _key, migration in loader.disk_migrations.items():
|
|
311
|
+
if hasattr(migration, "replaces"):
|
|
312
|
+
squashed_replacements.update(migration.replaces)
|
|
313
|
+
|
|
314
|
+
deployed_migrations: dict = {}
|
|
315
|
+
with connection.cursor() as cursor:
|
|
316
|
+
cursor.execute(
|
|
317
|
+
"""
|
|
318
|
+
SELECT app, name, deployed
|
|
319
|
+
FROM django_migrations
|
|
320
|
+
ORDER BY app, deployed DESC
|
|
321
|
+
"""
|
|
322
|
+
)
|
|
323
|
+
for app, name, _deployed in cursor.fetchall():
|
|
324
|
+
# skip migrations that are part of a squashed migration
|
|
325
|
+
if (app, name) in squashed_replacements:
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
if app not in deployed_migrations:
|
|
329
|
+
deployed_migrations[app] = []
|
|
330
|
+
deployed_migrations[app].append(name)
|
|
331
|
+
return deployed_migrations
|