Migration¶
A small legacy telemetry script, run through ppy migrate instead of
ppy convert. The script is dynamic in the usual harmless ways:
importlib.import_module, a globals() write, and setattr/getattr on
constant names.
Run it¶
What it prints¶
ppy migrate legacy.py --diff
52 lines
--- ./legacy.py
+++ ./legacy.ppy
@@ -1,32 +1,34 @@
"""A little legacy telemetry script, dynamic in all the usual harmless ways."""
+import math
+from collections.abc import Sequence
+from typing import Final
-import importlib
+import ppy
-math = importlib.import_module("math")
-
-globals()["SCALE"] = 4
+SCALE: Final[int] = 4
class Reading:
- def __init__(self, value):
- self.value = value
- self.flagged = False
+ def __init__(self, value: float) -> None:
+ self.value: float = value
+ self.flagged: bool = False
-def normalize(readings):
- out = []
+def normalize(readings: Sequence[Reading]) -> list[float]:
+ out: list[float] = []
for reading in readings:
out.append(reading.value * SCALE)
return out
-def spread(values):
[email protected]
+def spread(values: Sequence[float]) -> int:
return math.ceil(max(values) - min(values))
-samples = [Reading(0.5), Reading(1.25), Reading(2.0)]
-first = samples[0]
-setattr(first, "flagged", True)
+samples: list[Reading] = [Reading(0.5), Reading(1.25), Reading(2.0)]
+first: Reading = samples[0]
+first.flagged = True
-scaled = normalize(samples)
-print(getattr(first, "flagged"), scaled, spread(scaled))
+scaled: list[float] = normalize(samples)
+print(first.flagged, scaled, spread(scaled))
python legacy.ppy, ppy run legacy.ppy
Before and after¶
math = importlib.import_module("math")
globals()["SCALE"] = 4
setattr(first, "flagged", True)
print(getattr(first, "flagged"), scaled, spread(scaled))
becomes
ppy convert would refuse the original. ppy migrate rewrites each site
to the static form it always meant, and proves the rewrite equivalent before
making it. Once nothing uses it, it removes the import importlib that fed
the first line.
Then the ordinary conversion runs over the rewritten code. It adds:
- fourteen annotations
Finalon the constantSequencewhere a parameter is only read@ppy.pureonspread
The result passes ppy check under strict = true with nothing left over.
This migration ends where ppy convert starts.
What a migration reports¶
ppy migrate --report migration.json writes the full accounting:
- what was
AUTOFIXED - what
REQUIRES_REWRITE - what needs a
DYNAMIC_BOUNDARY - whether the output is
strict_ready
--diff shows the rewrite before it happens.
On a real codebase¶
On a real codebase the unit of migration is a kernel file, not the whole repository. Migrating a real project says which files to hand it.
Read on: CLI: ppy migrate ยท
Dynamic boundaries
Generated, not hand-written: legacy.ppy is exactly what ppy migrate
legacy.py writes, and examples/verify_conversions.py checks that on every
run.
30_migrate/legacy.ppy¶
"""A little legacy telemetry script, dynamic in all the usual harmless ways."""
import math
from collections.abc import Sequence
from typing import Final
import ppy
SCALE: Final[int] = 4
class Reading:
def __init__(self, value: float) -> None:
self.value: float = value
self.flagged: bool = False
def normalize(readings: Sequence[Reading]) -> list[float]:
out: list[float] = []
for reading in readings:
out.append(reading.value * SCALE)
return out
@ppy.pure
def spread(values: Sequence[float]) -> int:
return math.ceil(max(values) - min(values))
samples: list[Reading] = [Reading(0.5), Reading(1.25), Reading(2.0)]
first: Reading = samples[0]
first.flagged = True
scaled: list[float] = normalize(samples)
print(first.flagged, scaled, spread(scaled))
Source: examples/30_migrate.