langmigrate 1.0.0__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 (99) hide show
  1. langmigrate-1.0.0/.github/workflows/ci.yml +75 -0
  2. langmigrate-1.0.0/.github/workflows/docs.yml +57 -0
  3. langmigrate-1.0.0/.github/workflows/publish.yml +100 -0
  4. langmigrate-1.0.0/.gitignore +36 -0
  5. langmigrate-1.0.0/.pre-commit-config.yaml +24 -0
  6. langmigrate-1.0.0/CHANGELOG.md +80 -0
  7. langmigrate-1.0.0/CLAUDE.md +86 -0
  8. langmigrate-1.0.0/CODE_OF_CONDUCT.md +65 -0
  9. langmigrate-1.0.0/CONTRIBUTING.md +99 -0
  10. langmigrate-1.0.0/LICENSE +21 -0
  11. langmigrate-1.0.0/PKG-INFO +222 -0
  12. langmigrate-1.0.0/README.md +174 -0
  13. langmigrate-1.0.0/SECURITY.md +35 -0
  14. langmigrate-1.0.0/docker-compose.yml +31 -0
  15. langmigrate-1.0.0/docs/Gemfile +4 -0
  16. langmigrate-1.0.0/docs/INTEGRATION.md +209 -0
  17. langmigrate-1.0.0/docs/_config.yml +41 -0
  18. langmigrate-1.0.0/docs/cookbook/index.md +637 -0
  19. langmigrate-1.0.0/docs/index.md +63 -0
  20. langmigrate-1.0.0/examples/README.md +36 -0
  21. langmigrate-1.0.0/examples/batch_migration/README.md +67 -0
  22. langmigrate-1.0.0/examples/batch_migration/demo.py +224 -0
  23. langmigrate-1.0.0/examples/batch_migration/migrations/a1c0_add_priority.py +23 -0
  24. langmigrate-1.0.0/examples/batch_migration/migrations/b2d1_normalise_status.py +32 -0
  25. langmigrate-1.0.0/examples/deep_research_agent/README.md +58 -0
  26. langmigrate-1.0.0/examples/deep_research_agent/demo.py +243 -0
  27. langmigrate-1.0.0/examples/deep_research_agent/migrations/a1c0_add_depth.py +23 -0
  28. langmigrate-1.0.0/examples/deep_research_agent/migrations/b2d1_add_findings.py +40 -0
  29. langmigrate-1.0.0/examples/deep_research_agent/migrations/c3e2_drop_debug.py +27 -0
  30. langmigrate-1.0.0/examples/evolving_agent/README.md +42 -0
  31. langmigrate-1.0.0/examples/evolving_agent/demo.py +63 -0
  32. langmigrate-1.0.0/examples/evolving_agent/migrations/a1c0_add_context.py +22 -0
  33. langmigrate-1.0.0/examples/evolving_agent/migrations/b2d1_rename_msgs.py +24 -0
  34. langmigrate-1.0.0/examples/middleware_agent/README.md +54 -0
  35. langmigrate-1.0.0/examples/middleware_agent/demo.py +222 -0
  36. langmigrate-1.0.0/examples/middleware_agent/migrations/a1c0_add_metadata.py +21 -0
  37. langmigrate-1.0.0/examples/middleware_agent/migrations/b2d1_add_confidence.py +23 -0
  38. langmigrate-1.0.0/examples/multi_tool_agent/README.md +50 -0
  39. langmigrate-1.0.0/examples/multi_tool_agent/demo.py +195 -0
  40. langmigrate-1.0.0/examples/multi_tool_agent/migrations/a1c0_add_session.py +25 -0
  41. langmigrate-1.0.0/examples/multi_tool_agent/migrations/b2d1_add_tool_count.py +26 -0
  42. langmigrate-1.0.0/examples/multi_tool_agent/migrations/c3e2_require_query.py +28 -0
  43. langmigrate-1.0.0/examples/quickstart/main.py +58 -0
  44. langmigrate-1.0.0/examples/quickstart/migrations/__init__.py +0 -0
  45. langmigrate-1.0.0/examples/quickstart/migrations/a1c0_add_context.py +24 -0
  46. langmigrate-1.0.0/pyproject.toml +96 -0
  47. langmigrate-1.0.0/src/langmigrate/__init__.py +62 -0
  48. langmigrate-1.0.0/src/langmigrate/adapters/__init__.py +5 -0
  49. langmigrate-1.0.0/src/langmigrate/adapters/base.py +48 -0
  50. langmigrate-1.0.0/src/langmigrate/adapters/postgres.py +126 -0
  51. langmigrate-1.0.0/src/langmigrate/adapters/redis.py +152 -0
  52. langmigrate-1.0.0/src/langmigrate/cli/__init__.py +1 -0
  53. langmigrate-1.0.0/src/langmigrate/cli/main.py +372 -0
  54. langmigrate-1.0.0/src/langmigrate/cli/templates/revision.py.tmpl +25 -0
  55. langmigrate-1.0.0/src/langmigrate/cli/templates/revision_auto.py.tmpl +30 -0
  56. langmigrate-1.0.0/src/langmigrate/config.py +68 -0
  57. langmigrate-1.0.0/src/langmigrate/core/__init__.py +1 -0
  58. langmigrate-1.0.0/src/langmigrate/core/engine.py +80 -0
  59. langmigrate-1.0.0/src/langmigrate/core/exceptions.py +143 -0
  60. langmigrate-1.0.0/src/langmigrate/core/migration.py +241 -0
  61. langmigrate-1.0.0/src/langmigrate/core/operations.py +123 -0
  62. langmigrate-1.0.0/src/langmigrate/core/registry.py +236 -0
  63. langmigrate-1.0.0/src/langmigrate/core/schema.py +180 -0
  64. langmigrate-1.0.0/src/langmigrate/core/topology.py +62 -0
  65. langmigrate-1.0.0/src/langmigrate/core/types.py +152 -0
  66. langmigrate-1.0.0/src/langmigrate/core/version.py +61 -0
  67. langmigrate-1.0.0/src/langmigrate/integrations/__init__.py +13 -0
  68. langmigrate-1.0.0/src/langmigrate/integrations/langchain.py +119 -0
  69. langmigrate-1.0.0/src/langmigrate/integrations/state.py +82 -0
  70. langmigrate-1.0.0/src/langmigrate/py.typed +2 -0
  71. langmigrate-1.0.0/src/langmigrate/runtime/__init__.py +1 -0
  72. langmigrate-1.0.0/src/langmigrate/runtime/batch.py +103 -0
  73. langmigrate-1.0.0/src/langmigrate/runtime/factory.py +53 -0
  74. langmigrate-1.0.0/src/langmigrate/runtime/interceptor.py +164 -0
  75. langmigrate-1.0.0/src/langmigrate/runtime/persistence.py +101 -0
  76. langmigrate-1.0.0/tests/__init__.py +0 -0
  77. langmigrate-1.0.0/tests/conftest.py +20 -0
  78. langmigrate-1.0.0/tests/integration/__init__.py +0 -0
  79. langmigrate-1.0.0/tests/integration/test_postgres_e2e.py +282 -0
  80. langmigrate-1.0.0/tests/integration/test_redis_e2e.py +221 -0
  81. langmigrate-1.0.0/tests/unit/__init__.py +0 -0
  82. langmigrate-1.0.0/tests/unit/test_batch.py +199 -0
  83. langmigrate-1.0.0/tests/unit/test_before_after_langmigrate.py +275 -0
  84. langmigrate-1.0.0/tests/unit/test_bugs.py +256 -0
  85. langmigrate-1.0.0/tests/unit/test_cli.py +167 -0
  86. langmigrate-1.0.0/tests/unit/test_cli_autogenerate.py +118 -0
  87. langmigrate-1.0.0/tests/unit/test_cli_db.py +230 -0
  88. langmigrate-1.0.0/tests/unit/test_config.py +37 -0
  89. langmigrate-1.0.0/tests/unit/test_engine.py +150 -0
  90. langmigrate-1.0.0/tests/unit/test_functional_migration.py +121 -0
  91. langmigrate-1.0.0/tests/unit/test_integrations_langchain.py +114 -0
  92. langmigrate-1.0.0/tests/unit/test_integrations_state.py +119 -0
  93. langmigrate-1.0.0/tests/unit/test_interceptor.py +275 -0
  94. langmigrate-1.0.0/tests/unit/test_operations.py +132 -0
  95. langmigrate-1.0.0/tests/unit/test_registry.py +147 -0
  96. langmigrate-1.0.0/tests/unit/test_schema.py +93 -0
  97. langmigrate-1.0.0/tests/unit/test_setup.py +93 -0
  98. langmigrate-1.0.0/tests/unit/test_topology.py +102 -0
  99. langmigrate-1.0.0/tests/unit/test_version.py +55 -0
@@ -0,0 +1,75 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ lint-and-unit:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ matrix:
12
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v6
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ cache-dependency-glob: "pyproject.toml"
20
+ - name: Sync (dev + langchain extras)
21
+ run: uv sync --extra dev --extra langchain
22
+ - name: Ruff lint
23
+ run: uv run ruff check src tests
24
+ - name: Ruff format check
25
+ run: uv run ruff format --check src tests
26
+ - name: Type check
27
+ run: uv run mypy src/langmigrate
28
+ - name: Unit tests (with coverage)
29
+ run: uv run pytest -m "not integration" --cov=langmigrate --cov-report=term --cov-report=xml
30
+ - name: Upload coverage artifact
31
+ if: matrix.python-version == '3.12'
32
+ uses: actions/upload-artifact@v4
33
+ with:
34
+ name: coverage
35
+ path: coverage.xml
36
+
37
+ integration:
38
+ runs-on: ubuntu-latest
39
+ services:
40
+ postgres:
41
+ image: postgres:16-alpine
42
+ env:
43
+ POSTGRES_USER: langmigrate
44
+ POSTGRES_PASSWORD: langmigrate
45
+ POSTGRES_DB: langmigrate
46
+ ports:
47
+ - 5432:5432
48
+ options: >-
49
+ --health-cmd "pg_isready -U langmigrate"
50
+ --health-interval 2s
51
+ --health-timeout 3s
52
+ --health-retries 15
53
+ redis:
54
+ image: redis/redis-stack-server:latest
55
+ ports:
56
+ - 6379:6379
57
+ options: >-
58
+ --health-cmd "redis-cli ping"
59
+ --health-interval 2s
60
+ --health-timeout 3s
61
+ --health-retries 15
62
+ env:
63
+ LANGMIGRATE_TEST_PG: postgresql://langmigrate:langmigrate@localhost:5432/langmigrate
64
+ LANGMIGRATE_TEST_REDIS: redis://localhost:6379
65
+ steps:
66
+ - uses: actions/checkout@v5
67
+ - name: Install uv
68
+ uses: astral-sh/setup-uv@v6
69
+ with:
70
+ python-version: "3.12"
71
+ cache-dependency-glob: "pyproject.toml"
72
+ - name: Sync (all extras)
73
+ run: uv sync --extra dev --extra postgres --extra redis --extra langchain
74
+ - name: Integration tests
75
+ run: uv run pytest -m integration
@@ -0,0 +1,57 @@
1
+ name: Deploy docs to GitHub Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - ".github/workflows/docs.yml"
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ concurrency:
17
+ group: pages
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ defaults:
24
+ run:
25
+ working-directory: docs
26
+ steps:
27
+ - uses: actions/checkout@v5
28
+
29
+ - uses: actions/configure-pages@v5
30
+ with:
31
+ enablement: true
32
+
33
+ - name: Setup Ruby
34
+ uses: ruby/setup-ruby@v1
35
+ with:
36
+ ruby-version: "3.3"
37
+ bundler-cache: true
38
+ working-directory: ${{ github.workspace }}/docs
39
+
40
+ - name: Build with Jekyll
41
+ run: bundle exec jekyll build
42
+ env:
43
+ JEKYLL_ENV: production
44
+
45
+ - uses: actions/upload-pages-artifact@v3
46
+ with:
47
+ path: docs/_site
48
+
49
+ deploy:
50
+ needs: build
51
+ runs-on: ubuntu-latest
52
+ environment:
53
+ name: github-pages
54
+ url: ${{ steps.deployment.outputs.page_url }}
55
+ steps:
56
+ - id: deployment
57
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,100 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ # PyPI trusted publishing requires id-token: write.
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ jobs:
14
+ test-before-publish:
15
+ name: Run full test suite before publish
16
+ runs-on: ubuntu-latest
17
+ services:
18
+ postgres:
19
+ image: postgres:16-alpine
20
+ env:
21
+ POSTGRES_USER: langmigrate
22
+ POSTGRES_PASSWORD: langmigrate
23
+ POSTGRES_DB: langmigrate
24
+ ports: ["5432:5432"]
25
+ options: >-
26
+ --health-cmd "pg_isready -U langmigrate"
27
+ --health-interval 2s --health-timeout 3s --health-retries 15
28
+ redis:
29
+ image: redis/redis-stack-server:latest
30
+ ports: ["6379:6379"]
31
+ options: >-
32
+ --health-cmd "redis-cli ping"
33
+ --health-interval 2s --health-timeout 3s --health-retries 15
34
+ env:
35
+ LANGMIGRATE_TEST_PG: postgresql://langmigrate:langmigrate@localhost:5432/langmigrate
36
+ LANGMIGRATE_TEST_REDIS: redis://localhost:6379
37
+ steps:
38
+ - uses: actions/checkout@v5
39
+ - uses: astral-sh/setup-uv@v6
40
+ with:
41
+ cache-dependency-glob: "pyproject.toml"
42
+ - name: Sync (all extras)
43
+ run: uv sync --extra dev --extra postgres --extra redis --extra langchain
44
+ - name: Ruff lint
45
+ run: uv run ruff check src tests
46
+ - name: Ruff format check
47
+ run: uv run ruff format --check src tests
48
+ - name: Type check
49
+ run: uv run mypy src/langmigrate
50
+ - name: Unit tests (with coverage)
51
+ run: uv run pytest -m "not integration" --cov=langmigrate --cov-report=term
52
+ - name: Integration tests
53
+ run: uv run pytest -m integration
54
+
55
+ build:
56
+ name: Build sdist and wheel
57
+ needs: test-before-publish
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v5
61
+ - uses: astral-sh/setup-uv@v6
62
+ - name: Build
63
+ run: uv build
64
+ - uses: actions/upload-artifact@v4
65
+ with:
66
+ name: dist
67
+ path: dist/*
68
+
69
+ publish:
70
+ name: Publish to PyPI via trusted publishing
71
+ needs: build
72
+ runs-on: ubuntu-latest
73
+ environment:
74
+ name: pypi
75
+ url: https://pypi.org/p/langmigrate
76
+ steps:
77
+ - uses: actions/download-artifact@v4
78
+ with:
79
+ name: dist
80
+ path: dist/
81
+ - name: Publish
82
+ uses: pypa/gh-action-pypi-publish@release/v1
83
+
84
+ github-release:
85
+ name: Create GitHub Release
86
+ needs: publish
87
+ runs-on: ubuntu-latest
88
+ permissions:
89
+ contents: write
90
+ steps:
91
+ - uses: actions/checkout@v5
92
+ - uses: actions/download-artifact@v4
93
+ with:
94
+ name: dist
95
+ path: dist/
96
+ - name: Create release
97
+ uses: softprops/action-gh-release@v2
98
+ with:
99
+ files: dist/*
100
+ generate_release_notes: true
@@ -0,0 +1,36 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # Virtual env / uv
11
+ .venv/
12
+ venv/
13
+ uv.lock
14
+
15
+ # Test / coverage
16
+ .pytest_cache/
17
+ .coverage
18
+ htmlcov/
19
+ .ruff_cache/
20
+ .mypy_cache/
21
+
22
+ # OS
23
+ .DS_Store
24
+
25
+ # IDE
26
+ .idea/
27
+ .vscode/
28
+ .cursor/
29
+
30
+ # AI assistant local config
31
+ .claude/
32
+
33
+ # Project
34
+ langmigrate.toml
35
+ migrations/
36
+ !examples/**/migrations/
@@ -0,0 +1,24 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.11.0
4
+ hooks:
5
+ - id: ruff
6
+ - id: ruff-format
7
+
8
+ - repo: https://github.com/pre-commit/mirrors-mypy
9
+ rev: v1.15.0
10
+ hooks:
11
+ - id: mypy
12
+ additional_dependencies:
13
+ - pydantic>=2.5
14
+ - typer>=0.12
15
+ - langgraph-checkpoint>=2.0
16
+
17
+ - repo: https://github.com/pre-commit/pre-commit-hooks
18
+ rev: v5.0.0
19
+ hooks:
20
+ - id: trailing-whitespace
21
+ - id: end-of-file-fixer
22
+ - id: check-yaml
23
+ - id: check-toml
24
+ - id: check-added-large-files
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **`setup_langmigrate(saver, migrations)` factory.** One-liner that builds the
13
+ registry, engine and `MigrationInterceptor` for you. Accepts a path, a
14
+ `MigrationRegistry`, or a `MigrationEngine`; forwards `write_back` / `target`.
15
+ - **`@migration` decorator and `FunctionMigration`.** Write inline function-pair
16
+ migrations without subclassing `BaseMigration`; attach the reverse with
17
+ `.reverse`. `MigrationRegistry.from_path` now discovers both decorator-built
18
+ instances and `BaseMigration` subclasses.
19
+ - **Fluent `StateEnvelope` helpers.** `state.add_field(...)`, `.drop_field(...)`,
20
+ `.rename_field(...)`, `.coerce_field(...)`, `.require_field(...)` — the same
21
+ pure operations as methods, for the function-pair style.
22
+ - **`BaseMigration.is_reversible`.** Used by `langmigrate check` to flag one-way
23
+ migrations (works for both authoring styles).
24
+ - **`langmigrate init` scaffolding.** Now also writes `migrations/__init__.py`
25
+ and a `migrations/README.md`; `--example` drops a first revision skeleton.
26
+ - **Quickstart example** (`examples/quickstart/`) type-checked with `mypy --strict`.
27
+ - Public exports: `setup_langmigrate`, `migration`, `FunctionMigration`,
28
+ `new_revision_id`.
29
+
30
+ ## [1.0.0] — 2026-06-05
31
+
32
+ First stable release. LangMigrate brings declarative, Alembic-style schema
33
+ migrations to LangGraph state persistence — checkpointers (Postgres, Redis)
34
+ and stores.
35
+
36
+ ### Added
37
+
38
+ - **Migration engine and registry.** Alembic-style DAG with `revision` /
39
+ `down_revision`, path resolution, cycle and multiple-head detection.
40
+ - **Pure, idempotent operations.** `add_field`, `drop_field`, `rename_field`,
41
+ `coerce_field`, `require_field` — Safe vs Unsafe annotated, with
42
+ `IrreversibleMigrationError` for genuinely one-way migrations.
43
+ - **Declarative migrations.** `BaseMigration` ABC with helpers that delegate
44
+ to the pure operations module.
45
+ - **Topology repair.** `NodeRemap` helper for interrupted threads on
46
+ deleted/renamed graph nodes, applicable within any migration.
47
+ - **CLI (`langmigrate`).** `init`, `revision` (with `--autogenerate --schema`
48
+ for state-aware scaffolding), `history`, `current`, `check`, `upgrade`,
49
+ `downgrade`, `stamp`.
50
+ - **Online migration.** `MigrationInterceptor` — drop-in
51
+ `BaseCheckpointSaver` wrapper. Lazy upgrade on `get_tuple`/`aget_tuple`
52
+ with idempotent write-back; `list`/`alist` migrate in-memory only to
53
+ prevent write storms. `put`/`aput` stamp the HEAD revision.
54
+ - **Batch migration.** `run_batch_upgrade` / `run_batch_downgrade` for
55
+ proactive cure of every stored checkpoint.
56
+ - **State-level middleware.** `SchemaMigrationMiddleware` for managed
57
+ platforms where the checkpointer is owned by the framework
58
+ (LangGraph Server, deepagents). Hooks `before_agent`, `before_model` and
59
+ their async counterparts.
60
+ - **Pure helper.** `migrate_state_update` for hand-built `StateGraph`s or
61
+ custom entry nodes.
62
+ - **Adapters.** `PostgresAdapter` (indexed `metadata->>'langmigrate_rev'`
63
+ filter) and `RedisAdapter` (scan-based RedisJSON enumeration).
64
+ - **Version tag.** Stored in `checkpoint.metadata`, never in
65
+ `channel_values` — queryable at the DB level and safe from pruning.
66
+ - **Schema autogenerate.** `revision --autogenerate --schema <module>:<class>`
67
+ diffs the current state schema against the last revision and scaffolds
68
+ the migration body.
69
+ - **Test suite.** 114 unit tests + 8 integration tests covering Postgres
70
+ and Redis end-to-end, sync and async paths.
71
+
72
+ ### Compatibility
73
+
74
+ - Python 3.10+
75
+ - Pydantic v2
76
+ - `langgraph-checkpoint` ≥ 2.0
77
+ - Optional: `psycopg` ≥ 3.1 (Postgres), `langgraph-checkpoint-redis` (Redis),
78
+ `langchain` ≥ 1.0 (state-level middleware)
79
+
80
+
@@ -0,0 +1,86 @@
1
+ # CLAUDE.md — LangMigrate
2
+
3
+ Declarative schema migrations for LangGraph state persistence (checkpointers & stores).
4
+ This file defines the conventions every contributor (human or AI) must follow.
5
+
6
+ ## What this project is
7
+
8
+ When a LangGraph application evolves its state schema (`TypedDict` / Pydantic), old or
9
+ interrupted threads persisted by a checkpointer (Postgres, Redis, ...) stop deserializing
10
+ cleanly. LangMigrate brings the **Alembic model** to LangGraph state: declarative
11
+ revisions with a cascade of transformation functions, applied either **offline** (proactive
12
+ batch CLI) or **online** (lazy runtime interceptor).
13
+
14
+ ## Architecture — Clean Architecture, strictly enforced
15
+
16
+ ```
17
+ cli/ runtime/ adapters/ ──────► core/
18
+ (DB clients live here) (pure: no DB client imports)
19
+ ```
20
+
21
+ **Dependency rule:** `cli`, `runtime`, and `adapters` may import from `core`. `core` must
22
+ NEVER import a database client (`psycopg`, `redis`, ...) nor anything from `adapters` /
23
+ `runtime` / `cli`. Keep migration business logic independent of any backend.
24
+
25
+ - `core/` — pure logic: `types`, `exceptions`, `operations`, `migration`, `registry`,
26
+ `engine`, `version`, `topology`. No I/O, no DB drivers.
27
+ - `adapters/` — DB-specific bulk access for the batch CLI. DB client imports are confined
28
+ here, ideally imported lazily inside methods so the core stays importable without extras.
29
+ - `runtime/` — `MigrationInterceptor`, a `BaseCheckpointSaver` wrapper for lazy online
30
+ migration. DB-agnostic: it delegates to whatever saver it wraps.
31
+ - `cli/` — Typer app.
32
+
33
+ ## Core design decisions (do not change without discussion)
34
+
35
+ 1. **Versioning = Alembic-style DAG.** Each revision has a `revision` hash and a
36
+ `down_revision` pointer. The engine resolves a path through the DAG, then applies it as a
37
+ linear cascade.
38
+ 2. **Version tag lives ONLY in `checkpoint.metadata`** under the key `langmigrate_rev`.
39
+ Never store it inside `channel_values` (it is metadata, not application state, and would
40
+ risk being pruned by LangGraph). It must stay queryable at the DB level.
41
+ 3. **Lazy write-back is ON by default, disableable, and idempotent.** Re-persisting a
42
+ migrated checkpoint must NOT change `checkpoint["id"]` nor break the `parent_config`
43
+ chain. Write-back happens only on `get_tuple`/`aget_tuple` (single checkpoint on
44
+ resume). `list`/`alist` migrate **in memory only, never writing back** — they
45
+ enumerate history (many checkpoints) and healing there would be a write storm and
46
+ would rewrite past checkpoints. The proactive "cure the DB" path is the batch
47
+ runner (`langmigrate upgrade`), not `list()`.
48
+ 4. **Adapters:** Postgres and Redis are both implemented (batch enumeration + the
49
+ shared online interceptor). Postgres filters stale checkpoints with an indexed
50
+ `metadata->>'langmigrate_rev'` query; Redis scans `checkpoint:*` RedisJSON docs
51
+ (no server-side index on the tag).
52
+
53
+ ## Migration rules (binding)
54
+
55
+ - Every `upgrade` MUST have a corresponding `downgrade`. Genuinely irreversible migrations
56
+ must be marked explicitly and raise `IrreversibleMigrationError` from `downgrade`.
57
+ - Migrations MUST be **idempotent** and **pure** — no hidden I/O, no network, no clocks.
58
+ Re-applying a migration to already-migrated state must be a no-op.
59
+ - Never introduce a breaking change without a downgrade script.
60
+ - Use the declarative helpers (`add_field`, `drop_field`, `rename_field`, `coerce_field`,
61
+ `require_field`) instead of hand-mutating dicts where possible.
62
+
63
+ ## Commands
64
+
65
+ ```bash
66
+ uv sync --extra dev --extra postgres --extra redis --extra langchain # set up the environment
67
+ uv run pytest # unit tests
68
+ uv run pytest -m integration # integration tests (needs Docker)
69
+ uv run ruff check . && uv run ruff format . # lint + format
70
+ docker compose up -d # local Postgres + Redis for integration
71
+ ```
72
+
73
+ ## Code style
74
+
75
+ - Python 3.10+, full type hints, Pydantic v2.
76
+ - `ruff` for lint + format (config in `pyproject.toml`). Line length 100.
77
+ - Docstrings on all public APIs.
78
+ - No heavy dependencies in `core`.
79
+ - Public exports go through `langmigrate/__init__.py`.
80
+
81
+ ## Testing
82
+
83
+ - Every operation/primitive has unit tests, including Safe vs Unsafe behavior.
84
+ - Engine tests must cover the cascade, idempotency, and the no-op-at-HEAD case.
85
+ - Each adapter has integration tests behind `@pytest.mark.integration` (requires Docker).
86
+ - Prefer the in-memory saver for runtime/interceptor unit tests.
@@ -0,0 +1,65 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment:
18
+
19
+ * Demonstrating empathy and kindness toward other people
20
+ * Being respectful of differing opinions, viewpoints, and experiences
21
+ * Giving and gracefully accepting constructive feedback
22
+ * Accepting responsibility and apologizing to those affected by our mistakes,
23
+ and learning from the experience
24
+ * Focusing on what is best not just for us as individuals, but for the overall
25
+ community
26
+
27
+ Examples of unacceptable behavior:
28
+
29
+ * The use of sexualized language or imagery, and sexual attention or advances of
30
+ any kind
31
+ * Trolling, insulting or derogatory comments, and personal or political attacks
32
+ * Public or private harassment
33
+ * Publishing others' private information, such as a physical or email address,
34
+ without their explicit permission
35
+ * Other conduct which could reasonably be considered inappropriate in a
36
+ professional setting
37
+
38
+ ## Enforcement Responsibilities
39
+
40
+ Community leaders are responsible for clarifying and enforcing our standards of
41
+ acceptable behavior and will take appropriate and fair corrective action in
42
+ response to any behavior that they deem inappropriate, threatening, offensive,
43
+ or harmful.
44
+
45
+ ## Scope
46
+
47
+ This Code of Conduct applies within all community spaces, and also applies when
48
+ an individual is officially representing the community in public spaces.
49
+
50
+ ## Enforcement
51
+
52
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
53
+ reported to the community leaders via the
54
+ [private vulnerability reporting](https://github.com/scinfu/langmigrate/security/advisories/new)
55
+ feature on GitHub or by contacting the maintainers directly. All complaints
56
+ will be reviewed and investigated promptly and fairly.
57
+
58
+ ## Attribution
59
+
60
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
61
+ version 2.1, available at
62
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
63
+
64
+ [homepage]: https://www.contributor-covenant.org
65
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
@@ -0,0 +1,99 @@
1
+ # Contributing to LangMigrate
2
+
3
+ Thank you for your interest in contributing! This document explains how to
4
+ set up your development environment, run the tests, and submit changes that
5
+ fit the project's style and architecture.
6
+
7
+ ## Development setup
8
+
9
+ LangMigrate uses [uv](https://docs.astral.sh/uv/) as its package manager
10
+ and Python 3.10+ as the language floor.
11
+
12
+ ```bash
13
+ # Clone and install (all extras for full test coverage)
14
+ git clone https://github.com/scinfu/langmigrate.git
15
+ cd langmigrate
16
+ uv sync --extra dev --extra postgres --extra redis --extra langchain
17
+
18
+ # Bring up Postgres and Redis for integration tests
19
+ docker compose up -d
20
+ ```
21
+
22
+ ## Architecture
23
+
24
+ LangMigrate follows **Clean Architecture** strictly:
25
+
26
+ ```
27
+ cli/ runtime/ adapters/ ──────► core/
28
+ (DB clients live here) (pure: no DB client imports)
29
+ ```
30
+
31
+ - `core/` — pure logic. No I/O, no DB drivers. Nothing outside `core/` may
32
+ be imported from within `core/`.
33
+ - `adapters/` — DB-specific bulk access for the batch CLI. DB client
34
+ imports are confined here, ideally imported lazily inside methods.
35
+ - `runtime/` — `MigrationInterceptor` and batch runners. DB-agnostic;
36
+ delegates to whichever saver it wraps.
37
+ - `integrations/` — `SchemaMigrationMiddleware` and pure helpers for
38
+ hand-built `StateGraph`s.
39
+ - `cli/` — Typer application.
40
+
41
+ The dependency rule is non-negotiable: `core` must never import a database
42
+ client (`psycopg`, `redis`, …) nor anything from `adapters/`, `runtime/`,
43
+ `cli/` or `integrations/`. Keep migration logic independent of any backend.
44
+
45
+ ## Testing
46
+
47
+ Every feature must have tests. Conventions:
48
+
49
+ - **Unit tests** (`tests/unit/`): pure logic, no services.
50
+ - **Integration tests** (`tests/integration/`): marked with
51
+ `@pytest.mark.integration`; require Docker. They cover end-to-end paths
52
+ on Postgres and Redis.
53
+ - Operations, engine, and interceptor changes require coverage of the
54
+ cascade, idempotency, async paths, and the no-op-at-HEAD case.
55
+
56
+ ```bash
57
+ uv run pytest # unit tests only
58
+ uv run pytest -m integration # integration (needs Docker)
59
+ uv run pytest --cov=langmigrate # with coverage report
60
+ ```
61
+
62
+ ## Code style
63
+
64
+ - Python 3.10+, full type hints, Pydantic v2.
65
+ - `ruff` for lint and format (line length 100).
66
+ - Docstrings on all public APIs (Google style).
67
+ - `mypy` is run in CI — keep it happy.
68
+
69
+ ```bash
70
+ uv run ruff check . && uv run ruff format .
71
+ uv run mypy src/langmigrate
72
+ ```
73
+
74
+ ## Commit and PR conventions
75
+
76
+ - Keep commits atomic: one logical change per commit.
77
+ - Write imperative commit subjects ("Add feature", not "Added feature").
78
+ - PRs must link the issue they address (if any) and summarize the change.
79
+ - Run the full unit test suite, `ruff` and `mypy` before pushing.
80
+
81
+ ## Migration authoring rules
82
+
83
+ Every migration in user code (and the examples in this repository) must:
84
+
85
+ - Provide both an `upgrade` and a `downgrade`. Genuinely one-way migrations
86
+ raise `IrreversibleMigrationError` from `downgrade`.
87
+ - Be **idempotent and pure**: no I/O, no network, no clocks. Re-applying a
88
+ migration to already-migrated state must be a no-op.
89
+ - Prefer the declarative helpers (`add_field`, `drop_field`, `rename_field`,
90
+ `coerce_field`, `require_field`) over hand-mutating dicts.
91
+ - Never store the version tag in `channel_values` — it lives in
92
+ `checkpoint.metadata` and the framework handles it for you.
93
+
94
+ ## Releasing
95
+
96
+ Releases are published via PyPI trusted publishing. Pushing a `vX.Y.Z` tag
97
+ triggers the `publish.yml` workflow, which builds and uploads the
98
+ distribution after running the full test suite. See
99
+ [SECURITY.md](./SECURITY.md) for vulnerability reporting.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nabil Chatbi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.