Rewrite Python comments in Google developer documentation style (#12)
* Rewrite comments in Google developer documentation style Rewrite the comments and docstrings across the backend core modules so they read plainly. The previous prose was accurate but dense and figurative, which made it slow to skim. Applies the Google developer documentation style guide: short sentences, active voice, present tense, American spelling, and no metaphors, idioms, or rhetorical asides. Replaces em-dash chains with separate sentences.
This commit is contained in:
@@ -1,24 +1,25 @@
|
||||
"""Phase 14 SP6 — the export bundle carries the tree.
|
||||
"""Phase 14 SP6: the export bundle carries the tree.
|
||||
|
||||
A bundle is the only part of this phase a migration can never reach: the file
|
||||
is already on somebody's disk. So there are two formats, and the two halves of
|
||||
this file watch different things.
|
||||
A bundle is the only part of this phase a migration can never reach: the
|
||||
file already exists on somebody's disk. So there are two formats, and the
|
||||
two halves of this file check different properties.
|
||||
|
||||
**v2 has to be lossless for a story that forked**, which v1 could not be — it
|
||||
had one list and there were two stories, so it interleaved them by `index` and
|
||||
read as a mangled story. Losslessness here means the *tree*: every branch, the
|
||||
fork point it left its parent at, which attempt at each turn is the story, and
|
||||
what each node left behind — because that last one is what a branch switch puts
|
||||
back, and a tree nobody can switch inside is not the tree that was exported.
|
||||
v2 must be lossless for a story that forked. v1 could not be, because it
|
||||
stored one list for two stories, interleaved by `index`, which read back
|
||||
as a mangled story. Losslessness here means the tree: every branch, the
|
||||
fork point it left on its parent, which attempt at each turn is the
|
||||
story, and what each node left behind. That last item is what a branch
|
||||
switch restores, and a tree nobody can switch inside is not the tree
|
||||
that was exported.
|
||||
|
||||
**v1 has to still import**, because a backup that stops importing is not a
|
||||
v1 must still import, because a backup that stops importing is not a
|
||||
backup.
|
||||
|
||||
Underneath both is the rule the module is built on: a bundle carries what was
|
||||
*chosen* and never what is *derived*. The lineage, the head depth, the legacy
|
||||
`index` and the variant ordinals are all rebuilt on the way in, so a
|
||||
hand-edited file cannot disagree with itself — and the tests that matter most
|
||||
here are the ones that hand it a file which does.
|
||||
Both formats follow one rule: a bundle carries what was chosen and never
|
||||
what is derived. The lineage, the head depth, the legacy `index`, and the
|
||||
variant ordinals are all rebuilt on the way in, so a hand-edited file
|
||||
cannot disagree with itself. The tests that matter most here hand the
|
||||
importer a file that does disagree with itself.
|
||||
|
||||
python -m pytest tests/test_bundle_v2.py -v
|
||||
"""
|
||||
@@ -44,8 +45,8 @@ from app.routers import adventures
|
||||
|
||||
SCHEMA = {"player": {"hp": {"min": 0, "max": 100, "initial": 100}}}
|
||||
|
||||
# Ten gold a turn, so the script scoreboard is a number that says how many turns
|
||||
# the story behind it has — which makes an after-snapshot visible from outside.
|
||||
# Ten gold a turn, so the stored gold total tells how many turns the
|
||||
# story behind it played. This makes an after-snapshot visible from outside.
|
||||
GOLD_SCRIPT = """
|
||||
const modifier = (text) => {
|
||||
state.gold = (state.gold || 0) + 10;
|
||||
@@ -161,7 +162,7 @@ def _texts(client, adv_id) -> list[str]:
|
||||
|
||||
|
||||
def _every_branch_story(client, adv_id) -> list[list[str]]:
|
||||
"""What each branch tells, in branch order — the whole tree as text."""
|
||||
"""What each branch tells, in branch order. This is the whole tree as text."""
|
||||
stories = []
|
||||
for branch in _branches(client, adv_id):
|
||||
_switch(client, adv_id, branch["id"])
|
||||
@@ -213,13 +214,14 @@ def _script_state(adv_id) -> dict:
|
||||
|
||||
|
||||
def _forked_story(client) -> int:
|
||||
"""A story that went two ways, and stayed both.
|
||||
"""A story that went two ways and stayed both.
|
||||
|
||||
root: start · do · [attempt two] · do · next turn
|
||||
fork: [ATTEMPT ONE] · do · elsewhere
|
||||
root: start > do > [attempt two] > do > next turn
|
||||
fork: [ATTEMPT ONE] > do > elsewhere
|
||||
|
||||
The *discarded* attempt is the one that gets promoted, because a fork moves
|
||||
the take you are leaving for and leaves the line you came from untouched.
|
||||
The discarded attempt is the one this function promotes, because a
|
||||
fork moves the attempt being left for and leaves the line it came
|
||||
from untouched.
|
||||
|
||||
Returns the adventure id, with the head on the fork.
|
||||
"""
|
||||
@@ -237,11 +239,11 @@ def _forked_story(client) -> int:
|
||||
# ------------------------------------------------------- the round trip (v2)
|
||||
|
||||
def test_a_forked_story_survives_the_round_trip(client):
|
||||
"""The headline: both futures come back, and both are still readable.
|
||||
"""The main claim: both futures come back, and both are still readable.
|
||||
|
||||
This is the thing v1 could not do. The check is not "the same rows" — the
|
||||
ids are new — but "the same stories", read the way a player reads them: by
|
||||
switching to a branch and looking at what it says.
|
||||
This is the thing v1 could not do. The check is not "the same rows",
|
||||
since the ids are new, but "the same stories", read the way a player
|
||||
reads them: by switching to a branch and looking at what it says.
|
||||
"""
|
||||
original = _forked_story(client)
|
||||
before = _every_branch_story(client, original)
|
||||
@@ -254,11 +256,11 @@ def test_a_forked_story_survives_the_round_trip(client):
|
||||
|
||||
|
||||
def test_the_fork_point_comes_back_where_it_was_put(client):
|
||||
"""`fork_depth` is stored, never inferred — including through a file.
|
||||
"""`fork_depth` is stored, never inferred, including through a file.
|
||||
|
||||
Inferring it from where two branches' nodes first differ would be a guess
|
||||
about how the story was played, and a wrong one the moment an attempt
|
||||
happens to repeat its parent's text.
|
||||
Inferring it from where two branches' nodes first differ would guess
|
||||
at how the story was played, and that guess fails as soon as an
|
||||
attempt happens to repeat its parent's text.
|
||||
"""
|
||||
original = _forked_story(client)
|
||||
before = [(b["parent_branch_id"] is None, b["fork_depth"]) for b in _branches(client, original)]
|
||||
@@ -275,22 +277,23 @@ def test_the_head_comes_back_on_the_branch_it_was_left_on(client):
|
||||
|
||||
copy = _imported(client, _export(client, original))
|
||||
assert [b["is_head"] for b in _branches(client, copy)] == head_before
|
||||
# And the tip it sits at is derived from the nodes that arrived, not read
|
||||
# out of the file — the bundle never says how deep a branch goes.
|
||||
# The tip it sits at is derived from the nodes that arrived, not
|
||||
# read from the file. The bundle never states how deep a branch goes.
|
||||
assert _texts(client, copy) == _texts(client, original)
|
||||
|
||||
|
||||
def test_a_switch_in_the_copy_restores_what_that_branch_left_behind(client):
|
||||
"""The after-snapshots are why the bundle carries them.
|
||||
"""This test justifies why the bundle carries after-snapshots.
|
||||
|
||||
The gold script adds ten a turn, so the scoreboard is a count of the story
|
||||
behind it. A bundle that carried the actions but not the outcomes would
|
||||
import a tree that reads correctly and switches wrong.
|
||||
The gold script adds ten a turn, so the stored gold total counts the
|
||||
turns behind it. A bundle that carried the actions but not the
|
||||
outcomes would import a tree that reads correctly but switches to the
|
||||
wrong state.
|
||||
"""
|
||||
original = _forked_story(client)
|
||||
# One more turn on the fork, so the two tips are genuinely different
|
||||
# numbers: played turn for turn, the branches earn the same gold and a
|
||||
# switch that restored nothing at all would still look right.
|
||||
# Play one more turn on the fork, so the two tips end up at
|
||||
# genuinely different totals. Turn for turn, both branches earn the
|
||||
# same gold, so a switch that restored nothing would still look right.
|
||||
ScriptedProvider.replies = ["Further still."]
|
||||
_play(client, original, "press on")
|
||||
|
||||
@@ -340,12 +343,12 @@ def test_a_memory_comes_back_on_the_node_it_hangs_off(client):
|
||||
# --------------------------------------------------- what is not in the file
|
||||
|
||||
def test_the_lineage_is_rebuilt_rather_than_carried(client):
|
||||
"""A cache of `parent` + `fork_depth` is not a second thing to ship.
|
||||
"""A cache of `parent` plus `fork_depth` is not a second thing to ship.
|
||||
|
||||
The file says where each branch forked; the ancestry that makes the fork
|
||||
readable is computed from that on the way in, capped at the fork exactly as
|
||||
`tree.fork` caps it. Shipping the cache too would put two sources of truth
|
||||
for one fact in a file anybody can hand-edit.
|
||||
The file states where each branch forked. The ancestry that makes the
|
||||
fork readable is computed from that value on the way in, capped at
|
||||
the fork exactly as `tree.fork` caps it. Shipping the cache too would
|
||||
put two sources of truth for one fact in a file anybody can hand-edit.
|
||||
"""
|
||||
original = _forked_story(client)
|
||||
exported = _export(client, original)
|
||||
@@ -354,18 +357,19 @@ def test_the_lineage_is_rebuilt_rather_than_carried(client):
|
||||
root, forked = _branch_rows(_imported(client, exported))
|
||||
assert root.lineage == [[root.id, None]]
|
||||
assert forked.lineage == [[forked.id, None], [root.id, forked.fork_depth]]
|
||||
# Which is the arithmetic the reader depends on: the parent is capped one
|
||||
# depth short of the attempt that was promoted, so the fork cannot see it.
|
||||
# This is the arithmetic the reader depends on. The parent is capped
|
||||
# one depth short of the attempt that was promoted, so the fork
|
||||
# cannot see it.
|
||||
assert lineage.entries_of(forked) == [(forked.id, None), (root.id, forked.fork_depth)]
|
||||
|
||||
|
||||
def test_the_legacy_index_is_reissued_so_two_branches_never_share_one(client):
|
||||
"""`index` is a fact about the adventure, and `depth` is one about a path.
|
||||
"""`index` describes the adventure. `depth` describes a path.
|
||||
|
||||
Two branches have a node at depth 3, so depth cannot be the number
|
||||
`max_action_index` hands out next. The import allocates one per turn
|
||||
instead: siblings share it, the way SP4 leaves them, and no two coordinates
|
||||
do.
|
||||
Two branches can each have a node at depth 3, so depth cannot be the
|
||||
number `max_action_index` hands out next. The import allocates one
|
||||
index per turn instead. Siblings share an index, the way SP4 leaves
|
||||
them, and no two coordinates share one.
|
||||
"""
|
||||
copy = _imported(client, _export(client, _forked_story(client)))
|
||||
rows = _rows(copy)
|
||||
@@ -376,8 +380,9 @@ def test_the_legacy_index_is_reissued_so_two_branches_never_share_one(client):
|
||||
"one index per turn, whatever branch it is on"
|
||||
assert len(by_index) == len({(r.branch_id, r.depth) for r in rows})
|
||||
|
||||
# The case that makes the rule necessary: the fork and the line it left
|
||||
# both hold a turn at depth 2, and they are not the same turn.
|
||||
# This is the case that makes the rule necessary: the fork and the
|
||||
# branch it left both hold a turn at depth 2, and they are not the
|
||||
# same turn.
|
||||
at_depth_2 = [r for r in rows if r.depth == 2]
|
||||
assert len({r.branch_id for r in at_depth_2}) == 2
|
||||
assert len({r.index for r in at_depth_2}) == 2, "same depth, different turns"
|
||||
@@ -386,8 +391,9 @@ def test_the_legacy_index_is_reissued_so_two_branches_never_share_one(client):
|
||||
# ------------------------------------------------------- a file that is wrong
|
||||
|
||||
def test_a_node_naming_a_branch_the_file_does_not_list_is_refused(client):
|
||||
"""Refused, not half-applied. A tree missing a branch is a story that
|
||||
silently stops, which is the failure this whole phase exists to end."""
|
||||
"""The import must refuse this file rather than half-apply it. A tree
|
||||
missing a branch is a story that silently stops, which is the failure
|
||||
this whole phase exists to prevent."""
|
||||
payload = _export(client, _forked_story(client))
|
||||
payload["branches"] = payload["branches"][:1]
|
||||
before = _adventure_count()
|
||||
@@ -399,8 +405,9 @@ def test_a_node_naming_a_branch_the_file_does_not_list_is_refused(client):
|
||||
|
||||
|
||||
def test_a_branch_forking_from_one_listed_after_it_is_refused(client):
|
||||
"""The ordering rule buys acyclicity for the price of a comparison — and a
|
||||
cycle would be an import that never returns rather than one that fails."""
|
||||
"""The ordering rule guarantees no cycles at the cost of one
|
||||
comparison. Without it, a cycle would produce an import that never
|
||||
returns instead of one that fails cleanly."""
|
||||
payload = _export(client, _forked_story(client))
|
||||
payload["branches"] = [{"parent": 1, "forkDepth": 0}, {"parent": None, "forkDepth": None}]
|
||||
before = _adventure_count()
|
||||
@@ -439,8 +446,9 @@ def test_more_branches_than_the_cap_is_refused(client, monkeypatch):
|
||||
def test_a_turn_the_file_gives_no_live_attempt_still_tells_one(client):
|
||||
"""A coordinate with nothing live is a turn no read can see.
|
||||
|
||||
The file is allowed to be wrong about this — it is a text file — so the
|
||||
import picks the first attempt rather than importing a story with a hole.
|
||||
The file is allowed to be wrong about this, since it is a text file
|
||||
someone can edit. The import picks the first attempt instead of
|
||||
importing a story with a gap.
|
||||
"""
|
||||
payload = _export(client, _forked_story(client))
|
||||
for node in payload["actions"]:
|
||||
@@ -456,7 +464,8 @@ def test_a_turn_the_file_gives_no_live_attempt_still_tells_one(client):
|
||||
# ------------------------------------------------------------- the v1 reader
|
||||
|
||||
def test_a_v1_bundle_still_imports(client):
|
||||
"""The reader stays after the writer goes: those files are already saved."""
|
||||
"""The v1 reader must remain even after the v1 writer is gone, because
|
||||
those files already exist and are saved."""
|
||||
payload = {
|
||||
"format": bundle.LEGACY_FORMAT,
|
||||
"title": "Old backup",
|
||||
@@ -474,8 +483,8 @@ def test_a_v1_bundle_still_imports(client):
|
||||
copy = _imported(client, payload)
|
||||
|
||||
assert _texts(client, copy) == [OPENING, "> You go north.", "Two."]
|
||||
# One branch, and the `variants` array split back into the sibling group it
|
||||
# always described.
|
||||
# The import produces one branch, and the `variants` array splits
|
||||
# back into the sibling group it always described.
|
||||
assert len(_branches(client, copy)) == 1
|
||||
ai = [r for r in _rows(copy) if r.type == "ai"]
|
||||
assert [(r.text, r.live) for r in ai] == [("One.", False), ("Two.", True)]
|
||||
@@ -483,8 +492,9 @@ def test_a_v1_bundle_still_imports(client):
|
||||
|
||||
|
||||
def test_a_v1_bundle_with_a_cursor_lands_it_on_a_node(client):
|
||||
"""v1 counts covered actions; the tree anchors them. The translation needs
|
||||
the nodes to exist, so it happens after they are written."""
|
||||
"""v1 counts covered actions. The tree anchors them to a node instead.
|
||||
The translation needs the nodes to exist first, so it runs after they
|
||||
are written."""
|
||||
payload = {
|
||||
"format": bundle.LEGACY_FORMAT, "title": "Old backup",
|
||||
"memoryCursor": 2, "summaryCursor": 2,
|
||||
@@ -507,7 +517,8 @@ def test_a_v1_bundle_with_a_cursor_lands_it_on_a_node(client):
|
||||
|
||||
|
||||
def test_a_v2_bundle_brings_its_anchors_back(client):
|
||||
"""The other direction: v2 carries the anchor and the count is read off it."""
|
||||
"""The other direction: v2 carries the anchor directly, and the legacy
|
||||
count is derived from it."""
|
||||
original = _forked_story(client)
|
||||
tip = [a for a in _rows(original) if a.live][-1]
|
||||
db = SessionLocal()
|
||||
@@ -535,13 +546,14 @@ def test_a_v2_bundle_brings_its_anchors_back(client):
|
||||
|
||||
|
||||
def test_a_v1_memory_that_summarises_nothing_lands_on_the_root(client):
|
||||
"""The import has to answer the question migration 62 answered.
|
||||
"""The import must answer the question migration 62 already answered.
|
||||
|
||||
A v1 file has no depths, and a memory the player typed has no `sourceEnd`
|
||||
to derive one from — so it used to come back with a NULL depth, which is the
|
||||
exact state SP7 removed from the schema. `Path._entry_clause` compares
|
||||
`depth <= max_depth` and a NULL fails it, so the memory would read fine
|
||||
until the imported adventure was forked and then vanish from the new branch.
|
||||
A v1 file has no depths, and a memory the player typed has no
|
||||
`sourceEnd` to derive one from. It used to come back with a NULL
|
||||
depth, the exact state SP7 removed from the schema.
|
||||
`Path._entry_clause` compares `depth <= max_depth`, and a NULL fails
|
||||
that comparison. The memory would read fine until the imported
|
||||
adventure forked, then vanish from the new branch.
|
||||
"""
|
||||
payload = {
|
||||
"format": bundle.LEGACY_FORMAT, "title": "Old backup",
|
||||
@@ -572,12 +584,13 @@ def test_a_v1_memory_that_summarises_nothing_lands_on_the_root(client):
|
||||
|
||||
|
||||
def test_the_action_cap_counts_the_rows_a_v1_file_expands_into(client, monkeypatch):
|
||||
"""The cap has to count what gets written, not what the file lists.
|
||||
"""The cap must count what gets written, not what the file lists.
|
||||
|
||||
A v1 turn carries its retries in a `variants` array, and SP4 made every
|
||||
attempt a row — so one entry can become ten. Counting entries lets a file
|
||||
inside the cap write a multiple of it, and the body limit is no help: the
|
||||
text is tiny, it is the row count that is the cost.
|
||||
A v1 turn carries its retries in a `variants` array, and SP4 made
|
||||
every attempt a row, so one entry can expand into ten. Counting
|
||||
entries instead of rows would let a file inside the cap write a
|
||||
multiple of it. The body-size limit does not help here: the text is
|
||||
tiny, and the row count is the actual cost.
|
||||
"""
|
||||
monkeypatch.setattr(auth, "MULTI_USER", True)
|
||||
monkeypatch.setattr(limits, "MAX_ACTIONS_PER_ADVENTURE", 6)
|
||||
|
||||
Reference in New Issue
Block a user