spark-sql-migrations 0.0.7__tar.gz → 0.0.8__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.
Files changed (22) hide show
  1. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/PKG-INFO +1 -1
  2. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/setup.py +1 -1
  3. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/spark_sql/spark_sql.py +72 -20
  4. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/PKG-INFO +1 -1
  5. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/LICENSE +0 -0
  6. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/MANIFEST.in +0 -0
  7. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/backend_shim.py +0 -0
  8. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/setup.cfg +0 -0
  9. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/__init__.py +0 -0
  10. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/migration_templates/default_migration_template.sql +0 -0
  11. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/migrations_initial/first_01_intial_create_cat.sql +0 -0
  12. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/migrations_initial/first_02_intial_create_schema.sql +0 -0
  13. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/migrations_initial/first_03_intial_create_state_table.sql +0 -0
  14. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/py.typed +0 -0
  15. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/spark_sql/__init__.py +0 -0
  16. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations/spark_utils.py +0 -0
  17. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/SOURCES.txt +0 -0
  18. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/dependency_links.txt +0 -0
  19. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/entry_points.txt +0 -0
  20. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/namespace_packages.txt +0 -0
  21. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/requires.txt +0 -0
  22. {spark_sql_migrations-0.0.7 → spark_sql_migrations-0.0.8}/spark_sql_migrations.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spark_sql_migrations
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Chained, idempotent SQL migrations for Spark and Databricks.
5
5
  Home-page: https://github.com/mcjug2015/spark_sql_migrations
6
6
  Author: Victor Semenov
@@ -299,5 +299,5 @@ Apache License 2.0 — see [LICENSE](LICENSE).
299
299
  },
300
300
  'python_requires': '>=3.10',
301
301
  'url': 'https://github.com/mcjug2015/spark_sql_migrations',
302
- 'version': '0.0.7',
302
+ 'version': '0.0.8',
303
303
  })
@@ -54,7 +54,7 @@ _COMMENT_OR_BLANK_RE = re.compile(r"^\s*(--.*)?$")
54
54
  @dataclass(frozen=True)
55
55
  class Migration:
56
56
  revision_id: str
57
- prev_revision_id: str | None
57
+ prev_revision_id: Optional[str]
58
58
  template_name: str
59
59
 
60
60
 
@@ -122,7 +122,7 @@ def get_ordered_migration_objs(migration_objs: List[Migration]) -> List[Migratio
122
122
  next_by_revision[migration.prev_revision_id] = migration
123
123
 
124
124
  ordered = []
125
- current: Migration | None = roots[0]
125
+ current: Optional[Migration] = roots[0]
126
126
  while current:
127
127
  ordered.append(current)
128
128
  current = next_by_revision.get(current.revision_id)
@@ -137,8 +137,14 @@ def get_ordered_migration_objs(migration_objs: List[Migration]) -> List[Migratio
137
137
  return ordered
138
138
 
139
139
 
140
- def get_migrations_list(migrations_dir: str) -> List[Migration]:
141
- """every migration in one directory, walked from its root to its head."""
140
+ def get_migrations_list(migrations_dir: str, max_revision_id: Optional[str] = None) -> List[Migration]:
141
+ """every migration in one directory, walked from its root to its head.
142
+
143
+ ``revision_id`` cuts the walk short: the chain is returned up to and including
144
+ the migration carrying that revision. A revision no migration in the directory
145
+ carries is not an error -- the whole chain comes back, same as for no revision
146
+ at all.
147
+ """
142
148
  if not os.path.isdir(migrations_dir):
143
149
  raise ValueError(f"no migrations dir at {migrations_dir};")
144
150
 
@@ -150,7 +156,15 @@ def get_migrations_list(migrations_dir: str) -> List[Migration]:
150
156
  if not migrations:
151
157
  return []
152
158
 
153
- return get_ordered_migration_objs(migrations)
159
+ ordered = get_ordered_migration_objs(migrations)
160
+ if max_revision_id is None:
161
+ return ordered
162
+
163
+ for index, migration in enumerate(ordered):
164
+ if migration.revision_id == max_revision_id:
165
+ return ordered[: index + 1]
166
+
167
+ return ordered
154
168
 
155
169
 
156
170
  def get_table_version(spark: SparkSession, cat: str, schema: str) -> Optional[Row]:
@@ -161,8 +175,10 @@ def get_table_version(spark: SparkSession, cat: str, schema: str) -> Optional[Ro
161
175
  return spark.read.table(version_table).select("version_num").head()
162
176
 
163
177
 
164
- def get_unapplied_migrations_list(spark: SparkSession, migrations_dir: str, cat: str, schema: str) -> List[Migration]:
165
- ordered = get_migrations_list(migrations_dir)
178
+ def get_unapplied_migrations_list(
179
+ spark: SparkSession, migrations_dir: str, cat: str, schema: str, max_revision_id: Optional[str] = None
180
+ ) -> List[Migration]:
181
+ ordered = get_migrations_list(migrations_dir, max_revision_id)
166
182
  current = get_table_version(spark, cat, schema)
167
183
  if not current:
168
184
  return ordered
@@ -239,9 +255,15 @@ def apply_template(output_dir: str, template, cat: str, schema: str) -> str:
239
255
 
240
256
 
241
257
  def get_ascending_letters_within_minute() -> str:
242
- micros_since_minute = datetime.datetime.now() - datetime.datetime.now().replace(second=0, microsecond=0)
243
- result = str(micros_since_minute.microseconds).translate(str.maketrans("0123456789", "ABCDEFGHIJ"))
244
- return result
258
+ """a letter stamp that sorts in the order it was taken, within one minute.
259
+
260
+ the full microseconds since the minute began, not timedelta.microseconds -- that
261
+ holds only the sub-second part and so starts over every second. zero-padded to a
262
+ fixed width, because these are compared as text once they are folder names.
263
+ """
264
+ now = datetime.datetime.now()
265
+ micros_since_minute = (now - now.replace(second=0, microsecond=0)) // datetime.timedelta(microseconds=1)
266
+ return f"{micros_since_minute:08d}".translate(str.maketrans("0123456789", "ABCDEFGHIJ"))
245
267
 
246
268
 
247
269
  def get_output_folder(output_parent_path: str) -> str:
@@ -286,7 +308,14 @@ def record_revision(spark: SparkSession, cat: str, schema: str, revision_id: str
286
308
  spark.sql(f"insert into {version_table} (version_num) values ('{revision_literal}')")
287
309
 
288
310
 
289
- def migrate_w_rev(spark: SparkSession, output_folder: str, migrations_dir: str, cat: str, schema: str) -> None:
311
+ def upgrade(
312
+ spark: SparkSession,
313
+ output_folder: str,
314
+ migrations_dir: str,
315
+ cat: str,
316
+ schema: str,
317
+ max_revision_id: Optional[str] = None,
318
+ ) -> None:
290
319
  """
291
320
  TODO XXX permit applying up to a revision below the head revision
292
321
  TODO XXX add backwards migrations, permit migrating backwards
@@ -294,7 +323,9 @@ def migrate_w_rev(spark: SparkSession, output_folder: str, migrations_dir: str,
294
323
  if not os.path.isdir(migrations_dir):
295
324
  logger.info(f"no chain at {migrations_dir}; skipping it;")
296
325
  return
297
- unapplied = get_unapplied_migrations_list(spark, migrations_dir, cat=cat, schema=schema)
326
+ unapplied = get_unapplied_migrations_list(
327
+ spark, migrations_dir, cat=cat, schema=schema, max_revision_id=max_revision_id
328
+ )
298
329
 
299
330
  logger.info(
300
331
  f"{len(unapplied)} unapplied migrations; "
@@ -308,7 +339,14 @@ def migrate_w_rev(spark: SparkSession, output_folder: str, migrations_dir: str,
308
339
  logger.info(f"{migration.template_name} done; now at {migration.revision_id};")
309
340
 
310
341
 
311
- def run_migrations(spark: SparkSession, cat: str, schema: str, output_folder: str, migrations_dir: str) -> None:
342
+ def run_upgrade(
343
+ spark: SparkSession,
344
+ cat: str,
345
+ schema: str,
346
+ output_folder: str,
347
+ migrations_dir: str,
348
+ max_revision_id: Optional[str] = None,
349
+ ) -> None:
312
350
  initial_output_folder = os.path.join(output_folder, INITIAL_MIGRATIONS_PACKAGE_PATH)
313
351
  client_output_folder = os.path.join(output_folder, CLIENT_OUTPUT_DIRNAME)
314
352
  os.makedirs(initial_output_folder, exist_ok=True)
@@ -318,17 +356,25 @@ def run_migrations(spark: SparkSession, cat: str, schema: str, output_folder: st
318
356
  logger.info(f"{version_table} already exists; skipping the initial migrations;")
319
357
  else:
320
358
  migrate_initial(spark, initial_output_folder, cat, schema)
321
- migrate_w_rev(spark, client_output_folder, migrations_dir, cat, schema)
359
+ upgrade(spark, client_output_folder, migrations_dir, cat, schema, max_revision_id)
322
360
 
323
361
 
324
- def main(cat: str, schema: str, migrations_dir: str, output_parent_path: str | None = None) -> None:
362
+ def main(
363
+ cat: str,
364
+ schema: str,
365
+ migrations_dir: str,
366
+ output_parent_path: Optional[str] = None,
367
+ max_revision_id: Optional[str] = None,
368
+ ) -> None:
325
369
  output_folder = get_output_folder(output_parent_path or os.path.join(os.getcwd(), "migrations_out"))
326
370
  os.makedirs(output_folder)
327
- run_migrations(get_spark(), cat, schema, output_folder, migrations_dir)
371
+ run_upgrade(get_spark(), cat, schema, output_folder, migrations_dir, max_revision_id)
328
372
 
329
373
 
330
- def _cli_main(cat: str, schema: str, migrations_dir: str) -> None: # pragma: no cover
331
- main(cat, schema, migrations_dir)
374
+ def _cli_main(
375
+ cat: str, schema: str, migrations_dir: str, max_revision_id: Optional[str] = None
376
+ ) -> None: # pragma: no cover
377
+ main(cat, schema, migrations_dir, max_revision_id=max_revision_id)
332
378
 
333
379
 
334
380
  def _cli_create_new_migration(
@@ -341,10 +387,16 @@ def build_parser() -> argparse.ArgumentParser: # pragma: no cover
341
387
  parser = argparse.ArgumentParser(description="spark_sql_migrations migration utilities")
342
388
  subparsers = parser.add_subparsers(dest="command", required=True)
343
389
 
344
- p_run = subparsers.add_parser("run", help="apply all pending migrations")
390
+ p_run = subparsers.add_parser("run", help="upgrade to head/apply pending migrations")
345
391
  p_run.add_argument("--cat", type=str, default="spark_catalog")
346
392
  p_run.add_argument("--schema", type=str, default="default")
347
393
  p_run.add_argument("--migrations-dir", type=str, required=True, help="the directory holding the migration chain")
394
+ p_run.add_argument(
395
+ "--max-revision-id",
396
+ type=str,
397
+ default=None,
398
+ help="apply the migration carrying this revision_id and stop; defaults to applying the chain to its head",
399
+ )
348
400
  p_run.set_defaults(func=_cli_main)
349
401
 
350
402
  p_create = subparsers.add_parser("create_new_migration", help="create a new migration from the template")
@@ -370,7 +422,7 @@ def build_parser() -> argparse.ArgumentParser: # pragma: no cover
370
422
  return parser
371
423
 
372
424
 
373
- def cli(argv: list[str] | None = None) -> None: # pragma: no cover
425
+ def cli(argv: Optional[list[str]] = None) -> None: # pragma: no cover
374
426
  """the `spark-sql-migrations` console command.
375
427
 
376
428
  an application entry point, so unlike the rest of this package it configures
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spark_sql_migrations
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Chained, idempotent SQL migrations for Spark and Databricks.
5
5
  Home-page: https://github.com/mcjug2015/spark_sql_migrations
6
6
  Author: Victor Semenov