Reusable profiler and importer chassis for tabular migrations
Project description
migration-workbench
Reusable Django chassis for tabular workbook → app migrations: connectors pull from spreadsheets (Google Sheets) or Coda; profiling produces deterministic bundles; importers validate and apply with structured summaries; the workbook app turns profiles into schema-contract YAML for product repos to harden into real models.
PyPI: migration-workbench — pip install migration-workbench (import package migration_workbench uses underscores).
Who it is for
- Product teams moving messy spreadsheet truth into a maintainable Django app.
- Single-operator or small teams who want a repeatable pipeline (profile → contract → import) instead of one-off scripts.
- Django-adjacent adopters comfortable wiring
INSTALLED_APPS, env vars, and Fly-style SQLite hosting.
Three ways to use it
1. As a library (recommended for product repos)
Add the apps you need to INSTALLED_APPS and wire URLs/commands in your Django project. Set **DJANGO_SETTINGS_MODULE** to your project’s settings module (not migration_workbench.settings) in production. Depend on a released version, e.g. migration-workbench>=0.1.0,<1.
2. Scaffold a new product repo
From a sibling checkout of this repo:
make new-product PRODUCT=my-product # writes ../my-product; git init + initial commit
make new-product PRODUCT=my-product PROVIDER=--coda
Then cd ../my-product && make install && make migrate && make check. Local make install matches the Dockerfile: the product package is editable (pip install -e .) and migration-workbench comes from PyPI via pyproject.toml. The scaffold also includes backend/, Makefile, scripts/entrypoint_product.sh, SQLite/Fly-aligned settings (SQLITE_PATH, /healthz, WAL pragmas), starter docs, and provider-specific config skeletons under config/ (Google Sheets by default; use PROVIDER=--coda for Coda). If git is on PATH, the scaffold initializes a repo and writes one initial commit using a scaffold-local author identity. Use --output-dir / --force on scripts/new_product.py for non-default paths.
3. Develop the chassis (this repo)
Clone, editable install, run the full gate:
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
. ./.env.example # or create .env
.venv/bin/python manage.py migrate
make chassis-gate
Quickstart (PyPI)
python3 -m venv .venv
.venv/bin/pip install "migration-workbench[dev]" # omit [dev] if you skip pytest/black
Use wb on your PATH, or import apps (connectors, profiler, importer, workbook, deployment, …). For consumer repos installing the chassis next to your code: pip install -e ../migration-workbench — see profiler/README.md for profiling commands and importer/README.md for import authoring.
Core bundle commands (from a project with manage.py):
python manage.py pull_bundle --config docs/examples/live-config.example.json --output-dir /tmp/bundle
python manage.py snapshot_bundle --config docs/examples/offline-config.example.json --output-dir /tmp/bundle
python manage.py import_reference_example example_data --validate-only
Note: bundled **migration_workbench.settings** is for development; production hosts use their own settings module.
Architecture at a glance
Five Django apps:
| App | Role |
|---|---|
| connectors | Provider adapters (Sheets, Coda). |
| profiler | Read-only profiling → normalized bundle artifacts. |
| importer | BaseImportCommand chassis, preflight/apply, summary JSON. |
| workbook | scaffold_workbook_schema → schema-contract YAML. |
| deployment | Manifest validation, wb CLI (manifest lint, deploy dry-run). |
flowchart LR
sourceConfig[SourceConfigJSON] --> pullBundle[PullBundleCommand]
pullBundle --> providerRouter[ProviderRouter]
providerRouter --> adapters[GoogleSheets_or_Coda]
adapters --> rawRows[RawRows]
rawRows --> normalizer[SpreadsheetNormalizer]
normalizer --> bundle[NormalizedBundle]
bundle --> importer[BaseImportCommandSubclass]
importer --> summary[SummaryArtifactJSON]
More detail: docs/architecture.md.
The pipeline
- Intake — Source config (Drive folder, sheet IDs, Coda doc URLs).
- Profile — Profiler commands emit JSON/Markdown under product-owned
data/profile_snapshots/by default. - Model —
scaffold_workbook_schemaproduces schema-contract YAML for review. - Harden — Importer tiers validate then apply; summary artifacts record outcomes.
- Deploy —
wb manifest lintvalidates deploy/spaces.yml;wb deploy <space> --env <preview|production> --dry-runplans releases (provider mutation deferred — see docs/deployment.md).
Deployment
Fly.io + SQLite on a persistent volume + Litestream replication to Tigris or any S3-compatible bucket. Operator bootstrap, secrets, CI/CD, rollback, and roadmap for the wb control plane: docs/deployment.md.
CI/CD
| Workflow | File | Trigger | Role |
|---|---|---|---|
| CI | .github/workflows/ci.yml | push, PR | make chassis-gate, wheel smoke |
| Deploy | .github/workflows/deploy.yml | after successful CI (workflow_run) |
manifest lint → flyctl deploy → /healthz smoke (main → production, preview/* → preview) |
| Publish PyPI | .github/workflows/publish-pypi.yml | tag v* |
Trusted Publishing to PyPI |
GitHub repository secret **FLY_API_TOKEN** is required for Deploy. Product repos can copy these CI patterns, but workflow files are maintained per repository.
Status and roadmap
Stable on 0.x today
- Profiler (Google Sheets / Drive + Coda), importer chassis, workbook scaffolder.
wb manifest lint,wb deploy --dry-run, PyPI trusted publishing.- Self-hosted Fly path: Litestream + shared Tigris bucket,
fly.toml/fly.preview.toml, entrypoint migrations.
In flight
- Align default Git branch with Deploy workflow (
mainvsmaster). - Production Deploy workflow green end-to-end after secrets and Fly bootstrap.
Next
- Real
wb deploy(today:flyctl deploy+ manifest lint is the operator path). - Backup/restore drill documented and exercised for the workbench space.
- Google auth runbook evolution toward WIF (docs/google-auth.md).
- Scaffold-delivered CI/CD templates for client product repos.
- Cross-reference tab detection via workbook code patterns (
\b\d{3}\b) in tab scoring heuristics, penalizing derived tabs from other workbooks (Issue #1).
Later
- Provider interface extraction after a second space is stable on Fly.
- Postgres mode where concurrent writes demand it.
v1.0 criteria
The pipeline is exercised toward v1.0 via a product test repo (farm). v1.0 is reached when:
- End-to-end pipeline — All five stages (Connectors → Profiler → Importer → Workbook → Deployment) exercised on a real corpus via the product repo.
- Schema design loop completed — At least one source corpus has gone through Profile → Observe → Draft → Decide → Author config → Author importer → Gate → Drift check.
- Production deployment live — A scaffolded product is deployed to Fly.io with real imported data, health-check passing.
- PyPI release cut — All gaps identified during the test run are patched upstream, and a new PyPI release is published.
Semantic versioning applies; **0.x** may ship breaking changes — pin ranges in product repos.
Releases
- Bump
**version** in[pyproject.toml](pyproject.toml). - Tag
**v + version** (must matchversion = "x.y.z"). - Trusted Publishing on PyPI for this repo (see publish workflow).
Manual upload: python -m build then twine upload dist/*, or make publish with maintainer credentials. Optional extras: [release] for build/twine only.
Documentation map
| Doc | Purpose |
|---|---|
| This README | Orientation, pipeline, roadmap |
| docs/architecture.md | Layered design |
| docs/deployment.md | Fly, secrets, Litestream/Tigris, CI/CD, control-plane roadmap |
| docs/schema-design-loop.md | Contract-first importer workflow |
| docs/google-auth.md | Sheets/Drive profiling auth |
| docs/google-corpus.md | Drive folder / multi-workbook Sheets corpus profiling |
| docs/coda.md | Coda profiling |
Per-package README.md under connectors/, profiler/, importer/, workbook/, deployment/ |
App-local surfaces |
Changelog
0.8.0
- Per-tier transaction savepoints:
--tier-atomic(default on) wraps each import tier in its owntransaction.atomic()savepoint. A failing tier rolls back only its own rows; preceding tiers persist.--no-tier-atomicrestores single-transaction behaviour. - Per-row exception catching in generated imports: Generated
_import_<model>()methods now catchIntegrityErrorand other exceptions per row, recording structured errors instead of aborting the entire tier. - New error codes:
type_mismatch,unique_violation, androw_exceptioninFAILURE_SIGNATURE_OWNERSHIPfor structured escalation routing. - Per-model row error counts in summary JSON: Each model's outcome dict now includes
row_errors_countfor quick per-model error tallying. - Expanded parsing edge-case handling: Tests for
None, whitespace-only, and common sentinel values ("N/A","-") across all parsers. - End-to-end import pipeline fixture:
ExampleFarm,ExampleField,ExampleVarietymodels with FK chains,column_mapmulti-source,field_transforms, andfield_parsersexercising the fullgenerate_import→BaseImportCommandpipeline. - Bundle reader multi-source fix:
iter_bundle_tab_rowsnow correctly skips list-valuedcolumn_mapentries instead of raisingTypeError. - Import pipeline smoke test in chassis-gate:
generate_importexercised with multi-model contract in CI.
0.7.0
- Profile-to-contract bridge — designed model detection:
scaffold_workbook_schemanow clusters tabs by overlapping column sets (>50% Jaccard-like overlap) and suggests designed/aggregate models withsource_tab: null. New moduleworkbook.codegen.designed_model_detection. - Contract review checklist round-out:
wb contract reviewnow checks FK lookup target existence, admin inlines target models, and computed_field snake_case naming conventions. validate-contractMake target: Wired into scaffolded product Makefile; aggregatescheck validate-contractfor CI.corpus-codegen-reportMake target: Runs contract review and Django system check on generated files; corpus feedback tracker doc for capturing papercuts.
0.6.0
- Reserved-character sanitization: Tab names containing
|,:,\,/,*,?,",<,>,%are automatically sanitized to underscore at ingestion, with a logged warning. - Tab exclusion by pattern: Configurable
tab_exclude_patternsin scoring heuristics — each entry specifies a regex pattern and penalty weight for matching tab titles. - Column formula structure analysis: Profiler classifies columns as
raw,row_formula,expansion_formula,hybrid, orempty. Classification flows into tab scoring (expansion_formula_ratiopenalty), schema contract field annotations, and column candidate shortlists.
0.5.0
- Migration safety checks:
wb contract safety --old contract-v1.yaml --new contract-v2.yamldetects destructive changes (field removed, nullable→non-nullable → DANGER; class change, max_length decreased, unique=True added, non-nullable field without default → WARNING) with text and--jsonoutput. - Null-key robustness:
_diff_fields()normalises YAMLnull:mapping keys to the string"null"to preventTypeErrorduring kwarg comparison.
0.4.0
- Multi-source column_map with field transforms:
column_mapvalues can be lists of source headers;field_transformsblock accepts lambda expressions for combining columns (default: space join). - Contract composition: Custom
!includeYAML tag resolves relative to including file's directory with cyclic-include detection. - Auto-detect import tier ordering:
assign_import_tiers()topological sorts FK dependency chains; explicit tiers override auto-detection. - Contract diff tool:
wb contract diff --old contract-v1.yaml --new contract-v2.yamlcompares models, fields, and meta with text and JSON (--json) output. - Schema review checklist:
wb contract review --contract <yaml>checks CharField max_length, nullable FK on_delete, missing unique_together, and str_template. - Snapshot testing:
make snapshot-codegen/make check-snapshotsstores generated output per contract version for regression detection. check-generatedMakefile target: py_compile validation of generated Python files.
0.3.0
- Admin scaffold maturity:
list_editable,autocomplete_fields,admin.inlinesfield overrides,--diffflag for regeneration preview. - Post-generation hook system:
hooks.after_model,hooks.after_meta,hooks.extra_methodsin contract YAML inject Python source at well-defined points in generated model classes. scaffold_designed_modelcommand: Emit contract table skeletons for designed/aggregate models with no source tab.- Admin
--diffflag: Preview changes before overwriting; forced regeneration shows diff of detected changes.
0.2.0
- Contract schema v1.3:
computed_fields(rendered as@property),is_abstract,source_tab: nullfor designed models,app_labelper table inmodel_meta. - Makefile improvements:
validate-contract,diff-generated,generate-admin-light,generate-admin,post-generatetargets. - Codegen QoL:
generate_models --diff, contract validation warnings at codegen time, import generator skip notes. - Backport AbstractUser admin scaffold support from codegen pipeline.
- Extend contract schema to v1.2: enums, admin config,
model_base, richerMeta. - Initial codegen pipeline:
generate_models,generate_admin,generate_importcommands producing production Django files from hardened schema-contract YAML. - Import generator base class with override hooks.
inject_project_local_config.shhelper for per-checkout config injection.
0.1.2
- Default profile output directory:
data/profile_snapshots/. - Drive folder tree rendered as Markdown artifact.
- Cohort corpus resume support with workbook index and HTTP 429 retry.
- Skeleton config files and raw_notes bucket included in
new-productscaffold. - New product scaffold emits fixed Makefile referencing editable workbench path.
- Bundle reader integration with YAML config files.
0.1.1
- View manifest draft YAML artifact from profiler structural pass.
structure.jsonartifact frompull_bundlecommand — tab- and column-level metadata.- New product scaffold defaults to PyPI
migration-workbench. read_bundle_tabwrapper for normalizing rows from bundle tab CSV.- Git init and initial commit after
new-product. - Consolidated docs folder with cross-cutting operator notes.
- Per-app READMEs at
connectors/,profiler/,importer/,workbook/,deployment/.
0.1.0
- Initial scaffold: profile, import, bundle commands.
- Project bootstrap scripting (
new-product). - Google Sheets / Drive and Coda adapters.
- Deployment documentation for Fly.io + Litestream.
Database modes
DB_ENGINE=sqlite(default)DB_ENGINE=postgreswithDB_NAME,DB_USER,DB_PASSWORD,DB_HOST,DB_PORT
License
See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file migration_workbench-0.8.0.tar.gz.
File metadata
- Download URL: migration_workbench-0.8.0.tar.gz
- Upload date:
- Size: 191.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39c906c1acadaf1d6796ba8dd39fbc152f74431d43a749fe34f887acb56f3090
|
|
| MD5 |
39d1788dcc2dfb59e03c4493d078d567
|
|
| BLAKE2b-256 |
e3d95d4b81fe85a8c607e9c3d68c264c58476ec8aade7098222f380804f6df43
|
Provenance
The following attestation bundles were made for migration_workbench-0.8.0.tar.gz:
Publisher:
publish-pypi.yml on MrAllatta/migration-workbench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
migration_workbench-0.8.0.tar.gz -
Subject digest:
39c906c1acadaf1d6796ba8dd39fbc152f74431d43a749fe34f887acb56f3090 - Sigstore transparency entry: 1643773166
- Sigstore integration time:
-
Permalink:
MrAllatta/migration-workbench@2d085d5a97b345879dfb7587c41cd3ab015d8bf2 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/MrAllatta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@2d085d5a97b345879dfb7587c41cd3ab015d8bf2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file migration_workbench-0.8.0-py3-none-any.whl.
File metadata
- Download URL: migration_workbench-0.8.0-py3-none-any.whl
- Upload date:
- Size: 240.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99ee4d7ed33b367c84c6af8ab55076ae0979f41e1ec2c6bca89ec3758a8e0ce9
|
|
| MD5 |
215f41fd6eb4fecb1371baba8f2fb1bf
|
|
| BLAKE2b-256 |
3752cf98f8de8a720d673f42784c2ec0788bb2c1c30dc031a2b6358f0e21ec4a
|
Provenance
The following attestation bundles were made for migration_workbench-0.8.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on MrAllatta/migration-workbench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
migration_workbench-0.8.0-py3-none-any.whl -
Subject digest:
99ee4d7ed33b367c84c6af8ab55076ae0979f41e1ec2c6bca89ec3758a8e0ce9 - Sigstore transparency entry: 1643773169
- Sigstore integration time:
-
Permalink:
MrAllatta/migration-workbench@2d085d5a97b345879dfb7587c41cd3ab015d8bf2 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/MrAllatta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@2d085d5a97b345879dfb7587c41cd3ab015d8bf2 -
Trigger Event:
push
-
Statement type: