varda 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.
- varda-0.1.0/LICENSE +21 -0
- varda-0.1.0/PKG-INFO +206 -0
- varda-0.1.0/README.md +170 -0
- varda-0.1.0/pyproject.toml +212 -0
- varda-0.1.0/setup.cfg +4 -0
- varda-0.1.0/src/varda/__init__.py +29 -0
- varda-0.1.0/src/varda/__main__.py +8 -0
- varda-0.1.0/src/varda/anns.py +103 -0
- varda-0.1.0/src/varda/cli.py +277 -0
- varda-0.1.0/src/varda/ext.py +181 -0
- varda-0.1.0/src/varda/gen_docs.py +90 -0
- varda-0.1.0/src/varda/gen_sql.py +127 -0
- varda-0.1.0/src/varda/generators.py +24 -0
- varda-0.1.0/src/varda/model.py +259 -0
- varda-0.1.0/src/varda/profile/varda.yaml +290 -0
- varda-0.1.0/src/varda/py.typed +0 -0
- varda-0.1.0/src/varda/registry.py +594 -0
- varda-0.1.0/src/varda/rules.py +596 -0
- varda-0.1.0/src/varda.egg-info/PKG-INFO +206 -0
- varda-0.1.0/src/varda.egg-info/SOURCES.txt +23 -0
- varda-0.1.0/src/varda.egg-info/dependency_links.txt +1 -0
- varda-0.1.0/src/varda.egg-info/entry_points.txt +2 -0
- varda-0.1.0/src/varda.egg-info/requires.txt +15 -0
- varda-0.1.0/src/varda.egg-info/top_level.txt +1 -0
- varda-0.1.0/tests/test_varda.py +1348 -0
varda-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Luttikhof
|
|
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.
|
varda-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: varda
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dimensional modeling for LinkML — a profile, its rules, and the generators that hang off it
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/mluttikh/varda
|
|
7
|
+
Project-URL: Documentation, https://mluttikh.github.io/varda/
|
|
8
|
+
Project-URL: Source, https://github.com/mluttikh/varda
|
|
9
|
+
Keywords: linkml,dimensional-modeling,star-schema,kimball,data-warehouse,data-mart,metadata
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: linkml-runtime>=1.9
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
25
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
27
|
+
Requires-Dist: pyyaml>=6; extra == "dev"
|
|
28
|
+
Requires-Dist: types-PyYAML>=6; extra == "dev"
|
|
29
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
30
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
31
|
+
Requires-Dist: actionlint-py>=1.7; extra == "dev"
|
|
32
|
+
Provides-Extra: docs
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
34
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Varda
|
|
38
|
+
|
|
39
|
+
[](https://github.com/mluttikh/varda/actions/workflows/ci.yml)
|
|
40
|
+
[](https://pypi.org/project/varda/)
|
|
41
|
+
[](https://pypi.org/project/varda/)
|
|
42
|
+
[](https://mluttikh.github.io/varda/)
|
|
43
|
+
|
|
44
|
+
**Dimensional modeling for [LinkML](https://linkml.io).**
|
|
45
|
+
|
|
46
|
+
Varda is a *profile* of LinkML: a small vocabulary that lets you say a class
|
|
47
|
+
is a fact table, that a column is a semi-additive measure, that a dimension
|
|
48
|
+
keeps history. It then checks those claims and generates from them.
|
|
49
|
+
|
|
50
|
+
A model annotated with Varda is still an ordinary LinkML schema. Every other
|
|
51
|
+
LinkML tool — `gen-pydantic`, `gen-owl`, `gen-json-schema` — will read it
|
|
52
|
+
happily and ignore what it does not understand.
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
FctSale:
|
|
56
|
+
annotations:
|
|
57
|
+
varda:role: fact
|
|
58
|
+
varda:fact_type: transaction
|
|
59
|
+
varda:grain: one row per product per line of a sales receipt
|
|
60
|
+
attributes:
|
|
61
|
+
customer_key:
|
|
62
|
+
range: integer
|
|
63
|
+
annotations:
|
|
64
|
+
varda:role: foreign_key
|
|
65
|
+
varda:references: DimCustomer
|
|
66
|
+
net_amount:
|
|
67
|
+
range: decimal
|
|
68
|
+
annotations:
|
|
69
|
+
varda:role: measure
|
|
70
|
+
varda:additivity: additive
|
|
71
|
+
varda:unit: EUR
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```console
|
|
75
|
+
$ varda check model.yaml
|
|
76
|
+
8 tables checked against 22 rules (varda 0.1.0): 0 errors, 0 warnings
|
|
77
|
+
|
|
78
|
+
$ varda generate model.yaml --out out/
|
|
79
|
+
wrote out/docs/model.md
|
|
80
|
+
wrote out/sql/mart.sql
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Install
|
|
84
|
+
|
|
85
|
+
```console
|
|
86
|
+
pip install varda
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Python 3.11+. The only runtime dependency is `linkml-runtime`.
|
|
90
|
+
|
|
91
|
+
## What it gives you
|
|
92
|
+
|
|
93
|
+
**Eleven annotations.** Five on tables — `role`, `grain`, `fact_type`, `scd`,
|
|
94
|
+
`physical_name`. Six on columns — `role`, `references`, `additivity`,
|
|
95
|
+
`semi_additive_over`, `unit`, `physical_name`. That is the whole core
|
|
96
|
+
vocabulary, and it is deliberately the whole core vocabulary.
|
|
97
|
+
|
|
98
|
+
**Twenty-two rules** that catch the mistakes worth catching:
|
|
99
|
+
|
|
100
|
+
| | |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `V001`–`V003` | the annotations themselves — typos, bad enum values, unknown prefixes |
|
|
103
|
+
| `V101`–`V113` | structure — a fact without a grain, a foreign key pointing at a fact, a dimension with no natural key |
|
|
104
|
+
| `V201`–`V206` | measures — an unclassified measure, a semi-additive one that never says what it cannot cross |
|
|
105
|
+
|
|
106
|
+
The `V2xx` family exists because additivity is where the expensive errors
|
|
107
|
+
live. A structural mistake usually breaks a query. An additivity mistake
|
|
108
|
+
returns a number that looks entirely reasonable and is wrong, to someone who
|
|
109
|
+
will act on it.
|
|
110
|
+
|
|
111
|
+
**Two generators**, `sql` and `docs`, producing runnable DDL and a Markdown
|
|
112
|
+
reference. Both are deterministic: no timestamps, no environment, same model
|
|
113
|
+
in and same bytes out, so the output can be committed and diffed.
|
|
114
|
+
|
|
115
|
+
## Extending it
|
|
116
|
+
|
|
117
|
+
Varda's core is small on purpose. Anything specific to how *your*
|
|
118
|
+
organization works — cost centers, retention, data classification, ownership
|
|
119
|
+
— goes in an extension under your own prefix.
|
|
120
|
+
|
|
121
|
+
The smallest useful extension needs no Python at all. Write a LinkML schema
|
|
122
|
+
declaring your vocabulary, then a `varda.toml`:
|
|
123
|
+
|
|
124
|
+
```toml
|
|
125
|
+
[[extension]]
|
|
126
|
+
name = "acme"
|
|
127
|
+
prefix = "acme"
|
|
128
|
+
profile = "profiles/acme.yaml"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
From then on `acme:cost_center` is a first-class annotation: checked for
|
|
132
|
+
typos, its enum values enforced, and listed by `varda ext`. Misspell it and
|
|
133
|
+
you get
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
ERROR V001 DimStore
|
|
137
|
+
unknown table annotation 'acme:cost_center'; declare it in
|
|
138
|
+
acme.yaml or fix the typo
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Full documentation: **<https://mluttikh.github.io/varda/>**
|
|
142
|
+
|
|
143
|
+
An extension with code behind it adds rules and generators through
|
|
144
|
+
`varda.ext`, and ships as an installable package advertising the
|
|
145
|
+
`varda.extensions` entry point. See [`SPEC.md`](SPEC.md) for the interface
|
|
146
|
+
and `tests/fixtures/acme_ext/` for a complete worked example.
|
|
147
|
+
|
|
148
|
+
**One party, one namespace; extensions add, they never redefine.** An
|
|
149
|
+
extension may introduce annotations, enums and rules under its own prefix. It
|
|
150
|
+
may not add a value to `TableRole` or change what `semi_additive` means —
|
|
151
|
+
every generator dispatches exhaustively on those, and the registry refuses at
|
|
152
|
+
load rather than warning.
|
|
153
|
+
|
|
154
|
+
## Commands
|
|
155
|
+
|
|
156
|
+
| | |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `varda check MODEL` | validate; `--strict` fails on warnings too |
|
|
159
|
+
| `varda generate MODEL --out DIR` | write artifacts; fails closed |
|
|
160
|
+
| `varda rules` | list every rule, `-v` for reasoning |
|
|
161
|
+
| `varda ext` | describe active extensions and their vocabulary |
|
|
162
|
+
| `varda importmap` | print the LinkML import map |
|
|
163
|
+
|
|
164
|
+
Exit codes are part of the contract: `0` success, `1` the model or run
|
|
165
|
+
failed, `2` the invocation was wrong.
|
|
166
|
+
|
|
167
|
+
## Status
|
|
168
|
+
|
|
169
|
+
**0.1.0 — alpha.** The vocabulary and rule codes are stable enough to build
|
|
170
|
+
on; rule codes will not be renumbered. Analytical functions, model diffing,
|
|
171
|
+
lineage and the drift gate are deliberately not here yet — see `SPEC.md` for
|
|
172
|
+
what is deferred and why.
|
|
173
|
+
|
|
174
|
+
## Building the documentation
|
|
175
|
+
|
|
176
|
+
```console
|
|
177
|
+
pip install -e ".[docs]"
|
|
178
|
+
mkdocs serve
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Then open **<http://127.0.0.1:8000/varda/>** — note the `/varda/` path, which
|
|
182
|
+
comes from `site_url` because this is a GitHub Pages project site rather than
|
|
183
|
+
a user site. Plain `http://127.0.0.1:8000/` redirects there.
|
|
184
|
+
|
|
185
|
+
The vocabulary, rules and command-line pages are generated from the package
|
|
186
|
+
itself into a git-ignored `docs/reference/`, so they cannot drift from the
|
|
187
|
+
code. `mkdocs serve` regenerates them on every rebuild and watches `src/` as
|
|
188
|
+
well as `docs/` — edit a rule's docstring and the page updates.
|
|
189
|
+
|
|
190
|
+
To build the static site the way CI does:
|
|
191
|
+
|
|
192
|
+
```console
|
|
193
|
+
python scripts/gen_reference.py
|
|
194
|
+
mkdocs build --strict
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`--strict` turns a broken internal link into a failed build. The generator is
|
|
198
|
+
a standalone script rather than a plugin, so the site also builds under
|
|
199
|
+
[Zensical](https://zensical.org) and [ProperDocs](https://properdocs.org) from
|
|
200
|
+
the same `mkdocs.yml` — see `docs/design.md` for why that matters.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT for the code. The profile vocabulary in `src/varda/profile/varda.yaml` is
|
|
205
|
+
CC0, so it can be reused anywhere without attribution — a vocabulary that
|
|
206
|
+
constrains its own reuse is not much of a vocabulary.
|
varda-0.1.0/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Varda
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mluttikh/varda/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/varda/)
|
|
5
|
+
[](https://pypi.org/project/varda/)
|
|
6
|
+
[](https://mluttikh.github.io/varda/)
|
|
7
|
+
|
|
8
|
+
**Dimensional modeling for [LinkML](https://linkml.io).**
|
|
9
|
+
|
|
10
|
+
Varda is a *profile* of LinkML: a small vocabulary that lets you say a class
|
|
11
|
+
is a fact table, that a column is a semi-additive measure, that a dimension
|
|
12
|
+
keeps history. It then checks those claims and generates from them.
|
|
13
|
+
|
|
14
|
+
A model annotated with Varda is still an ordinary LinkML schema. Every other
|
|
15
|
+
LinkML tool — `gen-pydantic`, `gen-owl`, `gen-json-schema` — will read it
|
|
16
|
+
happily and ignore what it does not understand.
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
FctSale:
|
|
20
|
+
annotations:
|
|
21
|
+
varda:role: fact
|
|
22
|
+
varda:fact_type: transaction
|
|
23
|
+
varda:grain: one row per product per line of a sales receipt
|
|
24
|
+
attributes:
|
|
25
|
+
customer_key:
|
|
26
|
+
range: integer
|
|
27
|
+
annotations:
|
|
28
|
+
varda:role: foreign_key
|
|
29
|
+
varda:references: DimCustomer
|
|
30
|
+
net_amount:
|
|
31
|
+
range: decimal
|
|
32
|
+
annotations:
|
|
33
|
+
varda:role: measure
|
|
34
|
+
varda:additivity: additive
|
|
35
|
+
varda:unit: EUR
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ varda check model.yaml
|
|
40
|
+
8 tables checked against 22 rules (varda 0.1.0): 0 errors, 0 warnings
|
|
41
|
+
|
|
42
|
+
$ varda generate model.yaml --out out/
|
|
43
|
+
wrote out/docs/model.md
|
|
44
|
+
wrote out/sql/mart.sql
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
pip install varda
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Python 3.11+. The only runtime dependency is `linkml-runtime`.
|
|
54
|
+
|
|
55
|
+
## What it gives you
|
|
56
|
+
|
|
57
|
+
**Eleven annotations.** Five on tables — `role`, `grain`, `fact_type`, `scd`,
|
|
58
|
+
`physical_name`. Six on columns — `role`, `references`, `additivity`,
|
|
59
|
+
`semi_additive_over`, `unit`, `physical_name`. That is the whole core
|
|
60
|
+
vocabulary, and it is deliberately the whole core vocabulary.
|
|
61
|
+
|
|
62
|
+
**Twenty-two rules** that catch the mistakes worth catching:
|
|
63
|
+
|
|
64
|
+
| | |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `V001`–`V003` | the annotations themselves — typos, bad enum values, unknown prefixes |
|
|
67
|
+
| `V101`–`V113` | structure — a fact without a grain, a foreign key pointing at a fact, a dimension with no natural key |
|
|
68
|
+
| `V201`–`V206` | measures — an unclassified measure, a semi-additive one that never says what it cannot cross |
|
|
69
|
+
|
|
70
|
+
The `V2xx` family exists because additivity is where the expensive errors
|
|
71
|
+
live. A structural mistake usually breaks a query. An additivity mistake
|
|
72
|
+
returns a number that looks entirely reasonable and is wrong, to someone who
|
|
73
|
+
will act on it.
|
|
74
|
+
|
|
75
|
+
**Two generators**, `sql` and `docs`, producing runnable DDL and a Markdown
|
|
76
|
+
reference. Both are deterministic: no timestamps, no environment, same model
|
|
77
|
+
in and same bytes out, so the output can be committed and diffed.
|
|
78
|
+
|
|
79
|
+
## Extending it
|
|
80
|
+
|
|
81
|
+
Varda's core is small on purpose. Anything specific to how *your*
|
|
82
|
+
organization works — cost centers, retention, data classification, ownership
|
|
83
|
+
— goes in an extension under your own prefix.
|
|
84
|
+
|
|
85
|
+
The smallest useful extension needs no Python at all. Write a LinkML schema
|
|
86
|
+
declaring your vocabulary, then a `varda.toml`:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[[extension]]
|
|
90
|
+
name = "acme"
|
|
91
|
+
prefix = "acme"
|
|
92
|
+
profile = "profiles/acme.yaml"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
From then on `acme:cost_center` is a first-class annotation: checked for
|
|
96
|
+
typos, its enum values enforced, and listed by `varda ext`. Misspell it and
|
|
97
|
+
you get
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
ERROR V001 DimStore
|
|
101
|
+
unknown table annotation 'acme:cost_center'; declare it in
|
|
102
|
+
acme.yaml or fix the typo
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Full documentation: **<https://mluttikh.github.io/varda/>**
|
|
106
|
+
|
|
107
|
+
An extension with code behind it adds rules and generators through
|
|
108
|
+
`varda.ext`, and ships as an installable package advertising the
|
|
109
|
+
`varda.extensions` entry point. See [`SPEC.md`](SPEC.md) for the interface
|
|
110
|
+
and `tests/fixtures/acme_ext/` for a complete worked example.
|
|
111
|
+
|
|
112
|
+
**One party, one namespace; extensions add, they never redefine.** An
|
|
113
|
+
extension may introduce annotations, enums and rules under its own prefix. It
|
|
114
|
+
may not add a value to `TableRole` or change what `semi_additive` means —
|
|
115
|
+
every generator dispatches exhaustively on those, and the registry refuses at
|
|
116
|
+
load rather than warning.
|
|
117
|
+
|
|
118
|
+
## Commands
|
|
119
|
+
|
|
120
|
+
| | |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `varda check MODEL` | validate; `--strict` fails on warnings too |
|
|
123
|
+
| `varda generate MODEL --out DIR` | write artifacts; fails closed |
|
|
124
|
+
| `varda rules` | list every rule, `-v` for reasoning |
|
|
125
|
+
| `varda ext` | describe active extensions and their vocabulary |
|
|
126
|
+
| `varda importmap` | print the LinkML import map |
|
|
127
|
+
|
|
128
|
+
Exit codes are part of the contract: `0` success, `1` the model or run
|
|
129
|
+
failed, `2` the invocation was wrong.
|
|
130
|
+
|
|
131
|
+
## Status
|
|
132
|
+
|
|
133
|
+
**0.1.0 — alpha.** The vocabulary and rule codes are stable enough to build
|
|
134
|
+
on; rule codes will not be renumbered. Analytical functions, model diffing,
|
|
135
|
+
lineage and the drift gate are deliberately not here yet — see `SPEC.md` for
|
|
136
|
+
what is deferred and why.
|
|
137
|
+
|
|
138
|
+
## Building the documentation
|
|
139
|
+
|
|
140
|
+
```console
|
|
141
|
+
pip install -e ".[docs]"
|
|
142
|
+
mkdocs serve
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then open **<http://127.0.0.1:8000/varda/>** — note the `/varda/` path, which
|
|
146
|
+
comes from `site_url` because this is a GitHub Pages project site rather than
|
|
147
|
+
a user site. Plain `http://127.0.0.1:8000/` redirects there.
|
|
148
|
+
|
|
149
|
+
The vocabulary, rules and command-line pages are generated from the package
|
|
150
|
+
itself into a git-ignored `docs/reference/`, so they cannot drift from the
|
|
151
|
+
code. `mkdocs serve` regenerates them on every rebuild and watches `src/` as
|
|
152
|
+
well as `docs/` — edit a rule's docstring and the page updates.
|
|
153
|
+
|
|
154
|
+
To build the static site the way CI does:
|
|
155
|
+
|
|
156
|
+
```console
|
|
157
|
+
python scripts/gen_reference.py
|
|
158
|
+
mkdocs build --strict
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`--strict` turns a broken internal link into a failed build. The generator is
|
|
162
|
+
a standalone script rather than a plugin, so the site also builds under
|
|
163
|
+
[Zensical](https://zensical.org) and [ProperDocs](https://properdocs.org) from
|
|
164
|
+
the same `mkdocs.yml` — see `docs/design.md` for why that matters.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT for the code. The profile vocabulary in `src/varda/profile/varda.yaml` is
|
|
169
|
+
CC0, so it can be reused anywhere without attribution — a vocabulary that
|
|
170
|
+
constrains its own reuse is not much of a vocabulary.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "varda"
|
|
7
|
+
|
|
8
|
+
# The version lives in `src/varda/__init__.py` and is read from there, rather
|
|
9
|
+
# than being declared here and repeated in the package. Two copies of a
|
|
10
|
+
# version number drift the first time somebody bumps one of them, and the
|
|
11
|
+
# failure is quiet: `varda check` prints `__version__` in its summary line, so
|
|
12
|
+
# a package would misreport itself to every user while the distribution
|
|
13
|
+
# metadata said something else. One source removes the class of mistake
|
|
14
|
+
# instead of adding a test that notices it afterwards.
|
|
15
|
+
dynamic = ["version"]
|
|
16
|
+
description = "Dimensional modeling for LinkML — a profile, its rules, and the generators that hang off it"
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
requires-python = ">=3.11"
|
|
19
|
+
license = "MIT"
|
|
20
|
+
keywords = [
|
|
21
|
+
"linkml",
|
|
22
|
+
"dimensional-modeling",
|
|
23
|
+
"star-schema",
|
|
24
|
+
"kimball",
|
|
25
|
+
"data-warehouse",
|
|
26
|
+
"data-mart",
|
|
27
|
+
"metadata",
|
|
28
|
+
]
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Development Status :: 3 - Alpha",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Programming Language :: Python :: 3.14",
|
|
36
|
+
"Topic :: Database",
|
|
37
|
+
"Topic :: Software Development :: Code Generators",
|
|
38
|
+
"Typing :: Typed",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# Only linkml-runtime. The full `linkml` distribution pulls a large tree in
|
|
42
|
+
# order to provide generators, and Varda's generators are hand-written against
|
|
43
|
+
# the model layer rather than against LinkML's generator framework. A minimal
|
|
44
|
+
# dependency set is most of what makes this installable inside somebody else's
|
|
45
|
+
# pipeline without an argument.
|
|
46
|
+
dependencies = ["linkml-runtime>=1.9"]
|
|
47
|
+
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
dev = [
|
|
50
|
+
"pytest>=8",
|
|
51
|
+
"mypy>=1.11",
|
|
52
|
+
"ruff>=0.6",
|
|
53
|
+
"pyyaml>=6",
|
|
54
|
+
"types-PyYAML>=6",
|
|
55
|
+
"build>=1",
|
|
56
|
+
"twine>=5",
|
|
57
|
+
# Vendors the actionlint binary. The workflows are code that only
|
|
58
|
+
# ever runs on a push, so a mistake in them is otherwise found by
|
|
59
|
+
# pushing a broken one and reading the failure.
|
|
60
|
+
"actionlint-py>=1.7",
|
|
61
|
+
]
|
|
62
|
+
docs = [
|
|
63
|
+
"mkdocs-material>=9.5",
|
|
64
|
+
# Pinned below 2.0 deliberately. MkDocs last shipped 1.6.1 in August
|
|
65
|
+
# 2024, and its maintainer has announced a 2.0 that will not support
|
|
66
|
+
# existing themes, plugins or config files. An unpinned `mkdocs` could
|
|
67
|
+
# therefore break the docs build without a change on our side.
|
|
68
|
+
#
|
|
69
|
+
# `properdocs`, a maintained continuation of 1.x, has been verified as a
|
|
70
|
+
# drop-in: `properdocs build --strict` produces an identical site from
|
|
71
|
+
# this same mkdocs.yml. If 2.0 lands and the pin becomes untenable, that
|
|
72
|
+
# is the migration, and it is a one-line change here.
|
|
73
|
+
"mkdocs>=1.6,<2",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[project.urls]
|
|
77
|
+
Homepage = "https://github.com/mluttikh/varda"
|
|
78
|
+
Documentation = "https://mluttikh.github.io/varda/"
|
|
79
|
+
Source = "https://github.com/mluttikh/varda"
|
|
80
|
+
|
|
81
|
+
[project.scripts]
|
|
82
|
+
varda = "varda.cli:main"
|
|
83
|
+
|
|
84
|
+
[project.entry-points."varda.extensions"]
|
|
85
|
+
# Extensions register here. Varda itself does not — it is built in, so that
|
|
86
|
+
# the core is present even when nothing else is installed.
|
|
87
|
+
|
|
88
|
+
[tool.setuptools.packages.find]
|
|
89
|
+
where = ["src"]
|
|
90
|
+
|
|
91
|
+
[tool.setuptools.dynamic]
|
|
92
|
+
version = { attr = "varda.__version__" }
|
|
93
|
+
|
|
94
|
+
# Ships the type information. An extension author's own type checker then sees
|
|
95
|
+
# the boundary Varda maintains: `model.py` is the wall built along the untyped
|
|
96
|
+
# LinkML runtime, and everything it returns is a concrete type.
|
|
97
|
+
[tool.setuptools.package-data]
|
|
98
|
+
varda = ["py.typed", "profile/*.yaml"]
|
|
99
|
+
|
|
100
|
+
[tool.pytest.ini_options]
|
|
101
|
+
testpaths = ["tests"]
|
|
102
|
+
|
|
103
|
+
[tool.mypy]
|
|
104
|
+
python_version = "3.11"
|
|
105
|
+
files = ["src/varda", "tests", "scripts"]
|
|
106
|
+
strict = true
|
|
107
|
+
|
|
108
|
+
# LinkML ships no `py.typed`, so every object out of `SchemaView` is `Any`.
|
|
109
|
+
# That is not a defect to suppress and forget: it is the boundary of the typed
|
|
110
|
+
# world, and `varda/model.py` is the wall built along it.
|
|
111
|
+
#
|
|
112
|
+
# `warn_return_any` is the rule doing the work. It fires whenever an `Any`
|
|
113
|
+
# escapes into a signature claiming something specific, which is exactly the
|
|
114
|
+
# mistake this arrangement exists to catch. Do not switch it off to make a
|
|
115
|
+
# stubborn line pass.
|
|
116
|
+
[[tool.mypy.overrides]]
|
|
117
|
+
module = ["linkml_runtime.*", "jsonasobj2"]
|
|
118
|
+
ignore_missing_imports = true
|
|
119
|
+
|
|
120
|
+
[tool.ruff]
|
|
121
|
+
line-length = 80
|
|
122
|
+
extend-exclude = ["out"]
|
|
123
|
+
|
|
124
|
+
[tool.ruff.lint]
|
|
125
|
+
select = [
|
|
126
|
+
"A", # flake8-builtins
|
|
127
|
+
"ARG", # unused-function-argument
|
|
128
|
+
"B", # flake8-bugbear
|
|
129
|
+
"BLE", # flake8-blind-except
|
|
130
|
+
"C4", # comprehensions
|
|
131
|
+
"C90", # mccabe
|
|
132
|
+
"D", # pydocstyle
|
|
133
|
+
"E", # pycodestyle
|
|
134
|
+
"EM", # flake8-errmsg
|
|
135
|
+
"ERA", # eradicate
|
|
136
|
+
"F", # pyflakes
|
|
137
|
+
"FA", # flake8-future-annotations
|
|
138
|
+
"FLY", # flynt
|
|
139
|
+
"FURB", # refurb
|
|
140
|
+
"G", # flake8-logging-format
|
|
141
|
+
"I", # isort
|
|
142
|
+
"ICN", # flake8-import-conventions
|
|
143
|
+
"INP", # flake8-no-pep420
|
|
144
|
+
"ISC", # flake8-implicit-str-concat
|
|
145
|
+
"LOG", # flake8-logging
|
|
146
|
+
"N", # pep8-naming
|
|
147
|
+
"PERF", # Perflint
|
|
148
|
+
"PGH", # pygrep-hooks
|
|
149
|
+
"PIE", # flake8-pie
|
|
150
|
+
"PLC", # pylint-convention
|
|
151
|
+
"PLE", # pylint-error
|
|
152
|
+
"PLR", # pylint-refactor
|
|
153
|
+
"PLW", # pylint-warning
|
|
154
|
+
"PT", # flake8-pytest-style
|
|
155
|
+
"PTH", # flake8-use-pathlib
|
|
156
|
+
"PYI", # flake8-pyi
|
|
157
|
+
"Q", # flake8-quotes
|
|
158
|
+
"RET", # flake8-return
|
|
159
|
+
"RSE", # flake8-raise
|
|
160
|
+
"RUF", # ruff-specific
|
|
161
|
+
"S", # flake8-bandit
|
|
162
|
+
"SIM", # flake8-simplify
|
|
163
|
+
"SLF", # flake8-self
|
|
164
|
+
"T10", # flake8-debugger
|
|
165
|
+
"T20", # flake8-print
|
|
166
|
+
"TCH", # flake8-type-checking
|
|
167
|
+
"TID", # flake8-tidy-imports
|
|
168
|
+
"UP", # pyupgrade
|
|
169
|
+
"W", # pycodestyle
|
|
170
|
+
"YTT", # flake8-2020
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
[tool.ruff.lint.pydocstyle]
|
|
174
|
+
convention = "pep257"
|
|
175
|
+
|
|
176
|
+
[tool.ruff.lint.per-file-ignores]
|
|
177
|
+
"__init__.py" = ["F401"]
|
|
178
|
+
"tests/**" = [
|
|
179
|
+
"D103", # undocumented-public-function
|
|
180
|
+
"PLR2004", # magic-value-comparison
|
|
181
|
+
"S101", # assert
|
|
182
|
+
"SLF001", # private-member-access
|
|
183
|
+
# INP001 wants an `__init__.py` here. Adding one would make `tests` a
|
|
184
|
+
# package, which changes how pytest imports these modules and breaks the
|
|
185
|
+
# `sys.path` insertion in `conftest.py` that makes the extension fixture
|
|
186
|
+
# importable the way an installed extension would be. The rule exists to
|
|
187
|
+
# catch source accidentally left as a namespace package; a pytest test
|
|
188
|
+
# directory is not that.
|
|
189
|
+
"INP001",
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
# Three deviations, each specific to this codebase rather than to taste.
|
|
193
|
+
#
|
|
194
|
+
# T201 in the CLI: a command-line tool's output *is* `print`. Routing it
|
|
195
|
+
# through a logger to satisfy the rule would make the thing worse at its job.
|
|
196
|
+
"src/varda/cli.py" = ["T201"]
|
|
197
|
+
#
|
|
198
|
+
# D102 in the model layer: `model.py` is mostly one-line property wrappers,
|
|
199
|
+
# and the only docstring they could carry is the property name restated. The
|
|
200
|
+
# class docstrings carry the meaning; the accessors do not need one each.
|
|
201
|
+
"src/varda/model.py" = ["D102"]
|
|
202
|
+
#
|
|
203
|
+
# D103 in the rules: every rule registers a human-readable title in its
|
|
204
|
+
# decorator, and `varda rules` prints it. A docstring is reserved for rules
|
|
205
|
+
# whose *reasoning* is not obvious, and `varda rules -v` prints those.
|
|
206
|
+
# Requiring one on all twenty-one would duplicate the title twenty-one times.
|
|
207
|
+
"src/varda/rules.py" = ["D103"]
|
|
208
|
+
#
|
|
209
|
+
# scripts/ is build tooling, not an importable package — INP001 wants an
|
|
210
|
+
# __init__.py that would serve no purpose. T201 because writing to stdout is
|
|
211
|
+
# how a build script reports what it wrote.
|
|
212
|
+
"scripts/**" = ["INP001", "T201"]
|
varda-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Varda — dimensional modeling for LinkML.
|
|
2
|
+
|
|
3
|
+
A profile of LinkML that adds the vocabulary of dimensional modeling — facts,
|
|
4
|
+
dimensions, grain, additivity, slowly-changing dimensions — plus the rules
|
|
5
|
+
that check a model against it and the generators that build from it.
|
|
6
|
+
|
|
7
|
+
Third parties import :mod:`varda.ext` and nothing else. Everything reachable
|
|
8
|
+
from there is public and versioned; everything else is internal and moves
|
|
9
|
+
without notice.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
from .ext import Context, Extension, ExtensionError, Generator
|
|
17
|
+
from .model import DimensionalModel
|
|
18
|
+
from .rules import Finding, RuleSet
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Context",
|
|
22
|
+
"DimensionalModel",
|
|
23
|
+
"Extension",
|
|
24
|
+
"ExtensionError",
|
|
25
|
+
"Finding",
|
|
26
|
+
"Generator",
|
|
27
|
+
"RuleSet",
|
|
28
|
+
"__version__",
|
|
29
|
+
]
|