Skip to content

dndwright 0.28.0

Choose a tag to compare

@sligara7 sligara7 released this 16 Aug 00:48
· 3 commits to main since this release

[0.28.0] — 2026-08-15

Added

  • ClassMechanics and weapon_attack are now part of the public API (dndwright.__all__,
    72 → 74 names). Both were already reachable — ClassMechanics only via the private
    dndwright.rules.components, and weapon_attack via the published dndwright.combat
    sub-API — and both are now importable directly from dndwright. Additive: no existing
    import path was removed, so nothing breaks.
    • The API contract test pins all three of its surfaces accordingly: the exported name set,
      weapon_attack's signature (every public free function must be pinned), and
      ClassMechanics's field list (every public pydantic model must be pinned).
    • A minor version bump is owed at the next release under SemVer, since this widens the
      public surface. Deliberately not bumped here: bumping without releasing is what left
      the sibling package mapwright with a 0.28.0 that looks shipped and never was.

Changed

  • Examples and the module docstring now teach published import paths only.
    examples/multiclass.py and the __init__ docstring imported ClassMechanics from
    dndwright.rules.components; examples/stat_diff.py imported compute_stat_diff from
    dndwright.rules.character_evaluator even though that name was already public. Since
    tests/test_examples.py executes every example, the package was effectively guaranteeing
    that private import paths keep working.

Added (2)

  • dndwright.combat now re-exports both submodules' full declared surfaces — the 20 names
    in conditions.__all__ and the 7 in initiative.__all__. combat.__all__ goes 22 → 49, so
    from dndwright.combat import attempt_save, order_initiative works instead of needing a
    second import from the submodule. Additive: dndwright.combat.conditions and
    .initiative remain importable exactly as before.
    • Both submodule surfaces were ALREADY contract-pinned (EXPECTED_CONDITIONS_PUBLIC,
      EXPECTED_INITIATIVE_PUBLIC); this publishes them one level up and pins the wider
      EXPECTED_COMBAT_PUBLIC to match.
    • The full submodule surfaces were taken rather than only the nine names the examples
      happened to use, because four of those nine return types (SaveResult,
      ConditionChange, TurnAdvance, InitiativeRoll) would otherwise have stayed
      unpublished — leaving callers able to invoke a function but unable to name its result.
  • examples/conditions.py and examples/initiative.py now import from dndwright.combat.
    No example imports a private path any more.

Known

  • examples/conditions.py and examples/initiative.py still import names that are on no
    published surface
    — attempt_save, tick_conditions, ActiveCondition, ROUND_END,
    SAVE_ENDS, order_initiative, advance_turn, roll_initiative, InitiativeEntry.
    dndwright.combat.__all__ exports the conditions and initiative submodules but none
    of the functions within them. Widening the surface further is a separate decision and has
    not been taken.

Changed

  • Adopted the wider lint families (I, SIM, UP, RUF) on top of the existing
    explicit rule set, in a deliberate pass rather than as a dependency side effect:
    import sorting, f-string conversions, __all__ sorting, and a deprecated import.
    PL is not adopted (262 findings, magic values that are the ruleset itself);
    RUF001-003, RUF005 and RUF012 are declined with reasons stated in
    pyproject.toml.

Added

  • RUF046 is enabled here, and the reason is now checked. RUF046 rewrites
    int(round(x)) into round(x), which is only safe while no value is a numpy
    scalar — round(np.float64) returns a numpy float, not an int. dndwright has no
    numpy by rule, so the rewrite is safe; the sibling package mapwright is numpy-based
    and declines the same rule. Those two decisions are coupled and nothing said so, so
    tests/test_packaging_contract.py now fails if numpy is ever added while RUF046
    is still enforced, naming the reason.

Notes

  • No behavioural change and no version bump. The public API is the same set of 72
    names (__all__ declaration order moved when it was sorted; the set did not), and
    seeded dice rolls and a computed character sheet hash identically against a detached
    worktree of the previous commit.