From 258d8608d085da84122c2cb90063cfb6fc6f3a61 Mon Sep 17 00:00:00 2001 From: Tam Nguyen Duc <1218621+tamnd@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:19:29 +0700 Subject: [PATCH] the sixty second snippet, run as it is printed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A quickstart is the most read and least executed code a client has. It is copied out of a page by hand, and it goes wrong quietly, a rename at a time, until somebody's first five minutes are a traceback. So the whole programs in the README are run here, in an interpreter of their own with a temporary directory as their working directory, which is what a reader does with them. A block is a whole program when it starts with `import zudb`, which is the rule the page already follows: a block that stands on its own carries its import and a block showing one call in the middle of a session does not. The load block gained its import, since it stands on its own and was one line short of it. The opening snippet now ends where dx/06 ยง1 says the sixty seconds end, at `.to_pandas()`, and the install line above it asks for the extra that last line wants rather than leaving a reader to find out. --- README.md | 10 +++--- tests/test_readme.py | 86 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 tests/test_readme.py diff --git a/README.md b/README.md index 654edf5..c7f4332 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,15 @@ with zudb.connect("social.zu1") as conn: conn.execute("INSERT (p:person {uid: 1, name: 'ada'})") conn.execute("INSERT (p:person {uid: $uid, name: $name})", {"uid": 2, "name": "grace"}) - for name, uid in conn.execute("MATCH (p:person) RETURN p.name AS name, p.uid AS uid"): - print(name, uid) + people = conn.execute("MATCH (p:person) RETURN p.name AS name, p.uid AS uid") + print(people.to_pandas()) ``` ``` -pip install zudb +pip install "zudb[pandas]" ``` -No compiler, no `pkg-config`, no postinstall script. One wheel per platform with the engine inside it. +No compiler, no `pkg-config`, no postinstall script. One wheel per platform with the engine inside it. `pip install zudb` on its own brings nothing else at all; the extra above is pandas, which the last line of the snippet asks for and which a result hands its columns to over Arrow. A test in this repository runs that snippet exactly as it is printed, in a directory of its own, because a quickstart is the most read and least compiled code a project has. ## What this is @@ -38,6 +38,8 @@ The interesting parts: A statement writes one row at a time, which is the wrong shape for loading data and cannot make a rel table at all. `load` is the other shape: a table's columns whole, the edges between them whole, one file written once. ```python +import zudb + zudb.load( "social.zu1", nodes="person", diff --git a/tests/test_readme.py b/tests/test_readme.py new file mode 100644 index 0000000..bc8c515 --- /dev/null +++ b/tests/test_readme.py @@ -0,0 +1,86 @@ +"""The README against a Python that runs it. + +A quickstart is the most read and least executed code a client has. It +is copied by hand out of a page, and it goes wrong quietly, a rename or +a renamed keyword at a time, until somebody's first five minutes are +spent on a traceback. So the blocks that are whole programs are run +here, as printed, character for character. + +A block is a whole program when it starts with `import zudb`, which is +the rule the README follows: a block that stands on its own carries its +import, and a block that shows one call in the middle of a session does +not. Each program runs in an interpreter of its own with a temporary +directory as its working directory, because the file it writes is the +one a reader would find beside them afterwards. +""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import pytest + +README = Path(__file__).resolve().parent.parent / "README.md" + + +def blocks(language: str) -> list[str]: + """Every fenced block of one language, in the order they appear.""" + found: list[str] = [] + current: list[str] | None = None + for line in README.read_text(encoding="utf-8").splitlines(): + if current is None: + if line.rstrip() == f"```{language}": + current = [] + elif line.rstrip() == "```": + found.append("\n".join(current) + "\n") + current = None + else: + current.append(line) + assert current is None, "a fenced block the README never closes" + return found + + +def programs() -> list[str]: + """The blocks that are whole programs.""" + return [block for block in blocks("python") if block.startswith("import zudb")] + + +def run(program: str, where: Path) -> subprocess.CompletedProcess[str]: + """A program, in an interpreter of its own, in `where`.""" + return subprocess.run( + [sys.executable, "-c", program], + cwd=where, + capture_output=True, + text=True, + timeout=120, + ) + + +def test_the_readme_prints_programs_and_not_fragments() -> None: + """The rule above holds: the page has both kinds and knows which.""" + assert len(programs()) == 2, "the README's whole programs" + assert len(blocks("python")) > len(programs()), "and its fragments" + + +def test_the_sixty_second_snippet_runs_as_printed(tmp_path: Path) -> None: + """The first block: connect, write two rows, read them as a frame.""" + snippet = programs()[0] + assert "zudb.connect" in snippet and "to_pandas" in snippet + done = run(snippet, tmp_path) + assert done.returncode == 0, done.stderr + # The frame pandas prints, whatever pandas decides to pad it with. + printed = done.stdout.split() + assert printed[:2] == ["name", "uid"] + assert "ada" in printed and "grace" in printed + # A reader runs it in the directory they are standing in, and the + # database is there when it finishes. + assert (tmp_path / "social.zu1").is_file() + + +@pytest.mark.parametrize("index", range(len(programs()))) +def test_every_whole_program_in_the_readme_runs(index: int, tmp_path: Path) -> None: + """Including the ones no other test looks at the output of.""" + done = run(programs()[index], tmp_path) + assert done.returncode == 0, done.stderr