Skip to content

Commit

Permalink
Merge pull request #161 from 0Hughman0/migrate
Browse files Browse the repository at this point in the history
Creating migration tools for 0.2.x to 0.3.x
  • Loading branch information
0Hughman0 authored Aug 26, 2024
2 parents 547d54d + ae6783f commit fe5cf6b
Show file tree
Hide file tree
Showing 31 changed files with 1,074 additions and 114 deletions.
2 changes: 0 additions & 2 deletions cassini/compat/__init__.py

This file was deleted.

112 changes: 0 additions & 112 deletions cassini/compat/update.py

This file was deleted.

95 changes: 95 additions & 0 deletions cassini/migrate/V0_1toV0_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import nbformat
import re

from .base import BaseMigrator, cell_processor


class V0_1toV0_2(BaseMigrator):
def __init__(self, project):
self.project = project
self.home = project.home

@cell_processor
def fix_imports(cell):
text = cell["source"]
text = text.replace("from htools.exp import g", "from htools.scify import *")
cell["source"] = text

Check warning on line 16 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L14-L16

Added lines #L14 - L16 were not covered by tests

@cell_processor
def fix_g_references(cell):
text = cell["source"]

Check warning on line 20 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L20

Added line #L20 was not covered by tests

def replace(match):
return f"env.{match.group(1)}"

Check warning on line 23 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L22-L23

Added lines #L22 - L23 were not covered by tests

cell["source"] = re.sub("g\.(.+)", replace, text)

Check warning on line 25 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L25

Added line #L25 was not covered by tests

@cell_processor
def replace_mksmpl(cell):
pattern = "%%mksmpl (.+)"
text = cell["source"]

Check warning on line 30 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L29-L30

Added lines #L29 - L30 were not covered by tests

match = re.search(pattern, text)
if match:
cell["source"] = f"smpl = env('{match.group(1)}')\nsmpl.header()"

Check warning on line 34 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L32-L34

Added lines #L32 - L34 were not covered by tests

@cell_processor
def replace_smplconc(cell):
text = cell["source"]
cell["source"] = text.replace("%%smplconc", "%%conc")

Check warning on line 39 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L38-L39

Added lines #L38 - L39 were not covered by tests

@cell_processor
def replace_expconc(cell):
text = cell["source"]
cell["source"] = text.replace("%%expconc", "%%conc")

Check warning on line 44 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L43-L44

Added lines #L43 - L44 were not covered by tests

@cell_processor
def fix_meta_store(cell):
text = cell["source"]
pattern = "smpl\.write_meta\('(.+)', (.+)\)"

Check warning on line 49 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L48-L49

Added lines #L48 - L49 were not covered by tests

def replace(match):
return f"smpl.{match.group(1)} = {match.group(2)}"

Check warning on line 52 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L51-L52

Added lines #L51 - L52 were not covered by tests

cell["source"] = re.sub(pattern, replace, text)

Check warning on line 54 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L54

Added line #L54 was not covered by tests

def walk_smpls(self):
for wp in self.home:
for exp in wp:
for smpl in exp:
yield smpl

Check warning on line 60 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L58-L60

Added lines #L58 - L60 were not covered by tests

def migrate(self):
for smpl in self.walk_smpls():
if not smpl.file.exists():
print(f"No file found for {smpl} - skippiping")
continue

Check warning on line 66 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L64-L66

Added lines #L64 - L66 were not covered by tests

print("Fixing", smpl)
with open(smpl.file, "rb") as f:
nb = nbformat.read(f, 4)

Check warning on line 70 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L68-L70

Added lines #L68 - L70 were not covered by tests

print("Backing up old file")
try:
new_name = smpl.file.rename(

Check warning on line 74 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L72-L74

Added lines #L72 - L74 were not covered by tests
smpl.file.with_name(f"{smpl.name}-old.ipynb")
)
except FileExistsError:
print(f"Already updated {smpl} - skipping")
continue

Check warning on line 79 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L77-L79

Added lines #L77 - L79 were not covered by tests

try:
print("Applying processors")
for cell in nb["cells"]:
for processor in self.cell_processors:
processor(cell)
print("Success")
print("Errors: ", nbformat.validate(nb, version=4))

Check warning on line 87 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L81-L87

Added lines #L81 - L87 were not covered by tests

print("Writing new file")
with open(smpl.file, "w", encoding="utf-8") as f:
nbformat.write(nb, f, 4)
print("Success")
except Exception:
print("Exception occured, rolling back")
new_name.rename(smpl.file.name)

Check warning on line 95 in cassini/migrate/V0_1toV0_2.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/V0_1toV0_2.py#L89-L95

Added lines #L89 - L95 were not covered by tests
44 changes: 44 additions & 0 deletions cassini/migrate/V0_2toV0_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import json
import datetime
import shutil

from .base import BaseMigrator


class V0_2toV0_3(BaseMigrator):
def __init__(self, project) -> None:
self.project = project
self.home = project.home

def migrate(self):
from cassini import NotebookTierBase

for tier in self.walk_tiers():
if not isinstance(tier, NotebookTierBase):
continue
else:
backup_path = shutil.copy(
tier.meta_file, tier.meta_file.with_suffix(".backup")
)
try:
self.migrate_meta(tier)
except Exception:
raise RuntimeError(
"Error occured, please restore from backup", backup_path
)
else:
print("Successfully migrated, removing backup")
backup_path.unlink()

def migrate_meta(self, tier):
with open(tier.meta_file, "r") as fs:
meta = json.load(fs)

started = meta.get("started")

if started:
started_dt = datetime.datetime.strptime(started, "%d/%m/%Y")
meta["started"] = str(started_dt)

with open(tier.meta_file, "w") as fs:
json.dump(meta, fs)
Empty file added cassini/migrate/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions cassini/migrate/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import sys
import argparse
from typing import List

from cassini.utils import find_project

from .base import BaseMigrator
from .V0_1toV0_2 import V0_1toV0_2
from .V0_2toV0_3 import V0_2toV0_3


migrators = {("0.1", "0.2"): V0_1toV0_2, ("0.2", "0.3"): V0_2toV0_3}


def main(args: List[str]) -> BaseMigrator:
parser = argparse.ArgumentParser(
description=(
"Migration tool for cassini projects. "
"It's not that fancy, so sequential updates may be needed."
)
)
parser.add_argument(
"old", choices=["0.1", "0.2"], help="which version to migrate from"
)
parser.add_argument(
"new", choices=["0.2", "0.3"], help="which version to migrate to"
)
parser.add_argument(
"--cassini-project",
default="project.py:project",
help="cassini project to migrate, see cassini.utils.find_project for possible forms",
)

out = parser.parse_args(args)

project = find_project(out.cassini_project)

print("Found project", project)

old, new = out.old, out.new

print("Looking for migrator for", old, "to", new)

try:
migrator = migrators[(old, new)](project)
except KeyError:
raise ValueError(
"No migator available for this combination, options are", list(migrators)
)

print("Found", migrator)

print("Running migrator")

migrator.migrate()

print("Success")

return migrator


if __name__ == "__main__":
main(sys.argv[1:])

Check warning on line 63 in cassini/migrate/__main__.py

View check run for this annotation

Codecov / codecov/patch

cassini/migrate/__main__.py#L63

Added line #L63 was not covered by tests
40 changes: 40 additions & 0 deletions cassini/migrate/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from abc import abstractmethod
from typing import Callable, List

from nbformat import NotebookNode

CellProcessor = Callable[[NotebookNode], None]


class BaseMigrator:
cell_processors: List[CellProcessor] = []

def __init_subclass__(cls) -> None:
cls.cell_processors = []

@classmethod
def _cell_processor(cls, f: CellProcessor):
cls.cell_processors.append(f)
return f

def walk_tiers(self):
yield self.home

for wp in self.home:
yield wp

for exp in wp:
yield exp

for smpl in exp:
yield smpl

@abstractmethod
def migrate(self):
"""
Perform the migration.
"""


def cell_processor(f):
return BaseMigrator._cell_processor(f)
Loading

0 comments on commit fe5cf6b

Please sign in to comment.