ddmodl 0.1.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.
- ddmodl-0.1.0/.envrc +1 -0
- ddmodl-0.1.0/.github/copilot-instructions.md +116 -0
- ddmodl-0.1.0/.gitignore +160 -0
- ddmodl-0.1.0/.pre-commit-config.yaml +39 -0
- ddmodl-0.1.0/CHANGELOG.md +12 -0
- ddmodl-0.1.0/CONTRIBUTING.md +73 -0
- ddmodl-0.1.0/LICENSE +373 -0
- ddmodl-0.1.0/PKG-INFO +932 -0
- ddmodl-0.1.0/README.md +915 -0
- ddmodl-0.1.0/diff_report_template.md +467 -0
- ddmodl-0.1.0/example-stakeholder-ir.json +6329 -0
- ddmodl-0.1.0/figures/modl_banner.PNG +0 -0
- ddmodl-0.1.0/figures/modl_logo.PNG +0 -0
- ddmodl-0.1.0/pyproject.toml +64 -0
- ddmodl-0.1.0/src/modl/__init__.py +16 -0
- ddmodl-0.1.0/src/modl/adapt.py +819 -0
- ddmodl-0.1.0/src/modl/cli.py +382 -0
- ddmodl-0.1.0/src/modl/config.py +539 -0
- ddmodl-0.1.0/src/modl/ir.py +414 -0
- ddmodl-0.1.0/src/modl/ledger.py +355 -0
- ddmodl-0.1.0/src/modl/models.py +80 -0
- ddmodl-0.1.0/src/modl/sync.py +773 -0
- ddmodl-0.1.0/tests/README.md +174 -0
- ddmodl-0.1.0/tests/test_adapt.py +1000 -0
- ddmodl-0.1.0/tests/test_cli.py +277 -0
- ddmodl-0.1.0/tests/test_config.py +612 -0
- ddmodl-0.1.0/tests/test_ir.py +1126 -0
- ddmodl-0.1.0/tests/test_ledger.py +927 -0
- ddmodl-0.1.0/tests/test_models.py +195 -0
- ddmodl-0.1.0/tests/test_modl.py +6 -0
- ddmodl-0.1.0/tests/test_sync.py +1169 -0
- ddmodl-0.1.0/uv.lock +1544 -0
ddmodl-0.1.0/.envrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[[ -d .venv ]] && source .venv/bin/activate
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Model Ledger — Project Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
`modl` is a CLI tool for maintaining an **append-only identity ledger** for evolving data models.
|
|
6
|
+
It tracks how model elements (entities and properties) change over time using four CSV tables:
|
|
7
|
+
`concepts`, `revisions`, `contracts`, `bindings`.
|
|
8
|
+
|
|
9
|
+
The tool is language-agnostic: language adapters produce a diff report in ModL's IR format, which feeds the sync engine. See [README.md](../README.md) for the full picture with examples.
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Config YAML → BreakingChangeConfig (config.py)
|
|
15
|
+
Diff JSON → DiffReport (ir.py)
|
|
16
|
+
↓
|
|
17
|
+
sync() [cli.py → ledger.py]
|
|
18
|
+
↓
|
|
19
|
+
Four CSV files on disk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Module | Role |
|
|
23
|
+
|--------|------|
|
|
24
|
+
| `cli.py` | Entry point; `modl sync` command via `rich-click` |
|
|
25
|
+
| `config.py` | Pydantic models for the YAML config (namespace, breaking change rules) |
|
|
26
|
+
| `ir.py` | Intermediate representation: `DiffReport`, `EntityChanged`, `PropertyChanged`, `ChangeType` |
|
|
27
|
+
| `models.py` | Pydantic row models for the four ledger tables; `ElementStatus`, `ElementKind` enums (`ENTITY`, `PROPERTY`, `ENUMERATION_SET`, `ENUM_VALUE`) |
|
|
28
|
+
| `ledger.py` | Ledger I/O, multi-level CSV validation, base-36 URI serial encoding |
|
|
29
|
+
|
|
30
|
+
## Domain Vocabulary
|
|
31
|
+
|
|
32
|
+
| Term | Meaning |
|
|
33
|
+
|------|---------|
|
|
34
|
+
| **Concept** | The agreed meaning of a model element (stable identity, independent of implementation) |
|
|
35
|
+
| **Revision** | An audit record of any detected change (breaking or not) |
|
|
36
|
+
| **Contract** | A versioned data contract for a concept — each contract captures a distinct variant of the concept's essential metadata |
|
|
37
|
+
| **Binding** | Maps a property contract to a runtime path via an instance label |
|
|
38
|
+
| **Entity** | Top-level model object (e.g., `Vehicle`, `Vehicle.Door`) — receives concepts/revisions/contracts; **no bindings** |
|
|
39
|
+
| **Property** | Field of an entity (e.g., `Vehicle.Speed`, `Vehicle.Door.IsOpen`) — receives concepts/revisions/contracts; **bindings** (one per instance, or one singleton if no instances) |
|
|
40
|
+
| **EnumerationSet** | Vocabulary entity (enum type, unit group, code list) — receives concepts/revisions/contracts but **no bindings**; set `kind: ENUMERATION_SET` in the diff event |
|
|
41
|
+
| **EnumValue** | Child of an `EnumerationSet` (e.g., `SpeedUnit.KMH`) — receives concepts/revisions/contracts but **no bindings**; set `kind: ENUM_VALUE` in the diff event |
|
|
42
|
+
| **Instance** | A concrete occurrence of a multi-instance entity (e.g., `Left`, `Right`) |
|
|
43
|
+
| **Breaking change** | Modification to an attribute listed in `essential_attributes` in the config |
|
|
44
|
+
|
|
45
|
+
## Coding Conventions
|
|
46
|
+
|
|
47
|
+
- **Python ≥ 3.11**; all modules start with `from __future__ import annotations`
|
|
48
|
+
- **Type hints everywhere**: use `|` union syntax, `Path` for file paths, `dict[str, Any]` for flexible dicts
|
|
49
|
+
- **Pydantic v2** for all data models: `BaseModel` + `ConfigDict(extra="forbid")` on config models
|
|
50
|
+
- **Enums are `StrEnum`** subclasses with UPPERCASE string values
|
|
51
|
+
- **Field validators** use `@field_validator` + `@classmethod` (Pydantic v2 style)
|
|
52
|
+
- **Docstrings**: concise, meaning-first (e.g., "Indicates whether ..." not "This method checks whether ...")
|
|
53
|
+
- Ledger validation is **fail-fast**: stop and raise `LedgerValidationError` on the first violation
|
|
54
|
+
- URI serials are **base-36 encoded** (lowercase `0-9a-z`): `b36encode(serial)` must match the URI suffix
|
|
55
|
+
|
|
56
|
+
## Build and Test
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Install (with uv)
|
|
60
|
+
uv sync --dev
|
|
61
|
+
|
|
62
|
+
# Run tests
|
|
63
|
+
pytest
|
|
64
|
+
|
|
65
|
+
# Run tests with coverage
|
|
66
|
+
pytest --cov=modl
|
|
67
|
+
|
|
68
|
+
# Lint / format
|
|
69
|
+
ruff check src tests
|
|
70
|
+
ruff format src tests
|
|
71
|
+
|
|
72
|
+
# Type check
|
|
73
|
+
ty check src
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Line length is **120 characters** (ruff).
|
|
77
|
+
|
|
78
|
+
Lint rules enabled: `E, F, I, UP, PGH, B, SIM`.
|
|
79
|
+
|
|
80
|
+
## Config File Format
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
namespace:
|
|
84
|
+
namespace: "https://myproject.org/model/" # required; absolute URI ending with / or #
|
|
85
|
+
prefix: "mp" # optional display alias
|
|
86
|
+
entity:
|
|
87
|
+
name.modified: false # renames are non-breaking; suppresses --strict warnings
|
|
88
|
+
instances.added: false # adding an instance is non-breaking
|
|
89
|
+
instances.removed: true # removing an instance is breaking
|
|
90
|
+
type: true # breaking — triggers a new contract
|
|
91
|
+
description: false # known, non-breaking; suppresses --strict warnings
|
|
92
|
+
property:
|
|
93
|
+
name.modified: false
|
|
94
|
+
output_type: true # breaking — triggers a new contract
|
|
95
|
+
unit: true # breaking — triggers a new contract
|
|
96
|
+
description: false # known, non-breaking; suppresses --strict warnings
|
|
97
|
+
enumeration_set:
|
|
98
|
+
name.modified: false
|
|
99
|
+
values.added: false
|
|
100
|
+
values.removed: true
|
|
101
|
+
enum_value:
|
|
102
|
+
name.modified: true
|
|
103
|
+
symbol: true
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All four sections (`entity`, `property`, `enumeration_set`, `enum_value`) are flat `dict[str, bool]` maps. Keys use a flat dotted form for per-op control (`unit.added`, `unit.modified`, `unit.removed`); a plain key is shorthand for all three ops. `true` = breaking, `false` = known non-breaking (silences `--strict`), absent = unknown (warns). Plain `name` is forbidden — use `name.modified`. The structural keys (`name.modified`, `instances.added`, `instances.removed`, `properties.added`, `properties.removed`, `values.added`, `values.removed`) are always recognised by the engine and never produce unknown-key warnings.
|
|
107
|
+
|
|
108
|
+
On entity `MODIFIED` events, instance-list changes are reported as a directional delta: `instances_added` and `instances_removed` (not the full `instances` list). The plain `instances` key is only valid on entity `ADDED` events.
|
|
109
|
+
|
|
110
|
+
## Test Conventions
|
|
111
|
+
|
|
112
|
+
- Use `tmp_path` (pytest built-in) for temporary file I/O
|
|
113
|
+
- Use `caplog` to assert on log output
|
|
114
|
+
- CLI tests use `click.testing.CliRunner`
|
|
115
|
+
- Use `pytest.raises(ValidationError)` to test Pydantic schema violations
|
|
116
|
+
- Prefer round-trip tests (write → read → compare) for I/O code
|
ddmodl-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# PyCharm
|
|
156
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
157
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
158
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
159
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
160
|
+
#.idea/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-ast
|
|
6
|
+
- id: check-builtin-literals
|
|
7
|
+
- id: check-case-conflict
|
|
8
|
+
- id: check-docstring-first
|
|
9
|
+
- id: check-illegal-windows-names
|
|
10
|
+
- id: check-merge-conflict
|
|
11
|
+
- id: check-json
|
|
12
|
+
- id: check-toml
|
|
13
|
+
- id: check-yaml
|
|
14
|
+
- id: end-of-file-fixer
|
|
15
|
+
- id: mixed-line-ending
|
|
16
|
+
- id: check-vcs-permalinks
|
|
17
|
+
- id: check-shebang-scripts-are-executable
|
|
18
|
+
- id: trailing-whitespace
|
|
19
|
+
- id: debug-statements
|
|
20
|
+
|
|
21
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
22
|
+
rev: v0.14.3
|
|
23
|
+
hooks:
|
|
24
|
+
- id: ruff
|
|
25
|
+
args: [--fix]
|
|
26
|
+
- id: ruff-format
|
|
27
|
+
|
|
28
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
29
|
+
rev: 0.9.7
|
|
30
|
+
hooks:
|
|
31
|
+
- id: uv-lock
|
|
32
|
+
|
|
33
|
+
- repo: local
|
|
34
|
+
hooks:
|
|
35
|
+
- id: ty
|
|
36
|
+
name: ty check
|
|
37
|
+
entry: uv run ty check
|
|
38
|
+
language: system
|
|
39
|
+
pass_filenames: false
|
|
@@ -0,0 +1,12 @@
|
|
|
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.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0] - 2026-05-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Everything (init)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Contributing to Model Ledger
|
|
2
|
+
|
|
3
|
+
## Development Environment
|
|
4
|
+
|
|
5
|
+
Model Ledger uses [uv](https://docs.astral.sh/uv/) for packaging and
|
|
6
|
+
dependency management. To start developing with Model Ledger, install `uv`
|
|
7
|
+
using the [recommended method](https://docs.astral.sh/uv/#getting-started).
|
|
8
|
+
|
|
9
|
+
Once `uv` is installed, install the dependencies with the following command:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
uv sync
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It will create a `.venv` in the root of the project.
|
|
16
|
+
|
|
17
|
+
If you want to have a shell in the virtual environment you can activate it with (for Linux/MacOS):
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
. .venv/bin/activate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The package is linked in editable mode so you will not need to reinstall the package when changing something.
|
|
24
|
+
|
|
25
|
+
Alternatively you can run things in the virtual environment by using a `uv run` prefix for commands, e.g.:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
uv run pytest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**The following section commands assume you are in the virtual environment by either activating or prefixing commands with `uv run`!**
|
|
32
|
+
|
|
33
|
+
### Pre-Commit-Hooks
|
|
34
|
+
|
|
35
|
+
Pre commit hooks can be setup with:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
pre-commit install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Tests
|
|
42
|
+
|
|
43
|
+
Run tests with the following command:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pytest --cov-report term-missing --cov=modl -vv
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
New code should ideally have tests and not break existing tests.
|
|
50
|
+
|
|
51
|
+
### Type Checking
|
|
52
|
+
|
|
53
|
+
Model Ledger uses type annotations throughout, and `ty` to do the checking. Run the following to type check Model Ledger:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ty check
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Code Formatting
|
|
60
|
+
|
|
61
|
+
Model Ledger uses [`ruff`](https://docs.astral.sh/ruff/) for code formatting.
|
|
62
|
+
Since it is very fast it makes sense to setup your editor to format on save.
|
|
63
|
+
|
|
64
|
+
Use `ruff format` to format all files in the current directory
|
|
65
|
+
|
|
66
|
+
### Versioning
|
|
67
|
+
|
|
68
|
+
- This tool is using [semantic versioning](https://semver.org/spec/v2.0.0.html). [CHANGELOG.md](./CHANGELOG.md) should be updated on every source code change.
|
|
69
|
+
- A new version can be bumped with the support of [bump-my-version](https://github.com/callowayproject/bump-my-version), which is a `dev` dependency:
|
|
70
|
+
```bash
|
|
71
|
+
# major,minor,patch
|
|
72
|
+
bump-my-version bump minor
|
|
73
|
+
```
|