wh40kdc 0.5.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.
- wh40kdc-0.5.0/.gitignore +45 -0
- wh40kdc-0.5.0/PKG-INFO +94 -0
- wh40kdc-0.5.0/README.md +66 -0
- wh40kdc-0.5.0/codegen/gen_typeddicts.py +63 -0
- wh40kdc-0.5.0/codegen/sync_bundle.py +56 -0
- wh40kdc-0.5.0/codegen/sync_spec.py +34 -0
- wh40kdc-0.5.0/pyproject.toml +86 -0
- wh40kdc-0.5.0/src/wh40kdc/__init__.py +76 -0
- wh40kdc-0.5.0/src/wh40kdc/_bundle.json +1 -0
- wh40kdc-0.5.0/src/wh40kdc/_spec.py +6 -0
- wh40kdc-0.5.0/src/wh40kdc/_types.py +835 -0
- wh40kdc-0.5.0/src/wh40kdc/_version.py +7 -0
- wh40kdc-0.5.0/src/wh40kdc/abilities_resolver.py +174 -0
- wh40kdc-0.5.0/src/wh40kdc/cruncher/__init__.py +31 -0
- wh40kdc-0.5.0/src/wh40kdc/cruncher/attribution.py +112 -0
- wh40kdc-0.5.0/src/wh40kdc/cruncher/buffs.py +207 -0
- wh40kdc-0.5.0/src/wh40kdc/cruncher/engine.py +520 -0
- wh40kdc-0.5.0/src/wh40kdc/cruncher/from_dsl.py +1157 -0
- wh40kdc-0.5.0/src/wh40kdc/cruncher/from_keyword.py +198 -0
- wh40kdc-0.5.0/src/wh40kdc/data/__init__.py +40 -0
- wh40kdc-0.5.0/src/wh40kdc/data/base.py +36 -0
- wh40kdc-0.5.0/src/wh40kdc/data/bundle.py +59 -0
- wh40kdc-0.5.0/src/wh40kdc/data/collection.py +164 -0
- wh40kdc-0.5.0/src/wh40kdc/data/dataset.py +371 -0
- wh40kdc-0.5.0/src/wh40kdc/data/entities.py +288 -0
- wh40kdc-0.5.0/src/wh40kdc/data/loadout.py +171 -0
- wh40kdc-0.5.0/src/wh40kdc/data/normalize.py +59 -0
- wh40kdc-0.5.0/src/wh40kdc/export/__init__.py +68 -0
- wh40kdc-0.5.0/src/wh40kdc/export/helpers.py +79 -0
- wh40kdc-0.5.0/src/wh40kdc/export/newrecruit_json.py +185 -0
- wh40kdc-0.5.0/src/wh40kdc/export/newrecruit_simple.py +113 -0
- wh40kdc-0.5.0/src/wh40kdc/export/newrecruit_wtc.py +154 -0
- wh40kdc-0.5.0/src/wh40kdc/export/roster_json.py +14 -0
- wh40kdc-0.5.0/src/wh40kdc/export/rosterizer.py +156 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/__init__.py +215 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/adapter.py +39 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/battlescribe.py +244 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/decode.py +76 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/gw.py +278 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/listforge.py +78 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_json.py +100 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_simple.py +236 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_text.py +126 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_wtc.py +413 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/resolve.py +321 -0
- wh40kdc-0.5.0/src/wh40kdc/imports/rosterizer.py +360 -0
- wh40kdc-0.5.0/src/wh40kdc/py.typed +0 -0
- wh40kdc-0.5.0/src/wh40kdc/runner.py +698 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/$defs/common.schema.json +143 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/$defs/game-version-ref.schema.json +11 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/deployment-pattern.schema.json +102 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/detachment.schema.json +56 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/enhancement.schema.json +46 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/faction.schema.json +29 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/force-disposition.schema.json +22 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/game-version.schema.json +20 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/leader-attachment.schema.json +18 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/mission-matchup.schema.json +25 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/mission.schema.json +42 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/roster.schema.json +218 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/secondary-card.schema.json +256 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/stratagem.schema.json +58 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/terrain-layout.schema.json +178 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/terrain-template.schema.json +105 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/unit-composition.schema.json +42 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/unit.schema.json +117 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/wargear-option.schema.json +73 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/wargear.schema.json +24 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/weapon-keyword.schema.json +31 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/core/weapon.schema.json +77 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/ability.schema.json +60 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/condition.schema.json +52 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/effect.schema.json +168 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/scope.schema.json +12 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/interaction-flag.schema.json +17 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/phase-mapping.schema.json +14 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/resource-pool.schema.json +36 -0
- wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/timing-flag.schema.json +28 -0
- wh40kdc-0.5.0/src/wh40kdc/scoring/__init__.py +237 -0
- wh40kdc-0.5.0/src/wh40kdc/terrain/__init__.py +25 -0
- wh40kdc-0.5.0/src/wh40kdc/terrain/keystones.py +114 -0
- wh40kdc-0.5.0/src/wh40kdc/terrain/resolve.py +263 -0
- wh40kdc-0.5.0/src/wh40kdc/translate/__init__.py +25 -0
- wh40kdc-0.5.0/src/wh40kdc/translate/condition.py +263 -0
- wh40kdc-0.5.0/src/wh40kdc/translate/effect.py +299 -0
- wh40kdc-0.5.0/src/wh40kdc/translate/scoring.py +83 -0
- wh40kdc-0.5.0/src/wh40kdc/validator.py +174 -0
wh40kdc-0.5.0/.gitignore
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
.vite/
|
|
4
|
+
.wrangler/
|
|
5
|
+
_site/
|
|
6
|
+
# HTML TypeDoc output for the Pages site — a CI build artifact assembled into
|
|
7
|
+
# _site/api/. The markdown counterpart (tools/docs/api/) stays committed for
|
|
8
|
+
# in-repo browsing; this HTML build is regenerated on every Pages deploy.
|
|
9
|
+
tools/docs/api-html/
|
|
10
|
+
/target/
|
|
11
|
+
/rust_analyzer/
|
|
12
|
+
tools/schemas/
|
|
13
|
+
tools/src/data/bundle.generated.ts
|
|
14
|
+
.DS_Store
|
|
15
|
+
*.log
|
|
16
|
+
rube-goldberg-output.txt
|
|
17
|
+
|
|
18
|
+
# _audit/ is the DSL-authoring worklist (#21). Keep the human-readable summaries
|
|
19
|
+
# (coverage.json, summary.md, proposed/*.md) but ignore the bulky, churn-prone
|
|
20
|
+
# machine-generated proposal/input JSON — it regenerates from the author-* tools.
|
|
21
|
+
/data/_audit/author-input/
|
|
22
|
+
/data/_audit/proposed/*.json
|
|
23
|
+
/data/_audit/worklist.json
|
|
24
|
+
|
|
25
|
+
# Fuzz-mode regression repros from the cross-impl parity differ. The directory
|
|
26
|
+
# itself is kept (via .gitkeep) so the differ has somewhere to write; the JSON
|
|
27
|
+
# repros are ephemeral — promoting one to the permanent corpus is a deliberate
|
|
28
|
+
# act and lands under conformance/.
|
|
29
|
+
/tooling/parity/regressions/*.json
|
|
30
|
+
|
|
31
|
+
# Python bytecode caches from running the differ locally.
|
|
32
|
+
__pycache__/
|
|
33
|
+
*.pyc
|
|
34
|
+
|
|
35
|
+
# Python package dev artifacts (python/ is the wh40kdc PyPI package).
|
|
36
|
+
/python/.venv/
|
|
37
|
+
/python/dist/
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
.mypy_cache/
|
|
40
|
+
.ruff_cache/
|
|
41
|
+
|
|
42
|
+
# IP-sensitive working material (source PDFs of upstream rulebooks, extracted
|
|
43
|
+
# prose, scratch notes) that must never be committed. Anything inside this
|
|
44
|
+
# directory stays local.
|
|
45
|
+
/_private/
|
wh40kdc-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wh40kdc
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Warhammer 40K dataset for the 40kdc-data schema layer: an embedded dataset behind a linked typed API, ListForge + NewRecruit roster importers and exporters, damage-projection cruncher, and a schema validator.
|
|
5
|
+
Project-URL: Homepage, https://40kdc.alpacasoft.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/wn-mitch/40kdc-data
|
|
7
|
+
Author: Alpaca Software
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: 40k,dataset,schema,wargaming,warhammer
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Games/Entertainment :: Board Games
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: jsonschema<5,>=4
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
22
|
+
Requires-Dist: datamodel-code-generator==0.60.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff==0.15.16; extra == 'dev'
|
|
26
|
+
Requires-Dist: types-jsonschema; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# wh40kdc
|
|
30
|
+
|
|
31
|
+
The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow
|
|
32
|
+
them to their weapons, abilities, phases, and factions. The Python counterpart
|
|
33
|
+
to [`@alpaca-software/40kdc-data`](https://www.npmjs.com/package/@alpaca-software/40kdc-data)
|
|
34
|
+
(npm) and [`wh40kdc`](https://crates.io/crates/wh40kdc) (crates.io), held in
|
|
35
|
+
behavioral lockstep with both by the shared conformance corpus in the
|
|
36
|
+
[40kdc-data repository](https://github.com/wn-mitch/40kdc-data).
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install wh40kdc
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from wh40kdc import Dataset
|
|
44
|
+
|
|
45
|
+
ds = Dataset.embedded()
|
|
46
|
+
unit = ds.units.find("Khârn the Betrayer")
|
|
47
|
+
for weapon in unit.weapons:
|
|
48
|
+
print(weapon.name)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
What ships:
|
|
52
|
+
|
|
53
|
+
- **Linked dataset API** — `Dataset` with id/name lookups (diacritic- and
|
|
54
|
+
punctuation-insensitive), reverse indexes, and join queries across units,
|
|
55
|
+
weapons, abilities, phases, and factions.
|
|
56
|
+
- **Importers** — ListForge, NewRecruit (JSON / WTC-compact / WTC-full /
|
|
57
|
+
simple), GW app export, and Rosterizer, all resolving to one `Roster` shape.
|
|
58
|
+
- **Exporters** — the same roster formats back out, byte-identical to the
|
|
59
|
+
TypeScript and Rust implementations.
|
|
60
|
+
- **Cruncher** — the damage-projection engine (attacks → hits → wounds →
|
|
61
|
+
unsaved → damage → after-FNP → models-killed) with buff resolution and
|
|
62
|
+
per-buff attribution.
|
|
63
|
+
- **Scoring, terrain, translation** — secondary-mission scoring, terrain
|
|
64
|
+
layout resolution and keystone measurements, and the ability-DSL /
|
|
65
|
+
scoring-card describers.
|
|
66
|
+
- **Validator** — `validate(target, value)` against the canonical JSON
|
|
67
|
+
Schemas, emitting the closed error-code enum shared by all implementations.
|
|
68
|
+
|
|
69
|
+
The conformance runner (used by the cross-implementation parity differ) is
|
|
70
|
+
invoked as `python -m wh40kdc.runner` — see `conformance/RUNNER_PROTOCOL.md`
|
|
71
|
+
in the repository.
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd python
|
|
77
|
+
uv venv && uv pip install -e ".[dev]"
|
|
78
|
+
pytest # includes the conformance suite when run inside the repo
|
|
79
|
+
ruff check .
|
|
80
|
+
mypy src
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Generated artifacts (`_bundle.json`, `_spec.py`, `_types.py`,
|
|
84
|
+
`src/wh40kdc/schemas/`) are produced by the scripts in `codegen/` and checked
|
|
85
|
+
for drift in CI — regenerate with:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python3 codegen/sync_bundle.py && python3 codegen/sync_spec.py && python3 codegen/gen_typeddicts.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT. The schema content the dataset describes is CC0; enrichment data is
|
|
94
|
+
CC BY 4.0 — see the repository's licensing table.
|
wh40kdc-0.5.0/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# wh40kdc
|
|
2
|
+
|
|
3
|
+
The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow
|
|
4
|
+
them to their weapons, abilities, phases, and factions. The Python counterpart
|
|
5
|
+
to [`@alpaca-software/40kdc-data`](https://www.npmjs.com/package/@alpaca-software/40kdc-data)
|
|
6
|
+
(npm) and [`wh40kdc`](https://crates.io/crates/wh40kdc) (crates.io), held in
|
|
7
|
+
behavioral lockstep with both by the shared conformance corpus in the
|
|
8
|
+
[40kdc-data repository](https://github.com/wn-mitch/40kdc-data).
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install wh40kdc
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from wh40kdc import Dataset
|
|
16
|
+
|
|
17
|
+
ds = Dataset.embedded()
|
|
18
|
+
unit = ds.units.find("Khârn the Betrayer")
|
|
19
|
+
for weapon in unit.weapons:
|
|
20
|
+
print(weapon.name)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
What ships:
|
|
24
|
+
|
|
25
|
+
- **Linked dataset API** — `Dataset` with id/name lookups (diacritic- and
|
|
26
|
+
punctuation-insensitive), reverse indexes, and join queries across units,
|
|
27
|
+
weapons, abilities, phases, and factions.
|
|
28
|
+
- **Importers** — ListForge, NewRecruit (JSON / WTC-compact / WTC-full /
|
|
29
|
+
simple), GW app export, and Rosterizer, all resolving to one `Roster` shape.
|
|
30
|
+
- **Exporters** — the same roster formats back out, byte-identical to the
|
|
31
|
+
TypeScript and Rust implementations.
|
|
32
|
+
- **Cruncher** — the damage-projection engine (attacks → hits → wounds →
|
|
33
|
+
unsaved → damage → after-FNP → models-killed) with buff resolution and
|
|
34
|
+
per-buff attribution.
|
|
35
|
+
- **Scoring, terrain, translation** — secondary-mission scoring, terrain
|
|
36
|
+
layout resolution and keystone measurements, and the ability-DSL /
|
|
37
|
+
scoring-card describers.
|
|
38
|
+
- **Validator** — `validate(target, value)` against the canonical JSON
|
|
39
|
+
Schemas, emitting the closed error-code enum shared by all implementations.
|
|
40
|
+
|
|
41
|
+
The conformance runner (used by the cross-implementation parity differ) is
|
|
42
|
+
invoked as `python -m wh40kdc.runner` — see `conformance/RUNNER_PROTOCOL.md`
|
|
43
|
+
in the repository.
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd python
|
|
49
|
+
uv venv && uv pip install -e ".[dev]"
|
|
50
|
+
pytest # includes the conformance suite when run inside the repo
|
|
51
|
+
ruff check .
|
|
52
|
+
mypy src
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Generated artifacts (`_bundle.json`, `_spec.py`, `_types.py`,
|
|
56
|
+
`src/wh40kdc/schemas/`) are produced by the scripts in `codegen/` and checked
|
|
57
|
+
for drift in CI — regenerate with:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python3 codegen/sync_bundle.py && python3 codegen/sync_spec.py && python3 codegen/gen_typeddicts.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT. The schema content the dataset describes is CC0; enrichment data is
|
|
66
|
+
CC BY 4.0 — see the repository's licensing table.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate TypedDict entity types from the bundled JSON Schema.
|
|
3
|
+
|
|
4
|
+
Runs datamodel-code-generator against
|
|
5
|
+
``crates/wh40kdc/schemas/bundled.schema.json`` (the same artifact Rust's
|
|
6
|
+
typify codegen consumes, produced by ``npm run bundle:schemas`` in tools/).
|
|
7
|
+
The output is a typing aid only — runtime values are plain dicts, mirroring
|
|
8
|
+
the TypeScript reference implementation.
|
|
9
|
+
|
|
10
|
+
``--no-use-closed-typed-dict`` keeps the output importable with the stdlib
|
|
11
|
+
``typing`` module alone (PEP 728 ``closed=True`` would require
|
|
12
|
+
typing_extensions at runtime); ``--disable-timestamp`` keeps regeneration
|
|
13
|
+
byte-stable for the CI drift check.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
23
|
+
SCHEMA = REPO_ROOT / "crates" / "wh40kdc" / "schemas" / "bundled.schema.json"
|
|
24
|
+
OUT = REPO_ROOT / "python" / "src" / "wh40kdc" / "_types.py"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> int:
|
|
28
|
+
if not SCHEMA.exists():
|
|
29
|
+
print(
|
|
30
|
+
f"missing {SCHEMA}; run `cd tools && npm run bundle:schemas` first",
|
|
31
|
+
file=sys.stderr,
|
|
32
|
+
)
|
|
33
|
+
return 1
|
|
34
|
+
cmd = [
|
|
35
|
+
sys.executable,
|
|
36
|
+
"-m",
|
|
37
|
+
"datamodel_code_generator",
|
|
38
|
+
"--input",
|
|
39
|
+
str(SCHEMA),
|
|
40
|
+
"--input-file-type",
|
|
41
|
+
"jsonschema",
|
|
42
|
+
"--output-model-type",
|
|
43
|
+
"typing.TypedDict",
|
|
44
|
+
"--output",
|
|
45
|
+
str(OUT),
|
|
46
|
+
"--target-python-version",
|
|
47
|
+
"3.11",
|
|
48
|
+
"--class-name",
|
|
49
|
+
"BundledSchemas",
|
|
50
|
+
"--no-use-closed-typed-dict",
|
|
51
|
+
"--disable-timestamp",
|
|
52
|
+
"--formatters",
|
|
53
|
+
"ruff-format",
|
|
54
|
+
"--custom-file-header",
|
|
55
|
+
"# Generated by codegen/gen_typeddicts.py — do not edit. CI fails on drift.",
|
|
56
|
+
]
|
|
57
|
+
subprocess.run(cmd, check=True)
|
|
58
|
+
print(f"wrote {OUT}")
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Sync the embedded data bundle and schema tree into the Python package.
|
|
3
|
+
|
|
4
|
+
The bundle is a byte-for-byte copy of the Rust crate's
|
|
5
|
+
``crates/wh40kdc/src/data/bundle.generated.json`` (itself regenerated by
|
|
6
|
+
``cargo run -p xtask -- bundle-data``). Copying — rather than re-deriving from
|
|
7
|
+
``data/`` — guarantees all three implementations share one bundler's file-walk
|
|
8
|
+
iteration order, which the set-semantics linked-api conformance queries depend
|
|
9
|
+
on (see CONFORMANCE.md).
|
|
10
|
+
|
|
11
|
+
The schema tree is copied because published wheels don't ship the repo's
|
|
12
|
+
``schemas/`` directory; the validator loads the packaged copy.
|
|
13
|
+
|
|
14
|
+
CI regenerates both and fails on drift (``git diff --exit-code``).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import shutil
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
24
|
+
RUST_BUNDLE = REPO_ROOT / "crates" / "wh40kdc" / "src" / "data" / "bundle.generated.json"
|
|
25
|
+
SCHEMAS_ROOT = REPO_ROOT / "schemas"
|
|
26
|
+
PKG_ROOT = REPO_ROOT / "python" / "src" / "wh40kdc"
|
|
27
|
+
PKG_BUNDLE = PKG_ROOT / "_bundle.json"
|
|
28
|
+
PKG_SCHEMAS = PKG_ROOT / "schemas"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> int:
|
|
32
|
+
if not RUST_BUNDLE.exists():
|
|
33
|
+
print(
|
|
34
|
+
f"missing {RUST_BUNDLE}; run `cargo run -p xtask -- bundle-data` first",
|
|
35
|
+
file=sys.stderr,
|
|
36
|
+
)
|
|
37
|
+
return 1
|
|
38
|
+
|
|
39
|
+
PKG_BUNDLE.write_bytes(RUST_BUNDLE.read_bytes())
|
|
40
|
+
print(f"wrote {PKG_BUNDLE} ({PKG_BUNDLE.stat().st_size} bytes)")
|
|
41
|
+
|
|
42
|
+
if PKG_SCHEMAS.exists():
|
|
43
|
+
shutil.rmtree(PKG_SCHEMAS)
|
|
44
|
+
count = 0
|
|
45
|
+
for src in sorted(SCHEMAS_ROOT.rglob("*.schema.json")):
|
|
46
|
+
rel = src.relative_to(SCHEMAS_ROOT)
|
|
47
|
+
dest = PKG_SCHEMAS / rel
|
|
48
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
dest.write_bytes(src.read_bytes())
|
|
50
|
+
count += 1
|
|
51
|
+
print(f"copied {count} schema files into {PKG_SCHEMAS}")
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Embed conformance/SPEC_VERSION as a generated constant.
|
|
3
|
+
|
|
4
|
+
The TS and Rust runners parent-walk to ``conformance/SPEC_VERSION`` at
|
|
5
|
+
runtime; published Python wheels don't ship the repo, so the version is baked
|
|
6
|
+
in at codegen time and drift-checked in CI.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
14
|
+
SPEC_FILE = REPO_ROOT / "conformance" / "SPEC_VERSION"
|
|
15
|
+
OUT = REPO_ROOT / "python" / "src" / "wh40kdc" / "_spec.py"
|
|
16
|
+
|
|
17
|
+
TEMPLATE = '''"""Generated by codegen/sync_spec.py — do not edit.
|
|
18
|
+
|
|
19
|
+
Mirrors conformance/SPEC_VERSION; CI fails on drift.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
SPEC_VERSION = {version}
|
|
23
|
+
'''
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main() -> int:
|
|
27
|
+
version = int(SPEC_FILE.read_text().strip())
|
|
28
|
+
OUT.write_text(TEMPLATE.format(version=version))
|
|
29
|
+
print(f"wrote {OUT} (SPEC_VERSION = {version})")
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wh40kdc"
|
|
7
|
+
# Version lives in src/wh40kdc/_version.py (single source), kept in lockstep
|
|
8
|
+
# with tools/package.json and crates/wh40kdc/Cargo.toml — CI hard-fails on
|
|
9
|
+
# mismatch (.github/workflows/validate.yml).
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = "Warhammer 40K dataset for the 40kdc-data schema layer: an embedded dataset behind a linked typed API, ListForge + NewRecruit roster importers and exporters, damage-projection cruncher, and a schema validator."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
authors = [{ name = "Alpaca Software" }]
|
|
16
|
+
keywords = ["warhammer", "40k", "wargaming", "schema", "dataset"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Games/Entertainment :: Board Games",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
# The only runtime dependency. Everything else (base64, zlib, re, unicodedata,
|
|
28
|
+
# math, json) is stdlib — keep it that way; see CONFORMANCE.md for why the
|
|
29
|
+
# implementations stay dependency-light.
|
|
30
|
+
dependencies = ["jsonschema>=4,<5"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://40kdc.alpacasoft.dev"
|
|
34
|
+
Repository = "https://github.com/wn-mitch/40kdc-data"
|
|
35
|
+
|
|
36
|
+
# NOTE: deliberately no [project.scripts] console script. The conformance
|
|
37
|
+
# runner is invoked as `python -m wh40kdc.runner` — a `wh40kdc-runner` script
|
|
38
|
+
# would collide with the Rust binary of the same name on PATH in CI's parity
|
|
39
|
+
# job (see conformance/RUNNER_PROTOCOL.md).
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=8",
|
|
44
|
+
"mypy>=1.13",
|
|
45
|
+
"types-jsonschema",
|
|
46
|
+
# Pinned: gen_typeddicts.py output must be byte-stable for the CI drift
|
|
47
|
+
# check (generator or formatter version drift changes the emitted text;
|
|
48
|
+
# the generator shells out to ruff-format, so ruff pins too).
|
|
49
|
+
"ruff==0.15.16",
|
|
50
|
+
"datamodel-code-generator==0.60.0",
|
|
51
|
+
"build>=1.2",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.version]
|
|
55
|
+
path = "src/wh40kdc/_version.py"
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["src/wh40kdc"]
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.sdist]
|
|
61
|
+
include = ["src/wh40kdc", "README.md", "codegen"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 100
|
|
65
|
+
src = ["src", "tests"]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = ["E", "F", "W", "I", "UP", "B", "RUF"]
|
|
69
|
+
# The ambiguous-unicode rules (RUF001-003) fight this package's substance:
|
|
70
|
+
# normalize_name's quote-variant regex and the test corpus are deliberately
|
|
71
|
+
# full of curly quotes and diacritics.
|
|
72
|
+
ignore = ["RUF001", "RUF002", "RUF003"]
|
|
73
|
+
# Generated code is exempt from style rules (mirrors the TS/Rust generated
|
|
74
|
+
# artifacts, which are also excluded from lint).
|
|
75
|
+
exclude = ["src/wh40kdc/_types.py"]
|
|
76
|
+
|
|
77
|
+
[tool.mypy]
|
|
78
|
+
python_version = "3.11"
|
|
79
|
+
strict = false
|
|
80
|
+
disallow_untyped_defs = true
|
|
81
|
+
warn_unused_ignores = true
|
|
82
|
+
no_implicit_optional = true
|
|
83
|
+
exclude = ["_types\\.py$"]
|
|
84
|
+
|
|
85
|
+
[tool.pytest.ini_options]
|
|
86
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""wh40kdc — the 40kdc Warhammer 40K dataset behind a linked, typed API.
|
|
2
|
+
|
|
3
|
+
Python counterpart to ``@alpaca-software/40kdc-data`` (npm) and the
|
|
4
|
+
``wh40kdc`` crate (crates.io), held in behavioral lockstep by the shared
|
|
5
|
+
conformance corpus. See the repository's CONFORMANCE.md.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from wh40kdc._version import __version__
|
|
9
|
+
from wh40kdc.abilities_resolver import resolve_eligible_abilities
|
|
10
|
+
from wh40kdc.cruncher import (
|
|
11
|
+
attribute_stages,
|
|
12
|
+
buffs_from_keyword,
|
|
13
|
+
crunch,
|
|
14
|
+
effect_to_buffs,
|
|
15
|
+
resolve_buffs,
|
|
16
|
+
)
|
|
17
|
+
from wh40kdc.data import (
|
|
18
|
+
Collection,
|
|
19
|
+
Dataset,
|
|
20
|
+
clamp_weapon_count,
|
|
21
|
+
maximal_loadout,
|
|
22
|
+
normalize_name,
|
|
23
|
+
option_cap,
|
|
24
|
+
validate_loadout,
|
|
25
|
+
weapon_bounds,
|
|
26
|
+
)
|
|
27
|
+
from wh40kdc.export import EXPORT_FORMATS, export_roster
|
|
28
|
+
from wh40kdc.imports import (
|
|
29
|
+
REGISTERED_ADAPTERS,
|
|
30
|
+
decode_listforge,
|
|
31
|
+
import_listforge,
|
|
32
|
+
import_newrecruit,
|
|
33
|
+
import_roster,
|
|
34
|
+
try_import_roster,
|
|
35
|
+
)
|
|
36
|
+
from wh40kdc.scoring import score_turn, wtc_result
|
|
37
|
+
from wh40kdc.terrain import BOARD_INCHES, keystone_measurements, resolve_layout
|
|
38
|
+
from wh40kdc.translate import describe_ability, describe_condition, describe_scoring_card
|
|
39
|
+
from wh40kdc.validator import VALIDATOR_TARGETS, SchemaValidator, create_validator
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"BOARD_INCHES",
|
|
43
|
+
"EXPORT_FORMATS",
|
|
44
|
+
"REGISTERED_ADAPTERS",
|
|
45
|
+
"VALIDATOR_TARGETS",
|
|
46
|
+
"Collection",
|
|
47
|
+
"Dataset",
|
|
48
|
+
"SchemaValidator",
|
|
49
|
+
"__version__",
|
|
50
|
+
"attribute_stages",
|
|
51
|
+
"buffs_from_keyword",
|
|
52
|
+
"clamp_weapon_count",
|
|
53
|
+
"create_validator",
|
|
54
|
+
"crunch",
|
|
55
|
+
"decode_listforge",
|
|
56
|
+
"describe_ability",
|
|
57
|
+
"describe_condition",
|
|
58
|
+
"describe_scoring_card",
|
|
59
|
+
"effect_to_buffs",
|
|
60
|
+
"export_roster",
|
|
61
|
+
"import_listforge",
|
|
62
|
+
"import_newrecruit",
|
|
63
|
+
"import_roster",
|
|
64
|
+
"keystone_measurements",
|
|
65
|
+
"maximal_loadout",
|
|
66
|
+
"normalize_name",
|
|
67
|
+
"option_cap",
|
|
68
|
+
"resolve_buffs",
|
|
69
|
+
"resolve_eligible_abilities",
|
|
70
|
+
"resolve_layout",
|
|
71
|
+
"score_turn",
|
|
72
|
+
"try_import_roster",
|
|
73
|
+
"validate_loadout",
|
|
74
|
+
"weapon_bounds",
|
|
75
|
+
"wtc_result",
|
|
76
|
+
]
|