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.
Files changed (87) hide show
  1. wh40kdc-0.5.0/.gitignore +45 -0
  2. wh40kdc-0.5.0/PKG-INFO +94 -0
  3. wh40kdc-0.5.0/README.md +66 -0
  4. wh40kdc-0.5.0/codegen/gen_typeddicts.py +63 -0
  5. wh40kdc-0.5.0/codegen/sync_bundle.py +56 -0
  6. wh40kdc-0.5.0/codegen/sync_spec.py +34 -0
  7. wh40kdc-0.5.0/pyproject.toml +86 -0
  8. wh40kdc-0.5.0/src/wh40kdc/__init__.py +76 -0
  9. wh40kdc-0.5.0/src/wh40kdc/_bundle.json +1 -0
  10. wh40kdc-0.5.0/src/wh40kdc/_spec.py +6 -0
  11. wh40kdc-0.5.0/src/wh40kdc/_types.py +835 -0
  12. wh40kdc-0.5.0/src/wh40kdc/_version.py +7 -0
  13. wh40kdc-0.5.0/src/wh40kdc/abilities_resolver.py +174 -0
  14. wh40kdc-0.5.0/src/wh40kdc/cruncher/__init__.py +31 -0
  15. wh40kdc-0.5.0/src/wh40kdc/cruncher/attribution.py +112 -0
  16. wh40kdc-0.5.0/src/wh40kdc/cruncher/buffs.py +207 -0
  17. wh40kdc-0.5.0/src/wh40kdc/cruncher/engine.py +520 -0
  18. wh40kdc-0.5.0/src/wh40kdc/cruncher/from_dsl.py +1157 -0
  19. wh40kdc-0.5.0/src/wh40kdc/cruncher/from_keyword.py +198 -0
  20. wh40kdc-0.5.0/src/wh40kdc/data/__init__.py +40 -0
  21. wh40kdc-0.5.0/src/wh40kdc/data/base.py +36 -0
  22. wh40kdc-0.5.0/src/wh40kdc/data/bundle.py +59 -0
  23. wh40kdc-0.5.0/src/wh40kdc/data/collection.py +164 -0
  24. wh40kdc-0.5.0/src/wh40kdc/data/dataset.py +371 -0
  25. wh40kdc-0.5.0/src/wh40kdc/data/entities.py +288 -0
  26. wh40kdc-0.5.0/src/wh40kdc/data/loadout.py +171 -0
  27. wh40kdc-0.5.0/src/wh40kdc/data/normalize.py +59 -0
  28. wh40kdc-0.5.0/src/wh40kdc/export/__init__.py +68 -0
  29. wh40kdc-0.5.0/src/wh40kdc/export/helpers.py +79 -0
  30. wh40kdc-0.5.0/src/wh40kdc/export/newrecruit_json.py +185 -0
  31. wh40kdc-0.5.0/src/wh40kdc/export/newrecruit_simple.py +113 -0
  32. wh40kdc-0.5.0/src/wh40kdc/export/newrecruit_wtc.py +154 -0
  33. wh40kdc-0.5.0/src/wh40kdc/export/roster_json.py +14 -0
  34. wh40kdc-0.5.0/src/wh40kdc/export/rosterizer.py +156 -0
  35. wh40kdc-0.5.0/src/wh40kdc/imports/__init__.py +215 -0
  36. wh40kdc-0.5.0/src/wh40kdc/imports/adapter.py +39 -0
  37. wh40kdc-0.5.0/src/wh40kdc/imports/battlescribe.py +244 -0
  38. wh40kdc-0.5.0/src/wh40kdc/imports/decode.py +76 -0
  39. wh40kdc-0.5.0/src/wh40kdc/imports/gw.py +278 -0
  40. wh40kdc-0.5.0/src/wh40kdc/imports/listforge.py +78 -0
  41. wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_json.py +100 -0
  42. wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_simple.py +236 -0
  43. wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_text.py +126 -0
  44. wh40kdc-0.5.0/src/wh40kdc/imports/newrecruit_wtc.py +413 -0
  45. wh40kdc-0.5.0/src/wh40kdc/imports/resolve.py +321 -0
  46. wh40kdc-0.5.0/src/wh40kdc/imports/rosterizer.py +360 -0
  47. wh40kdc-0.5.0/src/wh40kdc/py.typed +0 -0
  48. wh40kdc-0.5.0/src/wh40kdc/runner.py +698 -0
  49. wh40kdc-0.5.0/src/wh40kdc/schemas/$defs/common.schema.json +143 -0
  50. wh40kdc-0.5.0/src/wh40kdc/schemas/$defs/game-version-ref.schema.json +11 -0
  51. wh40kdc-0.5.0/src/wh40kdc/schemas/core/deployment-pattern.schema.json +102 -0
  52. wh40kdc-0.5.0/src/wh40kdc/schemas/core/detachment.schema.json +56 -0
  53. wh40kdc-0.5.0/src/wh40kdc/schemas/core/enhancement.schema.json +46 -0
  54. wh40kdc-0.5.0/src/wh40kdc/schemas/core/faction.schema.json +29 -0
  55. wh40kdc-0.5.0/src/wh40kdc/schemas/core/force-disposition.schema.json +22 -0
  56. wh40kdc-0.5.0/src/wh40kdc/schemas/core/game-version.schema.json +20 -0
  57. wh40kdc-0.5.0/src/wh40kdc/schemas/core/leader-attachment.schema.json +18 -0
  58. wh40kdc-0.5.0/src/wh40kdc/schemas/core/mission-matchup.schema.json +25 -0
  59. wh40kdc-0.5.0/src/wh40kdc/schemas/core/mission.schema.json +42 -0
  60. wh40kdc-0.5.0/src/wh40kdc/schemas/core/roster.schema.json +218 -0
  61. wh40kdc-0.5.0/src/wh40kdc/schemas/core/secondary-card.schema.json +256 -0
  62. wh40kdc-0.5.0/src/wh40kdc/schemas/core/stratagem.schema.json +58 -0
  63. wh40kdc-0.5.0/src/wh40kdc/schemas/core/terrain-layout.schema.json +178 -0
  64. wh40kdc-0.5.0/src/wh40kdc/schemas/core/terrain-template.schema.json +105 -0
  65. wh40kdc-0.5.0/src/wh40kdc/schemas/core/unit-composition.schema.json +42 -0
  66. wh40kdc-0.5.0/src/wh40kdc/schemas/core/unit.schema.json +117 -0
  67. wh40kdc-0.5.0/src/wh40kdc/schemas/core/wargear-option.schema.json +73 -0
  68. wh40kdc-0.5.0/src/wh40kdc/schemas/core/wargear.schema.json +24 -0
  69. wh40kdc-0.5.0/src/wh40kdc/schemas/core/weapon-keyword.schema.json +31 -0
  70. wh40kdc-0.5.0/src/wh40kdc/schemas/core/weapon.schema.json +77 -0
  71. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/ability.schema.json +60 -0
  72. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/condition.schema.json +52 -0
  73. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/effect.schema.json +168 -0
  74. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/ability-dsl/scope.schema.json +12 -0
  75. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/interaction-flag.schema.json +17 -0
  76. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/phase-mapping.schema.json +14 -0
  77. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/resource-pool.schema.json +36 -0
  78. wh40kdc-0.5.0/src/wh40kdc/schemas/enrichment/timing-flag.schema.json +28 -0
  79. wh40kdc-0.5.0/src/wh40kdc/scoring/__init__.py +237 -0
  80. wh40kdc-0.5.0/src/wh40kdc/terrain/__init__.py +25 -0
  81. wh40kdc-0.5.0/src/wh40kdc/terrain/keystones.py +114 -0
  82. wh40kdc-0.5.0/src/wh40kdc/terrain/resolve.py +263 -0
  83. wh40kdc-0.5.0/src/wh40kdc/translate/__init__.py +25 -0
  84. wh40kdc-0.5.0/src/wh40kdc/translate/condition.py +263 -0
  85. wh40kdc-0.5.0/src/wh40kdc/translate/effect.py +299 -0
  86. wh40kdc-0.5.0/src/wh40kdc/translate/scoring.py +83 -0
  87. wh40kdc-0.5.0/src/wh40kdc/validator.py +174 -0
@@ -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.
@@ -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
+ ]