Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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",
Expand Down
86 changes: 86 additions & 0 deletions tests/test_readme.py
Original file line number Diff line number Diff line change
@@ -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
Loading