diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 973357a..3e96859 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: # griffe reads the stub and inspects the installed extension, so # the check runs against the wheel rather than against the # checkout it was built from. - - run: pip install pytest griffe + - run: pip install pytest griffe ipython - run: pytest # The shared corpus, which is the same 945 cases the engine runs diff --git a/README.md b/README.md index eab13c2..8a38d54 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,26 @@ result.record_batches() # a reader, for a result larger than memory `Result` implements `__arrow_c_stream__`, so anything that reads the protocol reads a result directly and none of the four methods above is needed: `pyarrow.table(result)` and `polars.DataFrame(result)` both work. Batches are 65,536 rows. A column holds one type, which the values decide, and integers beside floats are the one mixture that widens rather than being refused. Nodes, rels and paths go across as structs. The copy runs with the GIL released, and on this machine 300,000 rows across three columns take 44 ms as Arrow against 67 ms as Python objects, and a single integer column takes 13.8 ms against 44.5 ms. +## In a notebook + +A result in a cell draws itself as a table, because Jupyter asks an object for `_repr_html_` before it falls back to `repr` and a line saying how many rows there are is a strictly worse answer than the rows. Nodes, rels and paths draw themselves too, a path as the walk it is: `(person #0) -[knows]-> (person #1)`. + +```python +%load_ext zudb.magic +%gql social.zu1 +``` + +```python +%%gql +MATCH (p:person) WHERE p.score > 40 RETURN p.name AS name, p.score AS score +``` + +The cell is one statement, it runs on the current connection, and the result is the value of the cell, so `_` is a `zudb.Result` and everything a result can do is still there. `%gql` is about which connection and `%%gql` is about the statement, and neither guesses the other's job. A notebook that already called `zudb.connect` needs no `%gql` at all: if exactly one connection is lying about the namespace `%%gql` uses it, and if there is more than one it names them and asks which. `%%gql --conn other --params args --out rows` says which connection, where the parameters are, and where to put the result instead of showing it, each of them naming a variable because a notebook has the values already. + +The markup is a table, a stylesheet and no script, so it survives `nbconvert`, an exported HTML file and a notebook diff, and there is nothing to install for any of it. Colours come from the notebook through `currentColor` and opacity, because a light theme and a dark one are both in the room. Values are escaped, since a string column holding `'})") + html = empty.execute("MATCH (p:person) RETURN p.name AS name")._repr_html_() + assert "