FreeCampus Python

Unit Challenge: Publish the Puzzle Garden Field Guide

Repair and publish the documentation for a playful clue-path package so a new player can reveal, customize, understand, and verify a garden route.
python-foundations documentation-publishing unit-challenge puzzle-garden
Open in Colab
  • Level: Python Foundations · Unit 15 challenge
  • Estimated time: 60–120 minutes
  • Outcome assessed: Design, write, test, build, and publish documentation that guides real readers from first contact to successful use, explains a stable public Python API, and remains trustworthy as the code changes.
  • Evidence: Reader-focused source, executable examples, resolved links, clean rendered pages, a publication gate, and one debugging record

1. Reopen the Puzzle Garden

The Puzzle Garden’s code still works, but its field guide does not. The quick start calls a retired function, the expected path is stale, the public docstring hides an invalid-clue failure, two pages are missing from navigation, and the workflow publishes source instead of the rendered site.

Your goal is to publish a small field guide that lets another player:

  1. understand what the package reveals;
  2. prepare the lesson project and reveal one exact path;
  3. replace the clue legend for a new theme;
  4. understand why the same seed repeats the same path;
  5. look up the public call and anticipated failure; and
  6. trust that the examples, links, pages, and deployment artifact were checked.

The satisfying result is visible. The quick start should reveal:

('Moss arch', 'Blue gate', 'Lantern lilies')

Each reader path must reach a checked source page, and every page must reach the same rendered field guide artifact.

flowchart LR
  A[README] --> B[Reveal a route]
  A --> C[Replace the legend]
  A --> D[Why seeds repeat]
  A --> E[Public API]
  B --> F[Quarto field guide]
  C --> F
  D --> F
  E --> F
  F --> G[Verified deployment]

NoteUse the scaffold to avoid blank-page work

The package behavior, file names, public signature, initial page headings, and progressive checks are supplied. Your central work is deciding what each reader needs, repairing public promises, and connecting the checked artifact to publication.

2. Inspect the supplied project

Begin with this tree:

puzzle-garden/
├── README.md
├── pyproject.toml
├── src/puzzle_garden/
│   ├── __init__.py
│   └── trail.py
├── tests/
│   ├── test_trail.py
│   └── test_documentation.py
├── docs/
│   ├── _quarto.yml
│   ├── index.qmd
│   ├── first-route.qmd
│   ├── customize-legend.qmd
│   ├── why-seeds-repeat.qmd
│   └── api.qmd
└── .github/workflows/docs.yml

The public package implementation already passes its ordinary unit tests. Do not redesign its algorithm. Documentation changes may improve the public module/function docstrings, examples, README, QMD pages, checks, and workflow.

The source uses three clue symbols:

Symbol Default location
L Lantern lilies
M Moss arch
B Blue gate

A seed rotates the explicit clue sequence. It does not use global randomness or remember an earlier call.

Acceptance example

from puzzle_garden import reveal_path

route = reveal_path("L M B", seed=1)
assert route == ("Moss arch", "Blue gate", "Lantern lilies")
print(route)

Changed legend:

from puzzle_garden import reveal_path

moon_legend = {
    "L": "Lunar pond",
    "M": "Meteor arch",
    "B": "Blue moon gate",
}
assert reveal_path("L M B", legend=moon_legend) == (
    "Lunar pond",
    "Meteor arch",
    "Blue moon gate",
)

Important failure:

from puzzle_garden import reveal_path

try:
    reveal_path("L X")
except ValueError as error:
    assert str(error) == "unknown clue: X"
else:
    raise AssertionError("unknown clue was accepted")

3. Start from the contract

Create src/puzzle_garden/trail.py from this supplied implementation. Complete its docstring; do not change the observable algorithm to make stale prose pass.

from collections.abc import Mapping

DEFAULT_LEGEND = {
    "L": "Lantern lilies",
    "M": "Moss arch",
    "B": "Blue gate",
}


def reveal_path(
    clues: str,
    *,
    seed: int = 0,
    legend: Mapping[str, str] = DEFAULT_LEGEND,
) -> tuple[str, ...]:
    """Return decoded clue names rotated by a repeatable seed.

    TODO: Document parameter meaning, returned order, empty/unknown clue
    failures, and two deterministic examples.
    """
    symbols = clues.split()
    if not symbols:
        raise ValueError("clues cannot be empty")
    for symbol in symbols:
        if symbol not in legend:
            raise ValueError(f"unknown clue: {symbol}")
    offset = seed % len(symbols)
    ordered = symbols[offset:] + symbols[:offset]
    return tuple(legend[symbol] for symbol in ordered)

Export the public names in src/puzzle_garden/__init__.py:

from .trail import DEFAULT_LEGEND, reveal_path

__all__ = ["DEFAULT_LEGEND", "reveal_path"]

The starter README currently contains these defects:

# Garden Tool

Install it normally. Run `make_path("L M B")` and expect
`('Lantern lilies', 'Moss arch', 'Blue gate')`.

[Click here](docs/guide.qmd "retired guide") for more.

The starter Quarto sidebar lists only index.qmd, first-route.qmd, and api.qmd. The starter workflow ends with publish_dir: docs.

4. Build the field guide in small stages

Stage A — Repair the project entrance

Write a README with:

  • a specific purpose and Python 3.10+ context;
  • a truthful preparation/install path for the supplied source project;
  • a complete public import and seed-1 quick start;
  • the exact Moss arch → Blue gate → Lantern lilies result;
  • limitations and support sections;
  • descriptive relative links to the four deeper reader pages.

Stage B — Give every page one job

Finish these pages:

  • first-route.qmd: a guided path using supplied clues and seed;
  • customize-legend.qmd: a focused task for replacing every used symbol;
  • why-seeds-repeat.qmd: explanation of rotation over explicit input and no hidden state;
  • api.qmd: exact signature, parameters, return, and ValueError cases.

Use informative titles, visible prerequisites, expected output, meaningful link text, and recovery close to the failure it repairs.

Stage C — Make examples executable

Complete the public docstring with one ordinary example and the unknown-clue failure. Ensure doctest reports 2 attempted, 0 failed. Keep the custom-legend boundary in pytest where a structured tuple assertion is clearer.

Stage D — Build and connect the site

Repair _quarto.yml so all five QMD pages appear in reader order. Render from a clean output directory and verify all five HTML files. Repair every local source link before publication.

Stage E — Publish only checked output

The workflow should run ordinary tests and doctests, render the complete site, verify the HTML inventory, and publish docs/_site only after success on the accepted branch. Never commit a personal token.

5. Run progressive assertions

Run these stages in order. Keep the expected values unchanged unless you can explain an approved contract change.

Check 1 — Public behavior

from puzzle_garden import reveal_path

assert reveal_path("L M B", seed=1) == (
    "Moss arch",
    "Blue gate",
    "Lantern lilies",
)
assert reveal_path("L M", seed=0) == (
    "Lantern lilies",
    "Moss arch",
)

try:
    reveal_path("L X")
except ValueError as error:
    assert str(error) == "unknown clue: X"
else:
    raise AssertionError("unknown clue was accepted")

Check 2 — Discovered examples

import doctest
import puzzle_garden.trail

results = doctest.testmod(puzzle_garden.trail, verbose=False)
assert results.attempted == 2
assert results.failed == 0

Check 3 — Real README and source pages

from pathlib import Path

root = Path(".").resolve()
readme = (root / "README.md").read_text(encoding="utf-8")

for heading in (
    "## Requirements",
    "## Installation",
    "## Quick start",
    "## Limitations",
    "## Support",
    "## Learn more",
):
    assert heading in readme, f"README missing {heading}"

assert "from puzzle_garden import reveal_path" in readme
assert "seed=1" in readme
assert "('Moss arch', 'Blue gate', 'Lantern lilies')" in readme

source_pages = {
    "index.qmd",
    "first-route.qmd",
    "customize-legend.qmd",
    "why-seeds-repeat.qmd",
    "api.qmd",
}
assert source_pages == {path.name for path in (root / "docs").glob("*.qmd")}

Check 4 — Local README targets

from pathlib import Path

root = Path(".").resolve()
targets = {
    root / "docs/first-route.qmd",
    root / "docs/customize-legend.qmd",
    root / "docs/why-seeds-repeat.qmd",
    root / "docs/api.qmd",
}
assert all(path.is_file() for path in targets), sorted(targets)

Then run the broader source-link checker from Lesson 5 so links inside QMD pages are also resolved relative to their source.

Check 5 — Clean rendered artifact

quarto render docs
from pathlib import Path

site = Path("docs/_site")
expected_html = {
    "index.html",
    "first-route.html",
    "customize-legend.html",
    "why-seeds-repeat.html",
    "api.html",
}
observed_html = {path.name for path in site.glob("*.html")}
assert expected_html <= observed_html, sorted(expected_html - observed_html)

Check 6 — Local/CI contract

pytest --doctest-modules src tests -q
quarto render docs
python tests/check_rendered_docs.py

Confirm the workflow contains the same responsibilities in this order and that its deployment input is docs/_site.

WarningA list of headings is not a README check

Do not replace file inspection with README_HEADINGS = [...] and then assert the list against itself. The checks must read the artifact the learner will publish.

6. Use the hint ladder only when needed

Hint 1

Anchor the README on the seed-1 route. State the project-root context, import reveal_path, show the exact tuple, and link each later reader question to its own page.

Hint 2

For the docstring, document clues, keyword-only seed, and legend; explain rotation and the returned tuple; include the ordinary call plus a traceback ending in ValueError: unknown clue: X. Verify attempted count as well as failures.

Hint 3

List all five QMD paths in website.sidebar.contents. Render from docs/ or target the docs project from root. Publish docs/_site only after pytest, doctest, render, and page-inventory checks pass.

7. Keep debugging evidence

Preserve one real failing check before repairing it. Use the earliest useful evidence rather than the final symptom.

Failure Exact evidence One hypothesis Controlled change Verified rerun
What command/check failed? Expected, observed, file/line/path Which single cause fits? What one source changed? Which unchanged checks now pass?

Good candidates include:

  • zero doctest attempts because the transcript prompt is malformed;
  • expected default-order output while the quick start passes seed=1;
  • customize-legend.qmd missing from the sidebar;
  • a README link to retired guide.qmd;
  • a clean render missing api.html; or
  • workflow publish_dir pointing at source docs instead of docs/_site.

Do not use “changed everything and it worked” as the record. A reader should be able to connect the evidence to your one repair.

8. Compare with a complete solution path

Reveal after your checks pass or all three hints are exhausted

A complete public docstring can be:

from collections.abc import Mapping

DEFAULT_LEGEND = {
    "L": "Lantern lilies",
    "M": "Moss arch",
    "B": "Blue gate",
}


def reveal_path(
    clues: str,
    *,
    seed: int = 0,
    legend: Mapping[str, str] = DEFAULT_LEGEND,
) -> tuple[str, ...]:
    """Return decoded clue names rotated by a repeatable seed.

    Args:
        clues: Space-separated clue symbols.
        seed: Integer offset selecting the first clue in the returned route.
        legend: Mapping from clue symbols to visitor-facing location names.

    Returns:
        Decoded location names rotated by `seed`.

    Raises:
        ValueError: If `clues` is empty or contains an unknown symbol.

    Examples:
        >>> reveal_path("L M B", seed=1)
        ('Moss arch', 'Blue gate', 'Lantern lilies')
        >>> reveal_path("L X")
        Traceback (most recent call last):
        ...
        ValueError: unknown clue: X
    """
    symbols = clues.split()
    if not symbols:
        raise ValueError("clues cannot be empty")
    for symbol in symbols:
        if symbol not in legend:
            raise ValueError(f"unknown clue: {symbol}")
    offset = seed % len(symbols)
    ordered = symbols[offset:] + symbols[:offset]
    return tuple(legend[symbol] for symbol in ordered)

A complete README entry path can be:

# Puzzle Garden Field Guide

Reveal repeatable routes through a tiny symbolic garden puzzle.

## Requirements

Use Python 3.10 or newer and run commands from the project root.

## Installation

For this supplied source project, install in editable mode:

```text
python -m pip install -e .
```

## Quick start

```python
from puzzle_garden import reveal_path

print(reveal_path("L M B", seed=1))
```

Expected result: `('Moss arch', 'Blue gate', 'Lantern lilies')`.

## Limitations

Every clue must exist in the selected legend. The package stores no route state.

## Support

Report the clue string, seed, complete error, and Python version.

## Learn more

- [Reveal your first garden route](docs/first-route.qmd "first route")
- [Replace the clue legend](docs/customize-legend.qmd "customize legend")
- [Understand why seeds repeat](docs/why-seeds-repeat.qmd "seed explanation")
- [Look up the public API](docs/api.qmd "API reference")

The complete site configuration can be:

project:
  type: website
  output-dir: _site

website:
  title: Puzzle Garden Field Guide
  page-navigation: true
  search: true
  sidebar:
    contents:
      - href: index.qmd
        text: Start Here
      - href: first-route.qmd
        text: Reveal a Route
      - href: customize-legend.qmd
        text: Replace the Legend
      - href: why-seeds-repeat.qmd
        text: Why Seeds Repeat
      - href: api.qmd
        text: Public API

format:
  html:
    toc: true

Each QMD file still needs its own content. A concise complete set includes:

---
title: Reveal Your First Garden Route
---

## Prepare the project

Use Python 3.10+ and the editable installation from the README.

## Reveal the path

Run `reveal_path("L M B", seed=1)` and confirm the exact Moss arch, Blue gate,
Lantern lilies tuple.

## Recover from an unknown clue

Read the symbol named by `ValueError`, then add it to the legend or correct the
clue string.
---
title: Replace the Clue Legend
---

## Supply every used symbol

Create a mapping for `L`, `M`, and `B`; pass it through `legend=` and confirm the
returned tuple uses only the new location names.
---
title: Why the Same Seed Repeats a Path
---

The function rotates an explicit symbol list by `seed % len(symbols)`. Equal
clues, seed, and legend therefore produce the same tuple without global state.
---
title: Public API Reference
---

`reveal_path(clues, *, seed=0, legend=DEFAULT_LEGEND)` returns a tuple of decoded
names. It raises `ValueError` for empty clues or an unknown symbol.

Finally, the workflow’s gate and publication boundary should read clearly:

- name: Test code and documentation
  run: pytest --doctest-modules src tests -q

- name: Render field guide
  run: quarto render docs

- name: Verify rendered pages
  run: python tests/check_rendered_docs.py

- name: Publish verified field guide
  if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
  uses: peaceiris/actions-gh-pages@v4
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: docs/_site

Compare reader outcomes, public contracts, checks, and artifact paths—not only sentence order. A different site structure is sound when it satisfies the same observable promises.

9. Check the field guide decisions

10. Decide whether the challenge is complete

Use the evidence rather than page length:

Evidence Ready to record when
Reader entrance A clean learner can prepare the project and reproduce the seed-1 route using only README.
Page purposes Tutorial, how-to, explanation, and API pages answer distinct questions and link meaningfully.
Public API help()/docstring state inputs, rotation result, and both ValueError boundaries.
Executable examples Exactly two intended doctest attempts pass; structured boundaries remain in pytest.
Site artifact A clean render produces all five named HTML pages with repaired navigation and links.
Publication CI repeats the local gate and publishes only the verified _site artifact.
Debugging One record connects exact evidence to one hypothesis, controlled change, and clean rerun.

This button stores a self-reported marker only in this browser. It does not submit work, grade it, verify identity, or issue a certificate.

Not yet recorded.

Key points

  • Documentation is part of the Puzzle Garden result: it lets another player reveal and understand the route without the author’s hidden state.
  • Progressive checks inspect real README, docstring, QMD, HTML, and workflow artifacts rather than stand-in lists.
  • A reader-focused page, executable example, resolved link, clean render, and verified deployment each contribute different evidence.
  • The strongest solution keeps the public API stable while repairing the promises around it.
Back to top