Rename the project from peo to boltage

The name peo cannot be registered on PyPI. Rename the package, the
command, the PEO_* environment variables (now BOLTAGE_*), the container
paths and scripts, and the source URLs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bart BroereandClaude Opus 5.5 committed 2026-10-30 17:43:29 +01:00
1 parent 5418473158
commit 5784de249d
20 files changed
+180 -180

No files matched your search

+52 -52
View File
@@ -1,16 +1,16 @@
# peo
# boltage
[neo4j-browser](https://github.com/neo4j/neo4j-browser), cypher-shell and the Neo4j
drivers on top of PostgreSQL, using [Apache AGE](https://github.com/apache/age) as the
graph engine.
peo is a small Python server that speaks Neo4j's Bolt protocol and translates Cypher
boltage is a small Python server that speaks Neo4j's Bolt protocol and translates Cypher
into AGE `cypher()` calls. It can also serve neo4j-browser. A [container
image](#container-images) bundles everything, together with
[pgAdmin](https://www.pgadmin.org/) for working with the same data in SQL.
```
neo4j-browser (static files) peo (Python) PostgreSQL + AGE
neo4j-browser (static files) boltage (Python) PostgreSQL + AGE
┌──────────────────────┐ Bolt ┌────────────────────────-──┐ SQL ┌────────────────┐
│ in your web browser │───────▶│ Bolt server (TCP + WS) │──────▶│ cypher('g',$$ │
│ served on :7474 │ :7687 │ Cypher → AGE translation │ :5432 │ ... $$) │
@@ -20,63 +20,63 @@ image](#container-images) bundles everything, together with
## Installation
peo needs Python 3.11 or later and a PostgreSQL server with the AGE extension installed
boltage needs Python 3.11 or later and a PostgreSQL server with the AGE extension installed
and loaded (`shared_preload_libraries=age`).
```sh
pip install 'peo[binary]'
PEO_PG_HOST=localhost PEO_PG_DATABASE=postgres peo
pip install 'boltage[binary]'
BOLTAGE_PG_HOST=localhost BOLTAGE_PG_DATABASE=postgres boltage
```
The `binary` extra pulls in psycopg's prebuilt wheels; leave it out to use a psycopg
built against your system's libpq. `python -m peo` works the same as `peo`.
built against your system's libpq. `python -m boltage` works the same as `boltage`.
Neo4j clients can now connect to `neo4j://localhost:7687` with a PostgreSQL user name
and password.
peo does not include neo4j-browser itself. To serve it, install the `browser` extra
boltage does not include neo4j-browser itself. To serve it, install the `browser` extra
([Starlette](https://www.starlette.io/) and [uvicorn](https://www.uvicorn.org/)),
build neo4j-browser and point `PEO_BROWSER_DIR` at its `dist` directory; it is then
build neo4j-browser and point `BOLTAGE_BROWSER_DIR` at its `dist` directory; it is then
served on <http://localhost:7474/>. The [container images](#container-images) do all of
this for you.
```sh
pip install 'peo[binary,browser]'
PEO_PG_HOST=localhost PEO_PG_DATABASE=postgres PEO_BROWSER_DIR=/path/to/neo4j-browser/dist peo
pip install 'boltage[binary,browser]'
BOLTAGE_PG_HOST=localhost BOLTAGE_PG_DATABASE=postgres BOLTAGE_BROWSER_DIR=/path/to/neo4j-browser/dist boltage
```
## Configuration
peo is configured through environment variables:
boltage is configured through environment variables:
| Variable | Default | Purpose |
| --- | --- | --- |
| `PEO_PG_HOST` | `127.0.0.1` | PostgreSQL host. |
| `PEO_PG_PORT` | `5432` | PostgreSQL port. |
| `PEO_PG_DATABASE` | `$POSTGRES_DB`, else `$POSTGRES_USER`, else `postgres` | PostgreSQL database holding the graphs. |
| `PEO_DEFAULT_GRAPH` | `neo4j` | Graph used when a client does not pick a database. |
| `PEO_BOLT_PORT` | `7687` | Port the Bolt server listens on (TCP and WebSocket). |
| `PEO_HTTP_PORT` | `7474` | Port neo4j-browser is served on, when `PEO_BROWSER_DIR` is set. |
| `PEO_BROWSER_DIR` | (unset) | neo4j-browser `dist` directory to serve; needs the `browser` extra. |
| `PEO_ADVERTISED_BOLT_PORT` | `$PEO_BOLT_PORT` | Bolt port advertised to the browser; set it when clients reach the Bolt port on another port. |
| `PEO_ADVERTISED_HOST` | `localhost` | Host reported in `SHOW DATABASES` and to clients that do not send a routing address. |
| `PEO_LOG_LEVEL` | `INFO` | `DEBUG` logs every generated SQL statement. |
| `BOLTAGE_PG_HOST` | `127.0.0.1` | PostgreSQL host. |
| `BOLTAGE_PG_PORT` | `5432` | PostgreSQL port. |
| `BOLTAGE_PG_DATABASE` | `$POSTGRES_DB`, else `$POSTGRES_USER`, else `postgres` | PostgreSQL database holding the graphs. |
| `BOLTAGE_DEFAULT_GRAPH` | `neo4j` | Graph used when a client does not pick a database. |
| `BOLTAGE_BOLT_PORT` | `7687` | Port the Bolt server listens on (TCP and WebSocket). |
| `BOLTAGE_HTTP_PORT` | `7474` | Port neo4j-browser is served on, when `BOLTAGE_BROWSER_DIR` is set. |
| `BOLTAGE_BROWSER_DIR` | (unset) | neo4j-browser `dist` directory to serve; needs the `browser` extra. |
| `BOLTAGE_ADVERTISED_BOLT_PORT` | `$BOLTAGE_BOLT_PORT` | Bolt port advertised to the browser; set it when clients reach the Bolt port on another port. |
| `BOLTAGE_ADVERTISED_HOST` | `localhost` | Host reported in `SHOW DATABASES` and to clients that do not send a routing address. |
| `BOLTAGE_LOG_LEVEL` | `INFO` | `DEBUG` logs every generated SQL statement. |
## How it works
- **Databases are AGE graphs.** Every AGE graph shows up as a Neo4j database, and
`:use <name>` switches between them. Clients that do not pick a database get `PEO_DEFAULT_GRAPH`
`:use <name>` switches between them. Clients that do not pick a database get `BOLTAGE_DEFAULT_GRAPH`
(`neo4j`); the full container image and the compose setup create that graph on first start. `CREATE DATABASE x` and `DROP DATABASE x` on the `system`
database call `create_graph` / `drop_graph`; AGE requires names of at least 3 characters.
- **Queries** are sent to AGE as `SELECT * FROM cypher('<graph>', $$ <query> $$) AS (...)`.
peo works out the result columns from the final `RETURN` clause and inlines
boltage works out the result columns from the final `RETURN` clause and inlines
query parameters as Cypher literals.
- **Neo4j procedures** that neo4j-browser relies on (`dbms.components`, `SHOW DATABASES`,
`db.labels`, `db.schema.visualization`, `SHOW PROCEDURES`, ...) are emulated from the
AGE catalog.
- **Workarounds for AGE**: neo4j-browser's "expand node" query uses a pattern
comprehension, and its relationship lookup uses `id(n) IN [...]`, which AGE rejects
for lists of more than one element. peo rewrites both into equivalent Cypher
for lists of more than one element. boltage rewrites both into equivalent Cypher
that AGE accepts.
- **Authentication** is passed straight through: every Bolt connection opens its own
PostgreSQL connection with the credentials you log in with.
@@ -283,7 +283,7 @@ things to keep in mind:
- This only works in one direction. AGE writes label tables without firing triggers, so
a Cypher `CREATE`, `SET` or `DELETE` on a mirrored label does not reach the tables,
and the next change to the row overwrites it. AGE does check table privileges, so
grant the users that log in through peo only `SELECT` on the mirrored label tables.
grant the users that log in through boltage only `SELECT` on the mirrored label tables.
The triggers are `SECURITY DEFINER`, so relational writes still reach the graph.
Superusers bypass privileges.
- Vertex ids need a `bigint` primary key below 2<sup>48</sup>. Other keys, such as
@@ -295,23 +295,23 @@ things to keep in mind:
The [`Containerfile`](Containerfile) builds two images:
- **full** (the default): PostgreSQL with AGE, peo, neo4j-browser and pgAdmin in one
- **full** (the default): PostgreSQL with AGE, boltage, neo4j-browser and pgAdmin in one
container.
- **slim** (`--target slim`): only peo and neo4j-browser, for a PostgreSQL server
- **slim** (`--target slim`): only boltage and neo4j-browser, for a PostgreSQL server
with AGE that runs elsewhere. [`compose.yaml`](compose.yaml) runs it next to
separate PostgreSQL and pgAdmin containers.
Any OCI-compatible build tool works. Docker does not pick up a `Containerfile` by
itself, so pass it explicitly: `docker build -f Containerfile -t peo .`. `docker`
itself, so pass it explicitly: `docker build -f Containerfile -t boltage .`. `docker`
takes the same options as `podman` in the commands below.
### Everything in one container
```sh
podman build -t peo .
podman run -d --name peo -e POSTGRES_PASSWORD=secret \
podman build -t boltage .
podman run -d --name boltage -e POSTGRES_PASSWORD=secret \
-p 7474:7474 -p 7687:7687 -p 5050:5050 -p 5432:5432 \
-v peo-data:/var/lib/postgresql -v peo-pgadmin:/var/lib/pgadmin peo
-v boltage-data:/var/lib/postgresql -v boltage-pgadmin:/var/lib/pgadmin boltage
```
| Port | Service | Log in with |
@@ -321,41 +321,41 @@ podman run -d --name peo -e POSTGRES_PASSWORD=secret \
| 5050 | pgAdmin: <http://localhost:5050/> | `admin@example.com` and `POSTGRES_PASSWORD` |
| 5432 | PostgreSQL, for `psql` and other SQL clients | same as neo4j-browser |
pgAdmin comes with the bundled PostgreSQL server already registered as "peo";
pgAdmin comes with the bundled PostgreSQL server already registered as "boltage";
it asks for the PostgreSQL password the first time you open it. AGE stores each
graph as a schema of the same name, with one table per label.
Besides peo's own variables above, the image uses these:
Besides boltage's own variables above, the image uses these:
| Variable | Default | Purpose |
| --- | --- | --- |
| `POSTGRES_PASSWORD` | (required) | Password of the PostgreSQL superuser, see the [postgres image docs](https://hub.docker.com/_/postgres). `POSTGRES_USER` and `POSTGRES_DB` work too. |
| `PEO_DEFAULT_GRAPH` | `neo4j` | Also the graph created when the database is initialised. |
| `BOLTAGE_DEFAULT_GRAPH` | `neo4j` | Also the graph created when the database is initialised. |
| `PGADMIN_DEFAULT_EMAIL` | `admin@example.com` | pgAdmin login, created on first start. |
| `PGADMIN_DEFAULT_PASSWORD` | `$POSTGRES_PASSWORD` | pgAdmin login password, set on first start. |
| `PEO_PGADMIN` | `true` | Set to `false` to not start pgAdmin. |
| `PEO_PGADMIN_PORT` | `5050` | Port pgAdmin listens on inside the container. |
| `BOLTAGE_PGADMIN` | `true` | Set to `false` to not start pgAdmin. |
| `BOLTAGE_PGADMIN_PORT` | `5050` | Port pgAdmin listens on inside the container. |
pgAdmin keeps its users and settings in `/var/lib/pgadmin`; changing
`PGADMIN_DEFAULT_*` after the first start has no effect unless that directory is empty.
### peo with a separate PostgreSQL server
### boltage with a separate PostgreSQL server
The slim image runs peo and serves neo4j-browser, and is configured with the
The slim image runs boltage and serves neo4j-browser, and is configured with the
variables in [Configuration](#configuration). Point it at any PostgreSQL server
that has AGE installed and loaded (`shared_preload_libraries=age`):
```sh
podman build --target slim -t peo:slim .
podman run -d --name peo -p 7474:7474 -p 7687:7687 \
-e PEO_PG_HOST=db.example.com -e PEO_PG_DATABASE=graphs peo:slim
podman build --target slim -t boltage:slim .
podman run -d --name boltage -p 7474:7474 -p 7687:7687 \
-e BOLTAGE_PG_HOST=db.example.com -e BOLTAGE_PG_DATABASE=graphs boltage:slim
```
peo creates no graphs of its own. If the database has none yet, run
boltage creates no graphs of its own. If the database has none yet, run
`CREATE DATABASE neo4j` on the `system` database (`:use system` in neo4j-browser),
or `SELECT ag_catalog.create_graph('neo4j');` in SQL.
### Compose: peo, PostgreSQL and pgAdmin as separate containers
### Compose: boltage, PostgreSQL and pgAdmin as separate containers
[`compose.yaml`](compose.yaml) builds the slim image and runs it with
[`apache/age`](https://hub.docker.com/r/apache/age) for PostgreSQL and the official
@@ -368,10 +368,10 @@ podman compose up -d
It works with `podman compose` and `docker compose`, and publishes the same ports and
logins as the full image: neo4j-browser on 7474, Bolt on 7687, pgAdmin on 5050 and
PostgreSQL on 5432. Set `PEO_POSTGRES_PORT` to publish PostgreSQL on another host
port. The default graph (`PEO_DEFAULT_GRAPH`, `neo4j`) is created when the database
is initialised, and pgAdmin comes with the server already registered as "peo".
`PGADMIN_DEFAULT_EMAIL`, `PGADMIN_DEFAULT_PASSWORD` and `PEO_LOG_LEVEL` can be set
PostgreSQL on 5432. Set `BOLTAGE_POSTGRES_PORT` to publish PostgreSQL on another host
port. The default graph (`BOLTAGE_DEFAULT_GRAPH`, `neo4j`) is created when the database
is initialised, and pgAdmin comes with the server already registered as "boltage".
`PGADMIN_DEFAULT_EMAIL`, `PGADMIN_DEFAULT_PASSWORD` and `BOLTAGE_LOG_LEVEL` can be set
in `.env` too. Data is kept in the `postgres-data` and `pgadmin-data` volumes;
`podman compose down -v` removes them.
@@ -394,11 +394,11 @@ This is a first version. Things that do not work (yet):
- User and role management, `SHOW TRANSACTIONS`, `:sysinfo`.
- TLS (`bolt+s://`, HTTPS).
- Each query's full result is fetched from PostgreSQL before it is streamed to the
client, so very large results use memory in peo.
client, so very large results use memory in boltage.
## Development
peo depends on `psycopg` 3 and `websockets`, plus Starlette and uvicorn for the
boltage depends on `psycopg` 3 and `websockets`, plus Starlette and uvicorn for the
`browser` extra. To work on it, install it in editable mode; `python -m build` (or
`uv build`) builds an sdist and wheel:
@@ -416,12 +416,12 @@ python tests/smoke_test.py neo4j://localhost:7687 postgres secret
## License
peo is free software, licensed under the GNU General Public License v3.0 or
boltage is free software, licensed under the GNU General Public License v3.0 or
later; see [LICENSE](LICENSE). The container images bundle third-party software
under their own licenses, including neo4j-browser (GPL-3.0), Apache AGE
(Apache-2.0), PostgreSQL and pgAdmin (PostgreSQL License). See
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for the full list and where
each license text and source archive is in the image.
Neo4j is a trademark of Neo4j, Inc.; peo is not affiliated with or endorsed
Neo4j is a trademark of Neo4j, Inc.; boltage is not affiliated with or endorsed
by Neo4j.