spark-sql-migrations 0.0.6__tar.gz → 0.0.7__tar.gz
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.
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/PKG-INFO +1 -1
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/setup.py +1 -1
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/spark_sql/spark_sql.py +39 -31
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/spark_utils.py +3 -3
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/PKG-INFO +1 -1
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/LICENSE +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/MANIFEST.in +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/backend_shim.py +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/setup.cfg +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/__init__.py +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/migration_templates/default_migration_template.sql +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/migrations_initial/first_01_intial_create_cat.sql +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/migrations_initial/first_02_intial_create_schema.sql +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/migrations_initial/first_03_intial_create_state_table.sql +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/py.typed +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/spark_sql/__init__.py +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/SOURCES.txt +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/dependency_links.txt +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/entry_points.txt +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/namespace_packages.txt +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/requires.txt +0 -0
- {spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/top_level.txt +0 -0
|
@@ -27,8 +27,11 @@ import os
|
|
|
27
27
|
import re
|
|
28
28
|
import uuid
|
|
29
29
|
from dataclasses import dataclass
|
|
30
|
+
from typing import Any, Dict, List, Optional
|
|
30
31
|
|
|
31
32
|
from jinja2 import Environment, FileSystemLoader, PackageLoader, select_autoescape
|
|
33
|
+
from pyspark.sql.session import SparkSession
|
|
34
|
+
from pyspark.sql.types import Row
|
|
32
35
|
|
|
33
36
|
from spark_sql_migrations.spark_utils import get_spark, is_dbr
|
|
34
37
|
|
|
@@ -55,13 +58,13 @@ class Migration:
|
|
|
55
58
|
template_name: str
|
|
56
59
|
|
|
57
60
|
|
|
58
|
-
def get_default_template_path():
|
|
61
|
+
def get_default_template_path() -> str:
|
|
59
62
|
"""the template this package ships; callers may pass their own instead."""
|
|
60
63
|
return os.path.join(os.path.dirname(__file__), "..", TEMPLATES_PACKAGE_PATH, DEFAULT_TEMPLATE_NAME)
|
|
61
64
|
|
|
62
65
|
|
|
63
|
-
def _parse_migration(migrations_dir, template_name):
|
|
64
|
-
headers = {}
|
|
66
|
+
def _parse_migration(migrations_dir: str, template_name: str) -> Migration:
|
|
67
|
+
headers: Dict[str, Any | str] = {}
|
|
65
68
|
migration_file_path = os.path.join(migrations_dir, template_name)
|
|
66
69
|
if os.path.getsize(migration_file_path) == 0:
|
|
67
70
|
raise ValueError(f"{template_name} is empty;")
|
|
@@ -84,8 +87,8 @@ def _parse_migration(migrations_dir, template_name):
|
|
|
84
87
|
)
|
|
85
88
|
|
|
86
89
|
|
|
87
|
-
def get_ordered_migration_objs(migration_objs):
|
|
88
|
-
by_revision = {}
|
|
90
|
+
def get_ordered_migration_objs(migration_objs: List[Migration]) -> List[Migration]:
|
|
91
|
+
by_revision: Dict[str, Migration] = {}
|
|
89
92
|
for migration in migration_objs:
|
|
90
93
|
clash = by_revision.get(migration.revision_id)
|
|
91
94
|
if clash:
|
|
@@ -102,7 +105,7 @@ def get_ordered_migration_objs(migration_objs):
|
|
|
102
105
|
f"found {[m.template_name for m in roots]};"
|
|
103
106
|
)
|
|
104
107
|
|
|
105
|
-
next_by_revision = {}
|
|
108
|
+
next_by_revision: Dict[str, Migration] = {}
|
|
106
109
|
for migration in migration_objs:
|
|
107
110
|
if not migration.prev_revision_id:
|
|
108
111
|
continue
|
|
@@ -119,7 +122,7 @@ def get_ordered_migration_objs(migration_objs):
|
|
|
119
122
|
next_by_revision[migration.prev_revision_id] = migration
|
|
120
123
|
|
|
121
124
|
ordered = []
|
|
122
|
-
current = roots[0]
|
|
125
|
+
current: Migration | None = roots[0]
|
|
123
126
|
while current:
|
|
124
127
|
ordered.append(current)
|
|
125
128
|
current = next_by_revision.get(current.revision_id)
|
|
@@ -134,7 +137,7 @@ def get_ordered_migration_objs(migration_objs):
|
|
|
134
137
|
return ordered
|
|
135
138
|
|
|
136
139
|
|
|
137
|
-
def get_migrations_list(migrations_dir):
|
|
140
|
+
def get_migrations_list(migrations_dir: str) -> List[Migration]:
|
|
138
141
|
"""every migration in one directory, walked from its root to its head."""
|
|
139
142
|
if not os.path.isdir(migrations_dir):
|
|
140
143
|
raise ValueError(f"no migrations dir at {migrations_dir};")
|
|
@@ -150,7 +153,7 @@ def get_migrations_list(migrations_dir):
|
|
|
150
153
|
return get_ordered_migration_objs(migrations)
|
|
151
154
|
|
|
152
155
|
|
|
153
|
-
def get_table_version(spark, cat: str, schema: str):
|
|
156
|
+
def get_table_version(spark: SparkSession, cat: str, schema: str) -> Optional[Row]:
|
|
154
157
|
"""the revision VERSION_TABLE records, or None if it holds no row yet."""
|
|
155
158
|
version_table = f"{cat}.{schema}.{VERSION_TABLE}"
|
|
156
159
|
if not spark.catalog.tableExists(version_table):
|
|
@@ -158,7 +161,7 @@ def get_table_version(spark, cat: str, schema: str):
|
|
|
158
161
|
return spark.read.table(version_table).select("version_num").head()
|
|
159
162
|
|
|
160
163
|
|
|
161
|
-
def get_unapplied_migrations_list(spark, migrations_dir, cat: str, schema: str):
|
|
164
|
+
def get_unapplied_migrations_list(spark: SparkSession, migrations_dir: str, cat: str, schema: str) -> List[Migration]:
|
|
162
165
|
ordered = get_migrations_list(migrations_dir)
|
|
163
166
|
current = get_table_version(spark, cat, schema)
|
|
164
167
|
if not current:
|
|
@@ -180,7 +183,7 @@ def get_unapplied_migrations_list(spark, migrations_dir, cat: str, schema: str):
|
|
|
180
183
|
raise ValueError(f"{VERSION_TABLE} is at {current_revision_id}, which matches no migration in {migrations_dir};")
|
|
181
184
|
|
|
182
185
|
|
|
183
|
-
def gate_on_is_dbr(migration_content):
|
|
186
|
+
def gate_on_is_dbr(migration_content: str) -> str:
|
|
184
187
|
"""wrap a migration's body in the jinja conditional, below its headers.
|
|
185
188
|
|
|
186
189
|
the revision headers stay outside it: they are read off the file as comments,
|
|
@@ -199,7 +202,7 @@ def gate_on_is_dbr(migration_content):
|
|
|
199
202
|
|
|
200
203
|
def create_new_migration(
|
|
201
204
|
message: str, template_path: str, output_path: str, truncate_slug_length: int = 40, add_is_dbr: bool = False
|
|
202
|
-
):
|
|
205
|
+
) -> None:
|
|
203
206
|
rev_id = uuid.uuid4().hex[-12:]
|
|
204
207
|
|
|
205
208
|
date_prefix = datetime.datetime.today().strftime("%y%m%d")
|
|
@@ -228,32 +231,32 @@ def create_new_migration(
|
|
|
228
231
|
migration_file.write(default_migration_content)
|
|
229
232
|
|
|
230
233
|
|
|
231
|
-
def apply_template(output_dir, template, cat: str, schema: str):
|
|
234
|
+
def apply_template(output_dir: str, template, cat: str, schema: str) -> str:
|
|
232
235
|
result_sql = template.render(cat=cat, schema=schema, is_dbr=is_dbr())
|
|
233
236
|
with open(os.path.join(output_dir, template.name.replace(".sql", "_primed.sql")), "w") as file_handle:
|
|
234
237
|
file_handle.write(result_sql)
|
|
235
238
|
return result_sql
|
|
236
239
|
|
|
237
240
|
|
|
238
|
-
def get_ascending_letters_within_minute():
|
|
241
|
+
def get_ascending_letters_within_minute() -> str:
|
|
239
242
|
micros_since_minute = datetime.datetime.now() - datetime.datetime.now().replace(second=0, microsecond=0)
|
|
240
243
|
result = str(micros_since_minute.microseconds).translate(str.maketrans("0123456789", "ABCDEFGHIJ"))
|
|
241
244
|
return result
|
|
242
245
|
|
|
243
246
|
|
|
244
|
-
def get_output_folder(output_parent_path):
|
|
247
|
+
def get_output_folder(output_parent_path: str) -> str:
|
|
245
248
|
folder_name = f"{datetime.datetime.today().strftime('%Y%m%d_%H%M')}_{get_ascending_letters_within_minute()}"
|
|
246
249
|
return os.path.join(output_parent_path, folder_name)
|
|
247
250
|
|
|
248
251
|
|
|
249
|
-
def has_no_statements(rendered_sql):
|
|
252
|
+
def has_no_statements(rendered_sql: str) -> bool:
|
|
250
253
|
for line in rendered_sql.splitlines():
|
|
251
254
|
if not _COMMENT_OR_BLANK_RE.match(line):
|
|
252
255
|
return False
|
|
253
256
|
return True
|
|
254
257
|
|
|
255
258
|
|
|
256
|
-
def render_and_apply(spark, output_folder, template, cat: str, schema: str):
|
|
259
|
+
def render_and_apply(spark: SparkSession, output_folder: str, template, cat: str, schema: str) -> None:
|
|
257
260
|
"""render one migration and run it, unless this engine was gated out of it."""
|
|
258
261
|
result_sql = apply_template(output_folder, template, cat=cat, schema=schema)
|
|
259
262
|
if has_no_statements(result_sql):
|
|
@@ -262,7 +265,7 @@ def render_and_apply(spark, output_folder, template, cat: str, schema: str):
|
|
|
262
265
|
spark.sql(result_sql)
|
|
263
266
|
|
|
264
267
|
|
|
265
|
-
def migrate_initial(spark, output_folder, cat: str, schema: str):
|
|
268
|
+
def migrate_initial(spark: SparkSession, output_folder: str, cat: str, schema: str) -> None:
|
|
266
269
|
"""apply the package's own bootstrap chain, in filename order rather than by revision."""
|
|
267
270
|
env = Environment(
|
|
268
271
|
loader=PackageLoader(package_name=PACKAGE_NAME, package_path=INITIAL_MIGRATIONS_PACKAGE_PATH),
|
|
@@ -276,14 +279,14 @@ def migrate_initial(spark, output_folder, cat: str, schema: str):
|
|
|
276
279
|
logger.info(f"rendered {len(all_templates)} initial migrations;")
|
|
277
280
|
|
|
278
281
|
|
|
279
|
-
def record_revision(spark, cat: str, schema: str, revision_id: str):
|
|
282
|
+
def record_revision(spark: SparkSession, cat: str, schema: str, revision_id: str) -> None:
|
|
280
283
|
version_table = f"{cat}.{schema}.{VERSION_TABLE}"
|
|
281
284
|
revision_literal = revision_id.replace("'", "''")
|
|
282
285
|
spark.sql(f"delete from {version_table}")
|
|
283
286
|
spark.sql(f"insert into {version_table} (version_num) values ('{revision_literal}')")
|
|
284
287
|
|
|
285
288
|
|
|
286
|
-
def migrate_w_rev(spark, output_folder, migrations_dir, cat: str, schema: str):
|
|
289
|
+
def migrate_w_rev(spark: SparkSession, output_folder: str, migrations_dir: str, cat: str, schema: str) -> None:
|
|
287
290
|
"""
|
|
288
291
|
TODO XXX permit applying up to a revision below the head revision
|
|
289
292
|
TODO XXX add backwards migrations, permit migrating backwards
|
|
@@ -305,7 +308,7 @@ def migrate_w_rev(spark, output_folder, migrations_dir, cat: str, schema: str):
|
|
|
305
308
|
logger.info(f"{migration.template_name} done; now at {migration.revision_id};")
|
|
306
309
|
|
|
307
310
|
|
|
308
|
-
def run_migrations(spark, cat, schema, output_folder, migrations_dir):
|
|
311
|
+
def run_migrations(spark: SparkSession, cat: str, schema: str, output_folder: str, migrations_dir: str) -> None:
|
|
309
312
|
initial_output_folder = os.path.join(output_folder, INITIAL_MIGRATIONS_PACKAGE_PATH)
|
|
310
313
|
client_output_folder = os.path.join(output_folder, CLIENT_OUTPUT_DIRNAME)
|
|
311
314
|
os.makedirs(initial_output_folder, exist_ok=True)
|
|
@@ -318,39 +321,44 @@ def run_migrations(spark, cat, schema, output_folder, migrations_dir):
|
|
|
318
321
|
migrate_w_rev(spark, client_output_folder, migrations_dir, cat, schema)
|
|
319
322
|
|
|
320
323
|
|
|
321
|
-
def main(cat, schema, migrations_dir, output_parent_path=None):
|
|
324
|
+
def main(cat: str, schema: str, migrations_dir: str, output_parent_path: str | None = None) -> None:
|
|
322
325
|
output_folder = get_output_folder(output_parent_path or os.path.join(os.getcwd(), "migrations_out"))
|
|
323
326
|
os.makedirs(output_folder)
|
|
324
327
|
run_migrations(get_spark(), cat, schema, output_folder, migrations_dir)
|
|
325
328
|
|
|
326
329
|
|
|
327
|
-
def _cli_main(cat, schema, migrations_dir): # pragma: no cover
|
|
330
|
+
def _cli_main(cat: str, schema: str, migrations_dir: str) -> None: # pragma: no cover
|
|
328
331
|
main(cat, schema, migrations_dir)
|
|
329
332
|
|
|
330
333
|
|
|
331
|
-
def _cli_create_new_migration(
|
|
334
|
+
def _cli_create_new_migration(
|
|
335
|
+
message: str, output_path: str, template_path: str, add_is_dbr: bool
|
|
336
|
+
) -> None: # pragma: no cover
|
|
332
337
|
create_new_migration(message, template_path or get_default_template_path(), output_path, add_is_dbr=add_is_dbr)
|
|
333
338
|
|
|
334
339
|
|
|
335
|
-
def build_parser(): # pragma: no cover
|
|
340
|
+
def build_parser() -> argparse.ArgumentParser: # pragma: no cover
|
|
336
341
|
parser = argparse.ArgumentParser(description="spark_sql_migrations migration utilities")
|
|
337
342
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
338
343
|
|
|
339
344
|
p_run = subparsers.add_parser("run", help="apply all pending migrations")
|
|
340
|
-
p_run.add_argument("--cat", default="spark_catalog")
|
|
341
|
-
p_run.add_argument("--schema", default="default")
|
|
342
|
-
p_run.add_argument("--migrations-dir", required=True, help="the directory holding the migration chain")
|
|
345
|
+
p_run.add_argument("--cat", type=str, default="spark_catalog")
|
|
346
|
+
p_run.add_argument("--schema", type=str, default="default")
|
|
347
|
+
p_run.add_argument("--migrations-dir", type=str, required=True, help="the directory holding the migration chain")
|
|
343
348
|
p_run.set_defaults(func=_cli_main)
|
|
344
349
|
|
|
345
350
|
p_create = subparsers.add_parser("create_new_migration", help="create a new migration from the template")
|
|
346
|
-
p_create.add_argument("--message", required=True)
|
|
351
|
+
p_create.add_argument("--message", type=str, required=True)
|
|
347
352
|
p_create.add_argument(
|
|
348
353
|
"--output-path",
|
|
354
|
+
type=str,
|
|
349
355
|
required=True,
|
|
350
356
|
help="the chain directory to write the new migration into. if sql files are in there, the last sql's rev_id"
|
|
351
357
|
"will be set as the new migrations prev_rev_id.",
|
|
352
358
|
)
|
|
353
|
-
p_create.add_argument(
|
|
359
|
+
p_create.add_argument(
|
|
360
|
+
"--template-path", type=str, default=None, help="defaults to the template shipped in this package"
|
|
361
|
+
)
|
|
354
362
|
# the %% are argparse's: it interpolates help strings
|
|
355
363
|
p_create.add_argument(
|
|
356
364
|
"--add-is-dbr",
|
|
@@ -362,7 +370,7 @@ def build_parser(): # pragma: no cover
|
|
|
362
370
|
return parser
|
|
363
371
|
|
|
364
372
|
|
|
365
|
-
def cli(argv=None): # pragma: no cover
|
|
373
|
+
def cli(argv: list[str] | None = None) -> None: # pragma: no cover
|
|
366
374
|
"""the `spark-sql-migrations` console command.
|
|
367
375
|
|
|
368
376
|
an application entry point, so unlike the rest of this package it configures
|
{spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/spark_utils.py
RENAMED
|
@@ -11,7 +11,7 @@ METASTORE_DIR_ENV_VAR = "SPARK_METASTORE_DIR"
|
|
|
11
11
|
DEFAULT_WAREHOUSE_DIRNAME = "spark-warehouse"
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def is_dbr():
|
|
14
|
+
def is_dbr() -> bool:
|
|
15
15
|
try:
|
|
16
16
|
from pyspark.dbutils import DBUtils # type: ignore # noqa: F401
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ def is_dbr():
|
|
|
21
21
|
return False
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def get_warehouse_dir():
|
|
24
|
+
def get_warehouse_dir() -> str:
|
|
25
25
|
"""where local (non-DBR) spark keeps its warehouse.
|
|
26
26
|
|
|
27
27
|
resolved against the caller's cwd, never against __file__: installed from a
|
|
@@ -34,7 +34,7 @@ def get_warehouse_dir():
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def get_spark(use_dbc=False):
|
|
37
|
+
def get_spark(use_dbc: bool = False) -> SparkSession:
|
|
38
38
|
if use_dbc or is_dbr():
|
|
39
39
|
from databricks.connect.session import DatabricksSession # type: ignore # pants: no-infer-dep
|
|
40
40
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations/spark_sql/__init__.py
RENAMED
|
File without changes
|
{spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spark_sql_migrations-0.0.6 → spark_sql_migrations-0.0.7}/spark_sql_migrations.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|