mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
165 lines
5.4 KiB
Python
Executable File
165 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import dataclasses
|
|
import importlib
|
|
import os
|
|
import pathlib
|
|
import re
|
|
|
|
import click
|
|
from django.apps import apps
|
|
|
|
from sentry.runner import configure
|
|
|
|
configure()
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class MigrationMeta:
|
|
number: int
|
|
name: str
|
|
path: str
|
|
|
|
@property
|
|
def full_name(self) -> str:
|
|
return f"{self.number:04}_{self.name}"
|
|
|
|
@classmethod
|
|
def from_filename(cls, filepath: str) -> "MigrationMeta":
|
|
filename = os.path.basename(filepath)
|
|
name, _ = filename.split(".", 2)
|
|
number, name = name.split("_", 1)
|
|
number = int(number)
|
|
return cls(number=number, name=name, path=filepath)
|
|
|
|
|
|
def load_module(filepath: str):
|
|
module_name = os.path.basename(filepath)
|
|
spec = importlib.util.spec_from_loader(
|
|
module_name, importlib.machinery.SourceFileLoader(module_name, filepath)
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def find_migration(migrations_path: str, migration: str) -> MigrationMeta:
|
|
matches = list(pathlib.Path(migrations_path).glob(f"*{migration}*"))
|
|
if len(matches) > 1:
|
|
click.echo(f"Found multiple migrations matching {migration}")
|
|
for match in matches:
|
|
click.echo(f"- {match}")
|
|
click.echo("Try again with a more specific pattern")
|
|
raise click.Abort()
|
|
|
|
if len(matches) == 0:
|
|
click.echo(f"Could not find migration matching {migration}")
|
|
raise click.Abort()
|
|
|
|
return MigrationMeta.from_filename(str(matches[0].resolve()))
|
|
|
|
|
|
def find_highest_migration(migrations_path: str, renamed_migration: MigrationMeta) -> MigrationMeta:
|
|
highest = 0
|
|
found = None
|
|
matches = list(pathlib.Path(migrations_path).glob("[0-9]*"))
|
|
for match in matches:
|
|
meta = MigrationMeta.from_filename(str(match.resolve()))
|
|
if meta.number > highest and meta.full_name != renamed_migration.full_name:
|
|
highest = meta.number
|
|
found = meta
|
|
if not found:
|
|
click.echo("Could not find the head migration")
|
|
raise click.Abort()
|
|
|
|
click.echo(f"> Current head migration is {found.full_name}")
|
|
return found
|
|
|
|
|
|
@click.command()
|
|
@click.argument("migration", required=True)
|
|
@click.argument("app_label", default="sentry")
|
|
def main(migration: str, app_label: str):
|
|
"""
|
|
Update a migration to the top of the migration history.
|
|
|
|
migration - The name or number of the migration.
|
|
app_label - The name of the django app the migration is in. Defaults to sentry
|
|
|
|
Will do the following:
|
|
|
|
- Rename the migration file.
|
|
- Update `dependencies` in the migration to point at the highest migration.
|
|
- Update the name of the related tests/migration if it exists.
|
|
- Update migrations_lockfile.txt.
|
|
|
|
"""
|
|
app_path = apps.get_app_config(app_label).path
|
|
migrations_path = os.path.join(app_path, "migrations")
|
|
|
|
migration_meta = find_migration(migrations_path, migration)
|
|
current_head = find_highest_migration(migrations_path, migration_meta)
|
|
|
|
# Create an instance of the migration so that we can read instance properties.
|
|
module = load_module(migration_meta.path)
|
|
migration_instance = module.Migration("", app_label)
|
|
dependencies = migration_instance.dependencies
|
|
|
|
new_name = migration_meta.full_name.replace(
|
|
str(migration_meta.number), str(current_head.number + 1)
|
|
)
|
|
click.echo(f"> Updating migration {migration_meta.full_name} to {new_name}")
|
|
with open(migration_meta.path) as f:
|
|
contents = f.read()
|
|
for dep in dependencies:
|
|
if dep[0] == app_label:
|
|
contents = contents.replace(dep[1], current_head.full_name)
|
|
with open(migration_meta.path, "w") as f:
|
|
f.write(contents)
|
|
|
|
# Rename the file.
|
|
os.rename(migration_meta.path, os.path.join(migrations_path, f"{new_name}.py"))
|
|
click.echo("> Migration file rename complete")
|
|
|
|
# Update lockfile
|
|
lockfile = os.path.join(app_path, "..", "..", "migrations_lockfile.txt")
|
|
with open(lockfile) as f:
|
|
contents = f.read()
|
|
contents = contents.replace(current_head.full_name, new_name)
|
|
with open(lockfile, "w") as f:
|
|
f.write(contents)
|
|
click.echo("> Updated migrations_lockfile.txt")
|
|
|
|
# Rename test if it exists.
|
|
test_file_prefix = f"*{migration_meta.number}*"
|
|
migration_test_path = os.path.join(app_path, "..", "..", "tests", "sentry", "migrations")
|
|
matches = list(pathlib.Path(migration_test_path).glob(test_file_prefix))
|
|
if len(matches) == 1:
|
|
click.echo("> Updating migration test file to & from attributes")
|
|
test_path = str(matches[0].resolve())
|
|
with open(test_path) as f:
|
|
contents = f.read()
|
|
contents = re.sub(
|
|
r"(migrate_from\s+=\s+\")([^\"]+)(\")",
|
|
lambda matches: f"{matches.group(1)}{current_head.full_name}{matches.group(3)}",
|
|
contents,
|
|
)
|
|
contents = re.sub(
|
|
r"(migrate_to\s+=\s+\")([^\"]+)(\")",
|
|
lambda matches: f"{matches.group(1)}{new_name}{matches.group(3)}",
|
|
contents,
|
|
)
|
|
with open(test_path, "w") as f:
|
|
f.write(contents)
|
|
click.echo("> Renaming test file")
|
|
os.rename(
|
|
str(matches[0].resolve()), os.path.join(migration_test_path, f"test_{new_name}.py")
|
|
)
|
|
else:
|
|
click.echo("> Could not find a migration test file")
|
|
|
|
click.echo("All done!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|