You have written chunk, repair_json,
retry and timed three times across Days 2–5, each one living
inside its own exercise folder. This is the day they become a package: a
pyproject.toml, the src/ layout, a
console entry point, a py.typed marker, and a wheel that installs on a
machine which has never seen your repository. Everything you ship after this — flagship
F1's RAG service included — is this shape with more code in it. It runs on your Mac, not
in the browser: it builds a real wheel and a real virtual environment, which Pyodide
cannot do.
src/llmutils/chunking.py — chunk(text, size, overlap) ->
list[list[str]]: split on whitespace, then into chunks of size tokens
with overlap shared, so the stride is size - overlap and
out[1][0] == out[0][size - overlap]. The last chunk is kept even when short;
whitespace-only text gives []; size < 1, overlap <
0 and overlap >= size raise ValueError
(py-02's arithmetic).src/llmutils/jsonrepair.py — repair_json(s) -> object:
unwrap a ```json fence, single quotes to double quotes (only when the text
has no double quotes at all), drop a comma before } or ], close
a truncated tail — string first, then the open arrays and objects in reverse order. Valid
JSON passes through; anything still unparseable raises ValueError
(py-06).src/llmutils/decorators.py — retry(times, backoff, on, *,
sleep) calls up to times times and re-raises the last error, waiting
backoff * 2 ** i between attempts and retrying only the types in
on; timed(sink, *, clock) reports sink(name,
seconds) exactly once per call, even when the call raises. Both keep
__name__ and __doc__
(py-04).src/llmutils/cli.py — main(argv=None) -> int:
llmutils --version prints llmutils <version> and returns
0; anything else prints a usage line on stderr and returns 2.src/llmutils/py.typed — an empty file you have to create. The
pyproject.toml already ships it; without the file,
PEP 561
says every downstream mypy must ignore your annotations.src/llmutils/__init__.py is provided: it re-exports the four functions
and reads __version__ from importlib.metadata — the installed
distribution's metadata, not a second copy of the number. Read the
pyproject.toml too: the build backend, the src/ layout, the entry
point and the two tool sections are the artifact this exercise is really about.
The tests are ordinary pytest and ship in the public folder with the starter — read them first; the names below are the check list. Solutions are not published.
Needs git. uv installs the right Python itself, so nothing else is required.
# once, anywhere on your machine
git clone https://github.com/theDocWho/ai-ml-roadmap.git
cd ai-ml-roadmap
No git? Download the ZIP, unzip it, and cd into the unzipped folder instead.
From the repo root:
# one-time: uv (https://docs.astral.sh/uv/) manages the venv and pins Python ≥ 3.12 cd exercises/py-08-tiny-package && uv sync && uv run pytest -q -m '' # the same bar the reference solution clears uv run ruff check . && uv run mypy --strict src && uv run python -m build --wheel
Done when uv run pytest -q -m '' prints 8 passed.
Plain uv run pytest -q runs the five fast checks; -m '' clears the
-m 'not slow' in addopts and adds the three that build a real
wheel and a throwaway venv (about 15 s). The untouched starter fails all eight —
including ruff (the imports it hands you are unused until you use them) and
mypy (a ... body is [empty-body] in strict mode).
test_chunk_shares_overlap_and_keeps_the_last_partial_chunk — 10 tokens at
size=4, overlap=1 give 3 chunks, out[1][0] == out[0][3], and a
6-token text gives lengths [4, 3]; the three bad argument combinations raise
ValueError.test_repair_json_fixes_fences_quotes_commas_and_a_truncated_tail — a fenced
block, single quotes, {"a": 1, "b": [2, 3,],}, {"a": [1, 2 and
{"a": "unfinis; prose raises ValueError.test_retry_re_raises_after_n_attempts_and_timed_reports_seconds — exactly 3
attempts and waits [0.1, 0.2]; a KeyError with
on=(ValueError,) is not retried; timed reports once and
wraps keeps the name and the docstring.test_mypy_strict_clean — mypy --strict src exits 0.test_ruff_clean — ruff check . exits 0.test_wheel_contains_py_typed (slow) — the built wheel holds
llmutils/py.typed.test_wheel_builds_and_installs (slow) — the wheel installs into an
empty venv, imports from site-packages (not from your src/), and
chunk works there.test_entry_point_prints_version (slow) — the venv grew an
llmutils launcher and llmutils --version prints
llmutils 0.1.0.This is self-attestation — the site cannot see your terminal, so the box and the button are you telling The Path the suite went green on your machine.
chunk — validate first, then
for start in range(0, len(tokens), size - overlap), take
tokens[start : start + size], and stop as soon as a chunk reached the end.repair_json — do not write one clever regex. Generate candidate texts,
cheapest repair first, and return the first one json.loads accepts;
json.JSONDecodeError is already a ValueError, so re-raising as
ValueError is consistent.retry — except on: accepts a tuple of types directly. The
attempt loop must return inside the try, and re-raise on the last
attempt rather than falling out of the loop.--strict — annotate the decorator factories with
ParamSpec and a TypeVar
(Callable[[Callable[P, R]], Callable[P, R]]), or every decorated function
becomes Any and strict mode complains about the callers.uv run python -m build --wheel then
unzip -l dist/*.whl shows exactly what someone else will get. If the listing
has no llmutils/ in it, the problem is in pyproject.toml, not in
your code.uv run pytest -q -m '' -x --tb=short stops at the
first failure; the message names the behaviour, not the fix.