Skip to content

Commit 6429e23

Browse files
committed
Cover Python syntax surface and add example graph
1 parent f6019d5 commit 6429e23

32 files changed

Lines changed: 2210 additions & 8 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0
2323
- Example source verifier, formatter, embedded-source build step, and golden parity check.
2424
- GitHub Actions verification workflow.
2525
- `iterators` and `generator-expressions` examples to make the Iteration arc explicit.
26+
- Syntax coverage examples for loop control, loop `else`, assertions, deletion, scope rebinding, positional-only parameters, assignment expressions, `yield from`, exception chaining/groups, advanced match patterns, inheritance, metaclasses, async iteration/context managers, import aliases, and specialized operators/literals.
27+
- Optional `see_also` example graph links with validation tests.
2628

2729
### Changed
2830

2931
- Example walkthroughs now pair prose, source fragments, and output evidence.
3032
- Iteration examples now frame Python iteration as a value-stream protocol: producers, consumers, eager containers, lazy streams, and one-pass iterators.
33+
- The quality rubric now requires explicit syntax-surface coverage and graph-style conceptual links for neighboring examples.
3134
- Read-only code highlighting is handled by Shiki; editable code highlighting is handled by CodeMirror.
3235
- Prototype routes under `/layout-options/*` bypass the Worker Cache API and return `Cache-Control: no-store`.
3336
- Static assets use immutable cache headers only on fingerprinted filenames.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Production: <https://www.pythonbyexample.dev> (`workers.dev` remains enabled as
66

77
## Features
88

9-
- 52 curated Python 3.13 examples in learning order
9+
- 69 curated Python 3.13 examples in learning order
1010
- Literate source/output cells for each example walkthrough
1111
- Editable complete examples powered by CodeMirror
1212
- Read-only syntax highlighting powered by Shiki

docs/example-graph-see-also.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# See also links and the example graph
2+
3+
The catalog should stay readable as a linear tour, but Python concepts are not purely linear. `See also` links turn the examples into a small concept graph without adding visual noise to every paragraph.
4+
5+
## Why add them
6+
7+
- **Boundaries become explicit.** A page can point to the neighboring feature learners often confuse it with: `break` vs loop `else`, comprehensions vs generator expressions, `assert` vs exceptions, property vs method.
8+
- **Prerequisites and follow-ups are visible.** A syntax page can stay compact while sending readers to the examples that explain the underlying model.
9+
- **Coverage becomes auditable.** If every edge points to a real slug, tests can catch broken conceptual links and make missing syntax harder to overlook.
10+
- **The site becomes a graph, not just a list.** Previous/next remains the recommended path; See also links expose cross-cutting concepts.
11+
12+
## Risks
13+
14+
- **Too many links can dilute the page.** Use two to four links, not a tag cloud.
15+
- **Circular links can feel arbitrary.** Cycles are fine when concepts truly reinforce each other, but every edge should answer “why should I read this next?”
16+
- **Maintenance cost increases.** Renaming or deleting a slug must update graph edges. Automated tests should validate every `see_also` slug.
17+
- **SEO and crawl shape changes.** Internal links help discovery, but repeated boilerplate links should not crowd the main lesson.
18+
19+
## Design rule
20+
21+
`See also` should be reserved for conceptual edges:
22+
23+
- prerequisite: `yield-from``generators`
24+
- contrast: `assignment-expressions``conditionals`
25+
- continuation: `async-iteration-and-context``async-await`
26+
- shared mechanism: `delete-statements``mutability`
27+
28+
Do not use it as a category list. The home page and previous/next navigation already provide the linear table of contents.
29+
30+
## Current implementation
31+
32+
Example Markdown frontmatter may include:
33+
34+
```toml
35+
see_also = [
36+
"for-loops",
37+
"while-loops",
38+
]
39+
```
40+
41+
The loader exposes `see_also`, the example renderer displays a compact `See also` section, and tests verify that every linked slug exists and does not point to itself.
42+
43+
## Future improvements
44+
45+
- Add a graph audit script that reports orphan pages, high-degree pages, and missing reciprocal links.
46+
- Show short edge labels later, e.g. `contrast`, `prerequisite`, `next depth`.
47+
- Use graph data to recommend examples on 404 pages or search results.
48+
- Keep the first version minimal until we know the links improve reading rather than distracting from the code.

docs/example-quality-rubric.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Score each example on a 10 point scale:
1111
5. **Source/result pairing (0-1.25)** — each important source fragment has nearby output that proves the semantic point, not merely that the code ran.
1212
6. **Concept decomposition (0-1.0)** — the example breaks the concept into meaningful parts instead of presenting one compressed trick.
1313
7. **Progressive walkthrough (0-0.75)** — each cell introduces one new idea, and the sequence builds toward the complete concept. Single-cell examples are acceptable only for intentionally atomic concepts.
14-
8. **Representative coverage (0-0.75)** — the code covers the forms promised by the title, summary, and prose. Do not claim lists, dictionaries, and sets while showing only two of them.
14+
8. **Representative coverage (0-0.75)** — the code covers the forms promised by the title, summary, and prose, and the catalog has an explicit home for every common Python syntax form. Do not claim lists, dictionaries, and sets while showing only two of them; do not let syntax such as `break`, `continue`, `assert`, `nonlocal`, `yield from`, or `async for` exist only as untested assumptions.
1515
9. **Contrast and boundary clarity (0-0.75)** — when a feature is commonly confused with another feature, the example shows the distinction: comprehension vs loop, `sorted()` vs `list.sort()`, `lambda` vs `def`, `==` vs `is`, generator vs list, property vs method, and similar boundaries.
1616
10. **Practical usefulness (0-1.0)** — names, data, and outputs resemble simplified real code rather than toy placeholders; the example gives the feature a reason to exist.
1717

@@ -22,6 +22,7 @@ Release gates outside the score:
2222
- page layout remains restrained and readable
2323
- examples verify under the configured Python version
2424
- generated embedded source and asset manifests are up to date
25+
- syntax-surface tests prove that each major statement, expression marker, loop-control form, function-signature marker, pattern form, import form, and async form has a runnable example
2526
- iteration examples identify what produces values, what consumes values, whether values are stored or streamed, and whether the stream is reusable or one-pass
2627

2728
Quality bands:
@@ -39,6 +40,7 @@ Flag these during review even when the code is correct:
3940

4041
- A multi-part concept has only one cell.
4142
- The title or prose promises forms not shown by code.
43+
- A Python syntax form appears in the language-tour checklist but has no runnable example.
4244
- Output is a single scalar for a collection transformation, so the reader cannot see the shape of the result.
4345
- A concept with a common confusion has no contrast or boundary example.
4446
- More than half of nonblank code lines are `print(...)` calls.
@@ -63,3 +65,4 @@ Before publishing or substantially editing an example, ask:
6365
7. For iteration examples, what produces values, what consumes them, and are they stored eagerly or streamed lazily?
6466
8. What neighboring feature would a learner confuse this with, and does the page explain the boundary?
6567
9. Does the data shape explain why this feature exists?
68+
10. What syntax form would disappear from the catalog if this page were removed, and is that covered somewhere else?

public/site.css

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/format_examples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
ROOT = Path(__file__).resolve().parents[1]
1111
SOURCE_DIR = ROOT / "src" / "example_sources"
12-
FRONTMATTER_ORDER = ["slug", "title", "section", "summary", "doc_path", "min_python", "version_sensitive", "version_notes"]
12+
FRONTMATTER_ORDER = ["slug", "title", "section", "summary", "doc_path", "see_also", "min_python", "version_sensitive", "version_notes"]
1313

1414

1515
def toml_value(value: Any) -> str:

src/app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,13 @@ def render_example_page(example, output=None, code=None, execution_time_ms=None)
303303
for index, step in enumerate(walkthrough, 1)
304304
)
305305
notes_html = "".join(f"<li>{note}</li>" for note in notes)
306+
see_also_examples = [get_example(slug) for slug in example.get("see_also", [])]
307+
see_also_links = "".join(
308+
f'<li><a class="text-link" href="/examples/{html.escape(item["slug"])}">{html.escape(item["title"])}</a></li>'
309+
for item in see_also_examples
310+
if item is not None
311+
)
312+
see_also_html = f'<section class="see-also"><h2>See also</h2><ul>{see_also_links}</ul></section>' if see_also_links else ""
306313
content = _replace(
307314
_template("example.html"),
308315
{
@@ -312,6 +319,7 @@ def render_example_page(example, output=None, code=None, execution_time_ms=None)
312319
"SUMMARY": html.escape(example["summary"]),
313320
"WALKTHROUGH": walkthrough_html,
314321
"NOTES": notes_html,
322+
"SEE_ALSO": see_also_html,
315323
"PREVIOUS_LINK": previous_link,
316324
"NEXT_LINK": next_link,
317325
"SLUG": html.escape(example["slug"]),

src/asset_manifest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Generated by scripts/fingerprint_assets.py. Do not edit by hand.
2-
ASSET_PATHS = {'SITE_CSS': '/site.06e133dcde6a.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3-
HTML_CACHE_VERSION = 'd3f04ba18268'
2+
ASSET_PATHS = {'SITE_CSS': '/site.dc51488c4ed9.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3+
HTML_CACHE_VERSION = '5cd6e8013a84'

src/example_loader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def example_to_dict(filename: str, catalog: ExampleCatalog) -> dict[str, Any]:
151151
"doc_url": f"{catalog.docs_base_url}{doc_path}",
152152
"explanation": intro,
153153
"notes": notes,
154+
"see_also": list(frontmatter.get("see_also", [])),
154155
"walkthrough": [{"prose": prose, "code": cell.source} for cell in cells for prose in cell.prose],
155156
"cells": [{"prose": cell.prose, "code": cell.source, "output": cell.output, "line": cell.line} for cell in cells],
156157
"code": code,

0 commit comments

Comments
 (0)