PyStack
The full stack, from scratch.
PyStack connects SEVENTEEN educational projects into one integrated platform. Think of it like a school:
| Project | Role | School Analogy |
|---|---|---|
| PyOS | Operating system | The school building |
| Pebble | Programming language | The language students speak |
| PyDB | Database engine | The filing cabinet |
| PyCrypt | Cryptography toolkit | The combination lock |
| PyWeb | HTTP web server | The front office |
| PyNet | Networking library | The phone system |
| PyGit | Version control | The yearbook archive |
| PySearch | Full-text search engine | The library index |
| PyMQ | Message queue | The intercom |
| PyKV | Key-value store | The cubby holes — one labeled box per thing |
| PyDocDB | Document database | The index-card box — every card has its own fields |
| PyGraphDB | Graph database | The friendship map — who knows whom |
| PyTSDB | Time-series database | The attendance logbook — readings over time |
| PyVecDB | Vector database | The "find similar" recommendation board |
| PyColDB | Column-family store | The flexible report cards |
| PyLLM | Language model | The imagination — the brain that dreams up new text |
| PyStack | Integration layer | The hallways connecting everything |
PyStack is the hallways. Without hallways, you can't get from the filing cabinet to the front office. PyStack lets a Pebble program query a database, hash a password, fetch a web page, search documents, and send messages -- all in one program.
What Can You Do?
PyStack adds 14 Pebble modules so your programs can use every
project. Just import the one you need.
Database -- import "db"
import "db"
db_execute("CREATE TABLE pets (name TEXT, kind TEXT)")
db_execute("INSERT INTO pets VALUES ('Buddy', 'dog')")
let pets = db_query("SELECT * FROM pets")
for pet in pets {
print(pet["name"] + " is a " + pet["kind"])
}
let tables = db_tables()
print(tables)
Cryptography -- import "crypto"
import "crypto"
let h = hash("secret password")
print(h)
let encrypted = caesar_encrypt("HELLO", 3)
print(encrypted)
let decrypted = caesar_decrypt(encrypted, 3)
print(decrypted)
let tag = hmac_sign("important message", "my-key")
let ok = hmac_verify("important message", tag, "my-key")
print(ok)
Web -- import "web"
import "web"
let page = http_get("http://example.com")
print(page)
let parts = url_parse("https://example.com:8080/hello?q=1")
print(parts["host"])
print(parts["port"])
Git -- import "git"
import "git"
let h = git_hash("hello world")
print(h)
let d = git_diff("old version", "new version")
print(d)
Networking -- import "net"
import "net"
let ip = dns_lookup("example.com")
print(ip)
let parts = url_parse("https://example.com/page")
print(parts["scheme"])
let encoded = base64_encode("Hello!")
print(encoded)
let decoded = base64_decode(encoded)
print(decoded)
Search -- import "search"
import "search"
let engine = search_create()
search_add(engine, "doc1", "the cat sat on the mat")
search_add(engine, "doc2", "the dog chased the cat")
let results = search_query(engine, "cat")
print(results)
Message Queue -- import "mq"
import "mq"
let q = mq_create("orders")
mq_put("orders", "one coffee please")
mq_put("orders", "two teas")
let msg = mq_get("orders")
print(msg)
mq_subscribe("news")
mq_publish("news", "school is cancelled!")
let messages = mq_receive("news")
print(messages)
Key-Value Store -- import "kv"
import "kv"
kv_set("name", "Alice")
let who = kv_get("name")
print(who)
kv_delete("name") # "ok", or "error: key 'name' not found"
let keys = kv_keys()
print(keys)
Document Database -- import "docdb"
import "docdb"
doc_insert("students", {"name": "Alice", "age": 14})
let results = doc_find("students", {"name": "Alice"})
print(results)
let total = doc_count("students")
print(total)
Graph Database -- import "graphdb"
import "graphdb"
graph_add_node("alice", "Person")
graph_add_node("bob", "Person")
graph_add_edge("alice", "bob", "KNOWS")
let path = graph_shortest_path("alice", "bob")
print(path)
Time-Series Database -- import "tsdb"
import "tsdb"
ts_add("temperature", 72.5)
ts_add("temperature", 74.0)
let readings = ts_query("temperature")
print(readings)
let avg = ts_avg("temperature", 3600)
print(avg)
Vector Database -- import "vecdb"
import "vecdb"
vec_insert("song1", [0.9, 0.1, 0.3])
vec_insert("song2", [0.2, 0.8, 0.4])
let similar = vec_search([0.85, 0.15, 0.35], 1)
print(similar)
Column-Family Store -- import "coldb"
import "coldb"
col_set("grades", "alice", "math", "A+")
let grade = col_get("grades", "alice", "math")
print(grade)
Language Model -- import "llm"
import "llm"
let code = llm_generate("add two numbers")
print(code)
let valid = llm_write_pebble("add two numbers")
print(valid)
let name = llm_dream("pik")
print(name)
llm_generate lets the model write freely, so its Pebble-flavoured code doesn't
always parse. llm_write_pebble calls in PyLLM's grammar referee, which
vetoes every illegal character, so what comes back is always real, parseable
Pebble. A plain-English request is pinned to the top as a comment and the model
writes code underneath:
# add two numbers
print(self)
yse = 36
print(c)
a = 43
let total = 83 % (total - 73)
let a = value * (25 + 14)
print(step())
All Commands
| Command | What It Does |
|---|---|
pystack pebble <file.pbl> |
Run a Pebble program with all 14 modules available |
pystack sql |
Interactive SQL REPL |
pystack os |
PyOS shell with Pebble, SQL, and plugin commands |
pystack web |
Browser-based UI at http://localhost:8080 |
pystack --help |
Show available commands |
pystack <file.pbl> |
Shortcut for pystack pebble <file.pbl> |
Shell Commands
When you run pystack os, these commands are available at the
pystack-os> prompt:
| Command | What It Does | From |
|---|---|---|
pebble run <file.pbl> |
Run a Pebble program | PyOS + Pebble |
pebble eval '<code>' |
Evaluate Pebble code inline | PyOS + Pebble |
sql <statement> |
Run a SQL statement | PyOS + PyDB |
hash <text> |
Print the SHA-256 hash | PyCrypt |
curl <url> |
Fetch a URL | PyWeb + PyNet |
webdemo |
Start a real PyWeb async server on localhost: public page, a doorman who says 401 until you log in, a session cookie wristband, then three live events | PyWeb |
dns <hostname> |
Resolve hostname to IP | PyNet |
overlap [n] [seconds] |
Run n sleeps at once on PyNet's event loop and time them | PyNet |
netecho <text> |
Send a message through a local PyNet server and time the round trip | PyNet |
wsecho <text> |
Send a message through a local PyNet WebSocket server and print the echo | PyNet |
git-hash <text> |
Print the Git blob hash (same as git hash-object) |
PyGit |
git-diff <old> <new> |
Show a diff between two strings | PyGit |
mq-put <queue> <msg> |
Put a message on a queue | PyMQ |
mq-get <queue> |
Get the next message from a queue | PyMQ |
mqnet |
Run a real PyMQ broker on localhost: one worker crashes, its lease comes back to another, a poison job lands in the dead-letter box | PyMQ |
dbcrash |
Pull the plug on a journalled PyDB: a helper commits three job runs (with dates and timestamps) and dies mid-way through a fourth; reopening brings back the three and not the fourth | PyDB |
kv-set <key> <value> |
Store a value under a key | PyKV |
kv-get <key> |
Get a value by key | PyKV |
kv-delete <key> |
Delete a key (says so if it wasn't there) | PyKV |
kv-keys |
List all keys | PyKV |
doc-insert <collection> <json> |
Insert a document | PyDocDB |
doc-find <collection> [query] |
Find documents | PyDocDB |
doc-count <collection> |
Count documents | PyDocDB |
graph <add-node|add-edge|path> ... |
Graph database operations | PyGraphDB |
ts-add <series> <value> |
Add a data point | PyTSDB |
ts-query <series> |
Query a series | PyTSDB |
ts-avg <series> <window_seconds> |
Average over a time window | PyTSDB |
vec <insert|search|cosine> ... |
Vector database operations | PyVecDB |
col <set|get> <family> <row> <col> [value] |
Column-family operations | PyColDB |
dream <prompt> |
Dream up new Pokemon names | PyLLM |
Quick Start
# Install dependencies (released versions of all 17 sibling projects)
uv sync --all-extras
# Run an example program
pystack pebble examples/hello.pbl
# Try the interactive SQL REPL
pystack sql
# Launch the OS shell
pystack os
# Open the browser UI
pystack web
# Run tests
uv run pytest
Developing alongside a sibling
Every sibling project comes from its PyPI release (fromscratch-* in
pyproject.toml), and uv.lock records the exact version. A change merged
into a sibling can't affect PyStack until the sibling publishes a release and
PyStack chooses to upgrade.
To try an unreleased change from a sibling checkout next to this one, install it over the pinned release:
uv pip install -e ../pebble-lang
uv run --no-sync pytest
Use uv run --no-sync while you do this: a plain uv run (like uv sync)
puts the pinned release back, which is how you undo the override.
To upgrade a sibling, raise its minimum version in pyproject.toml, run
uv lock --upgrade-package fromscratch-<name>, and open a pull request so CI
checks the pair together.
Releasing
- Merge the change to
main. - Tag it:
git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z. A change to the public API bumps the minor version (v0.1.0→v0.2.0); anything else bumps the patch (v0.1.0→v0.1.1).
Your Data Is Saved
PyStack is like a real database: what you store is still there next time.
Your SQL tables and the data in kv, docdb, graphdb, coldb, tsdb,
vecdb, search and mq are all kept in the pystack_data/ folder. Each
plugin has its own sub-folder (for example pystack_data/kv/store.json).
PyStack saves when it stops: .quit or exit, the end of a script, or
stopping the web server with Ctrl-C. Type save in the shell to save
straight away. If PyStack crashes, changes since the last save are lost.
Saving is crash-safe: a fresh copy is written beside the old file and swapped in at once, so a file is never half-written. If a save file is ever damaged, PyStack refuses to start and names the file, rather than starting empty and overwriting your data.
Message queues (mq) work like a real broker: messages still waiting in a
queue are saved in pystack_data/mq/queues.json and are there next time, in
the same order. Pub/sub subscriptions are different. They are like live
radio: a message published while nobody is listening is gone, and
subscriptions don't outlive the run.
Because data is kept, running the same example twice adds the same data
twice. To start fresh, delete the pystack_data/ folder (or just one
plugin's folder, like pystack_data/kv/). Saved search engines keep their
ids; search_list() lists them, so a later program can find them. Files in
pystack_data/search/ that aren't named like a saved engine (engine_7.json)
are left alone, with a warning in the log naming each one.
Plugin System
PyStack uses a plugin system to integrate projects. Active plugins include:
| Plugin | Pebble Module | Shell Commands |
|---|---|---|
| CryptoPlugin | crypto |
hash |
| WebPlugin | web |
curl, webdemo |
| GitPlugin | git |
git-hash, git-diff |
| NetPlugin | net |
dns, overlap, netecho, wsecho |
| SearchPlugin | search |
(none -- used from Pebble) |
| MQPlugin | mq |
mq-put, mq-get, mqnet |
| PyDBPlugin | (none) | dbcrash |
| PySchedulerPlugin | (none) | sched, schedagents, schedalerts, schedweb |
| LLMPlugin | llm |
dream |
The db module comes from the Pebble-DB adapter instead of a plugin,
because it needs this environment's database.
PyStack hands Pebble each environment's own modules on every run (through
Pebble's public run_source), so two environments never see each other's
data.
Each plugin can:
- Add a new stdlib module to the Pebble language (
import "crypto") - Add new commands to the PyOS shell (
hash,curl,dns, etc.) - Load its saved data at start-up (
load_state(folder)), save it (save_state(folder)) and clean up (on_shutdown()) - Run custom setup logic at boot time (
on_boot())
A plugin keeps its data on itself (self), never in module-level
variables, and saves into its own folder, pystack_data/<module>/.
Plugins are discovered automatically via Python entry points, or
registered manually in the PyStackEnvironment.
Related Projects
This project is one of a series that builds real systems "from scratch in Python" to teach how they work. See the series home for the full story.
| Project | What it teaches | Repository | Docs |
|---|---|---|---|
| PyOS | Operating systems | py-os | Docs |
| Pebble | Compilers and programming languages | pebble-lang | Docs |
| PyDB | Relational databases | pydb | Docs |
| PyKV | Key-value stores | pykv | Docs |
| PyDocDB | Document databases | pydoc-db | Docs |
| PyGraphDB | Graph databases | pygraphdb | Docs |
| PyTSDB | Time-series databases | pytsdb | Docs |
| PyVecDB | Vector databases | pyvecdb | Docs |
| PyColDB | Column-family databases | pycoldb | Docs |
| PyCrypt | Cryptography | pycrypt | Docs |
| PyNet | Computer networking | pynet | Docs |
| PyWeb | HTTP web servers | pyweb | Docs |
| PyGit | Version control | pygit | Docs |
| PySearch | Search engines | pysearch | Docs |
| PyMQ | Message queues | pymq | Docs |
| PyLLM | Language models (the brain) | pyllm | Docs |
| PyScheduler | Job scheduling | pyscheduler | Docs |
| PyStack | Full-stack integration | pystack | Docs |
Documentation
Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pystack/
License
MIT
Metadata
Release files for fromscratch-pystack 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fromscratch_pystack-0.5.0.tar.gz | 188.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fromscratch_pystack-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 256.9 kB
Release files / fromscratch_pystack-0.5.0.tar.gz
| Download URL | fromscratch_pystack-0.5.0.tar.gz |
|---|---|
| Size | 188.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c94d72e3a9d7569741144437c0b17cea2d74a6bcddd6f66ee65fba29023ac7d9
|
|
BLAKE2b-256 checksum How to use checksums |
0b3fdb64bd76b055a786a8668e461c69d70dc2b93b33d27cb1a7a488fb7d05bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / fromscratch_pystack-0.5.0-py3-none-any.whl
| Download URL | fromscratch_pystack-0.5.0-py3-none-any.whl |
|---|---|
| Size | 68.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c6633baee5055c107c2db4356e9fa17c40bcae9169d32789244ba686bda869d2
|
|
BLAKE2b-256 checksum How to use checksums |
5bea5ece646d6a0cd1f2ac3bd9ac2bed3dd627598cee8c196a50ed5ecf787649
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for fromscratch-pystack 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for fromscratch_pystack-0.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.