fromscratch-pygraphdb 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. fromscratch_pygraphdb-0.4.0/.github/workflows/ci.yml +14 -0
  2. fromscratch_pygraphdb-0.4.0/.github/workflows/docs.yml +16 -0
  3. fromscratch_pygraphdb-0.4.0/.github/workflows/release.yml +58 -0
  4. fromscratch_pygraphdb-0.4.0/.gitignore +11 -0
  5. fromscratch_pygraphdb-0.4.0/.pre-commit-config.yaml +56 -0
  6. fromscratch_pygraphdb-0.4.0/CLAUDE.md +52 -0
  7. fromscratch_pygraphdb-0.4.0/LICENSE +21 -0
  8. fromscratch_pygraphdb-0.4.0/PKG-INFO +145 -0
  9. fromscratch_pygraphdb-0.4.0/README.md +113 -0
  10. fromscratch_pygraphdb-0.4.0/conftest.py +21 -0
  11. fromscratch_pygraphdb-0.4.0/docs/concepts/edges.md +52 -0
  12. fromscratch_pygraphdb-0.4.0/docs/concepts/matching.md +52 -0
  13. fromscratch_pygraphdb-0.4.0/docs/concepts/nodes.md +42 -0
  14. fromscratch_pygraphdb-0.4.0/docs/concepts/pathfinding.md +47 -0
  15. fromscratch_pygraphdb-0.4.0/docs/concepts/persistence.md +79 -0
  16. fromscratch_pygraphdb-0.4.0/docs/concepts/queries.md +58 -0
  17. fromscratch_pygraphdb-0.4.0/docs/concepts/traversal.md +48 -0
  18. fromscratch_pygraphdb-0.4.0/docs/index.md +38 -0
  19. fromscratch_pygraphdb-0.4.0/docs/property-testing.md +398 -0
  20. fromscratch_pygraphdb-0.4.0/mkdocs.yml +82 -0
  21. fromscratch_pygraphdb-0.4.0/overrides/main.html +7 -0
  22. fromscratch_pygraphdb-0.4.0/pyproject.toml +156 -0
  23. fromscratch_pygraphdb-0.4.0/src/pygraphdb/__init__.py +72 -0
  24. fromscratch_pygraphdb-0.4.0/src/pygraphdb/_version.py +24 -0
  25. fromscratch_pygraphdb-0.4.0/src/pygraphdb/atomic.py +66 -0
  26. fromscratch_pygraphdb-0.4.0/src/pygraphdb/edge.py +177 -0
  27. fromscratch_pygraphdb-0.4.0/src/pygraphdb/errors.py +63 -0
  28. fromscratch_pygraphdb-0.4.0/src/pygraphdb/graph.py +509 -0
  29. fromscratch_pygraphdb-0.4.0/src/pygraphdb/matching.py +111 -0
  30. fromscratch_pygraphdb-0.4.0/src/pygraphdb/node.py +149 -0
  31. fromscratch_pygraphdb-0.4.0/src/pygraphdb/pathfinding.py +147 -0
  32. fromscratch_pygraphdb-0.4.0/src/pygraphdb/persistence.py +129 -0
  33. fromscratch_pygraphdb-0.4.0/src/pygraphdb/py.typed +0 -0
  34. fromscratch_pygraphdb-0.4.0/src/pygraphdb/query.py +241 -0
  35. fromscratch_pygraphdb-0.4.0/src/pygraphdb/traversal.py +105 -0
  36. fromscratch_pygraphdb-0.4.0/tests/__init__.py +1 -0
  37. fromscratch_pygraphdb-0.4.0/tests/strategies.py +77 -0
  38. fromscratch_pygraphdb-0.4.0/tests/test_atomic.py +75 -0
  39. fromscratch_pygraphdb-0.4.0/tests/test_edge.py +121 -0
  40. fromscratch_pygraphdb-0.4.0/tests/test_errors.py +38 -0
  41. fromscratch_pygraphdb-0.4.0/tests/test_graph.py +502 -0
  42. fromscratch_pygraphdb-0.4.0/tests/test_matching.py +99 -0
  43. fromscratch_pygraphdb-0.4.0/tests/test_model.py +298 -0
  44. fromscratch_pygraphdb-0.4.0/tests/test_node.py +116 -0
  45. fromscratch_pygraphdb-0.4.0/tests/test_pathfinding.py +127 -0
  46. fromscratch_pygraphdb-0.4.0/tests/test_persistence.py +186 -0
  47. fromscratch_pygraphdb-0.4.0/tests/test_properties.py +287 -0
  48. fromscratch_pygraphdb-0.4.0/tests/test_query.py +132 -0
  49. fromscratch_pygraphdb-0.4.0/tests/test_traversal.py +121 -0
  50. fromscratch_pygraphdb-0.4.0/uv.lock +920 -0
@@ -0,0 +1,14 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ ci:
14
+ uses: robcox2910/robcox2910.github.io/.github/workflows/python-ci.yml@v1
@@ -0,0 +1,16 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch: {}
7
+
8
+ jobs:
9
+ docs:
10
+ uses: robcox2910/robcox2910.github.io/.github/workflows/docs.yml@v1
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+ with:
16
+ coverage-report: false
@@ -0,0 +1,58 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v7
22
+ with:
23
+ version: "latest"
24
+
25
+ - name: Set up Python
26
+ run: uv python install 3.14
27
+
28
+ - name: Build distributions
29
+ run: uv build
30
+
31
+ - name: Upload build artifacts
32
+ uses: actions/upload-artifact@v7
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ - name: Upload to GitHub Release
38
+ uses: softprops/action-gh-release@v3
39
+ with:
40
+ files: dist/*
41
+
42
+ publish:
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ environment:
46
+ name: pypi
47
+ url: https://pypi.org/project/fromscratch-pygraphdb/
48
+ permissions:
49
+ id-token: write
50
+ steps:
51
+ - name: Download build artifacts
52
+ uses: actions/download-artifact@v8
53
+ with:
54
+ name: dist
55
+ path: dist/
56
+
57
+ - name: Publish to PyPI
58
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .coverage
8
+ htmlcov/
9
+ .ruff_cache/
10
+ _version.py
11
+ .hypothesis/
@@ -0,0 +1,56 @@
1
+ default_language_version:
2
+ python: python3.14
3
+
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v5.0.0
7
+ hooks:
8
+ # Pages paste real pytest output, trailing spaces included.
9
+ - id: trailing-whitespace
10
+ exclude: ^docs/property-testing\.md$
11
+ - id: end-of-file-fixer
12
+ exclude: ^docs/property-testing\.md$
13
+ - id: check-yaml
14
+ args: ['--unsafe']
15
+ - id: check-added-large-files
16
+ - id: check-json
17
+ - id: check-toml
18
+ - id: check-merge-conflict
19
+ - id: debug-statements
20
+
21
+ - repo: https://github.com/astral-sh/ruff-pre-commit
22
+ rev: v0.15.4
23
+ hooks:
24
+ - id: ruff
25
+ args: [--fix, --exit-non-zero-on-fix]
26
+ - id: ruff-format
27
+
28
+ - repo: https://github.com/RobertCraigie/pyright-python
29
+ rev: v1.1.408
30
+ hooks:
31
+ - id: pyright
32
+
33
+ - repo: local
34
+ hooks:
35
+ - id: pip-audit
36
+ name: pip-audit
37
+ language: system
38
+ pass_filenames: false
39
+ files: ^(pyproject\.toml|uv\.lock)$
40
+ entry: >-
41
+ bash -c 'f=$(mktemp) &&
42
+ uv export --frozen --no-emit-project --all-extras --no-hashes -o "$f" >/dev/null &&
43
+ uvx pip-audit==2.10.0 -r "$f" --disable-pip --no-deps;
44
+ status=$?; rm -f "$f"; exit $status'
45
+
46
+ - repo: https://github.com/rhysd/actionlint
47
+ rev: v1.7.7
48
+ hooks:
49
+ - id: actionlint
50
+
51
+ - repo: https://github.com/commitizen-tools/commitizen
52
+ rev: v4.13.8
53
+ hooks:
54
+ - id: commitizen
55
+ stages: [commit-msg]
56
+ additional_dependencies: [typing_extensions]
@@ -0,0 +1,52 @@
1
+ # Claude Code Instructions
2
+
3
+ <!-- standard:start -->
4
+ ## Code Style
5
+
6
+ - All public functions, methods, classes, and modules **must** have a docstring
7
+ - Use imperative mood: "Return the result." not "Returns the result."
8
+ - One-line docstrings for simple functions; multi-line with summary + details for complex ones
9
+ - Follow the Google docstring convention (D211, D212 selected via ruff)
10
+
11
+ ## Linting
12
+
13
+ - Ruff enforces a comprehensive rule set -- run `uv run ruff check .` before committing
14
+ - Pyright runs in strict mode -- run `uv run pyright src tests` before committing
15
+ - All violations must be fixed, not ignored. Exceptions are the repo-owned `ignore` and `per-file-ignores` lists in `pyproject.toml`, which belong to this repo; the series sync never edits them
16
+
17
+ ## Testing
18
+
19
+ - TDD workflow: Red-Green-Refactor
20
+ - Coverage is enforced by pytest-cov; the floor in `pyproject.toml` is repo-owned
21
+ - Test classes and methods also require docstrings
22
+ - Use named constants instead of magic values (PLR2004)
23
+ - Run `uv run pytest` before committing
24
+ - Property-based tests (Hypothesis) live in `tests/test_properties.py` and `tests/test_model.py`; the shared profiles are in the root `conftest.py` (owned by `series sync`). Run `uv run pytest --hypothesis-profile=thorough` before a release.
25
+
26
+ ## Commits
27
+
28
+ - Use conventional commits: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `ci:`, `chore:`
29
+ - All changes go via PR from a feature branch (main is protected); merge when CI is green
30
+
31
+ ## Dependencies
32
+
33
+ - Package manager: uv
34
+ - Run `uv sync --all-extras` after modifying dependencies
35
+ - `uv.lock` is committed; CI and pre-commit audit it with pip-audit through `uvx`, so pip-audit is not a project dependency
36
+ - Sibling projects, where used, are pinned release tags in `[tool.uv.sources]` (git + tag); never use `path` sources
37
+
38
+ ## Releasing
39
+
40
+ - A release is a `vX.Y.Z` tag on main after the PR is merged
41
+ - Docs-only and tooling-only changes do not get a release tag
42
+ - Docs are built with `mkdocs build --strict` and deployed to GitHub Pages by the docs workflow
43
+ <!-- standard:end -->
44
+
45
+ ## Project notes
46
+
47
+ - Pyright in strict mode -- run `uv run pyright src tests` before committing
48
+ - All violations must be fixed, not ignored (except D203/D213 incompatible pair)
49
+ - Minimum 80% coverage enforced by pytest-cov
50
+ - Use conventional commits (enforced by commitizen): `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `ci:`, `chore:`
51
+ - All changes go via PR from a feature branch (main is protected)
52
+ - Add production deps to `[project.dependencies]`
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rob Cox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.5
2
+ Name: fromscratch-pygraphdb
3
+ Version: 0.4.0
4
+ Summary: An educational graph database library built from scratch in Python
5
+ Project-URL: Homepage, https://github.com/robcox2910/pygraphdb
6
+ Project-URL: Repository, https://github.com/robcox2910/pygraphdb
7
+ Project-URL: Issues, https://github.com/robcox2910/pygraphdb/issues
8
+ Project-URL: Documentation, https://robcox2910.github.io/pygraphdb/
9
+ Author: Rob Cox
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: database,education,graph,neo4j
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Education
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.14
22
+ Provides-Extra: dev
23
+ Requires-Dist: commitizen>=4.4.0; extra == 'dev'
24
+ Requires-Dist: hypothesis>=6.170; extra == 'dev'
25
+ Requires-Dist: pip-audit>=2.10.0; extra == 'dev'
26
+ Requires-Dist: pre-commit>=4.0.1; extra == 'dev'
27
+ Requires-Dist: pyright>=1.1.400; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
29
+ Requires-Dist: pytest>=8.3.4; extra == 'dev'
30
+ Requires-Dist: ruff>=0.9.3; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # PyGraphDB
34
+
35
+ An educational graph database library built from scratch in Python. Part of the **PyLearn** educational platform for kids aged 12+.
36
+
37
+ A graph database stores data as **nodes** (entities) and **edges** (relationships) -- like a social network map where people are dots and friendships are lines connecting them.
38
+
39
+ ## Features
40
+
41
+ - **Nodes & Edges** -- Create entities with labels and properties, connect them with typed relationships
42
+ - **CRUD Operations** -- Add, get, update, and delete nodes and edges
43
+ - **Graph Traversal** -- Explore your graph with BFS (breadth-first) and DFS (depth-first) algorithms
44
+ - **Shortest Path** -- Find the quickest route between two nodes (like GPS directions)
45
+ - **Pattern Matching** -- Search for nodes and edges by label, type, or properties
46
+ - **Cypher-like Queries** -- Ask questions with a simple query language inspired by Neo4j's Cypher
47
+ - **Persistence** -- Save and load your graph to/from JSON files
48
+ - **Neighbor & Degree** -- Find connected nodes and count connections
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from pygraphdb import Graph, bfs, shortest_path, execute_query, find_nodes
54
+
55
+ # Create a social network
56
+ g = Graph()
57
+ g.add_node("alice", label="Person", properties={"name": "Alice", "age": 13})
58
+ g.add_node("bob", label="Person", properties={"name": "Bob", "age": 14})
59
+ g.add_node("charlie", label="Person", properties={"name": "Charlie", "age": 13})
60
+
61
+ g.add_edge("e1", "alice", "bob", rel_type="KNOWS")
62
+ g.add_edge("e2", "bob", "charlie", rel_type="KNOWS")
63
+
64
+ # Find Alice's friends
65
+ friends = g.get_neighbors("alice", direction="outgoing")
66
+ print([f.properties["name"] for f in friends]) # ['Bob']
67
+
68
+ # Traverse the whole network
69
+ visited = bfs(g, "alice")
70
+ print([n.properties["name"] for n in visited]) # ['Alice', 'Bob', 'Charlie']
71
+
72
+ # Find shortest path
73
+ path = shortest_path(g, "alice", "charlie")
74
+ print([n.properties["name"] for n in path]) # ['Alice', 'Bob', 'Charlie']
75
+
76
+ # Query with Cypher-like syntax
77
+ results = execute_query(g, 'MATCH (n:Person) WHERE n.name = "Alice" RETURN n')
78
+ print(results[0].properties["name"]) # 'Alice'
79
+
80
+ # Pattern matching
81
+ teenagers = find_nodes(g, label="Person")
82
+ print(len(teenagers)) # 3
83
+ ```
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ # Clone and install
89
+ git clone https://github.com/robcox2910/pygraphdb.git
90
+ cd pygraphdb
91
+ uv sync --all-extras
92
+
93
+ # Run tests
94
+ uv run pytest
95
+
96
+ # Lint & type check
97
+ uv run ruff check src/ tests/
98
+ uv run pyright src tests
99
+ ```
100
+
101
+ ## Releasing
102
+
103
+ Other projects in the series ([PyStack](https://github.com/robcox2910/pystack)) use a pinned release of this one,
104
+ not whatever is on `main`. To publish a change for them:
105
+
106
+ 1. Merge the change to `main`.
107
+ 2. Tag it: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`.
108
+ A change to the public API bumps the minor version (`v0.1.0` → `v0.2.0`);
109
+ anything else bumps the patch (`v0.1.0` → `v0.1.1`).
110
+ 3. In each project that uses it, edit the tag in `[tool.uv.sources]`, run
111
+ `uv lock --upgrade-package <name>`, and open a pull request so CI checks
112
+ the pair together.
113
+
114
+ <!-- series:start -->
115
+ ## Related Projects
116
+
117
+ This project is one of a series that builds real systems "from scratch in Python" to teach how they work. See the [series home](https://robcox2910.github.io/) for the full story.
118
+
119
+ | Project | What it teaches | Repository | Docs |
120
+ | --- | --- | --- | --- |
121
+ | PyOS | Operating systems | [py-os](https://github.com/robcox2910/py-os) | [Docs](https://robcox2910.github.io/py-os/) |
122
+ | Pebble | Compilers and programming languages | [pebble-lang](https://github.com/robcox2910/pebble-lang) | [Docs](https://robcox2910.github.io/pebble-lang/) |
123
+ | PyDB | Relational databases | [pydb](https://github.com/robcox2910/pydb) | [Docs](https://robcox2910.github.io/pydb/) |
124
+ | PyKV | Key-value stores | [pykv](https://github.com/robcox2910/pykv) | [Docs](https://robcox2910.github.io/pykv/) |
125
+ | PyDocDB | Document databases | [pydoc-db](https://github.com/robcox2910/pydoc-db) | [Docs](https://robcox2910.github.io/pydoc-db/) |
126
+ | **PyGraphDB** | **Graph databases** | [pygraphdb](https://github.com/robcox2910/pygraphdb) | [Docs](https://robcox2910.github.io/pygraphdb/) |
127
+ | PyTSDB | Time-series databases | [pytsdb](https://github.com/robcox2910/pytsdb) | [Docs](https://robcox2910.github.io/pytsdb/) |
128
+ | PyVecDB | Vector databases | [pyvecdb](https://github.com/robcox2910/pyvecdb) | [Docs](https://robcox2910.github.io/pyvecdb/) |
129
+ | PyColDB | Column-family databases | [pycoldb](https://github.com/robcox2910/pycoldb) | [Docs](https://robcox2910.github.io/pycoldb/) |
130
+ | PyCrypt | Cryptography | [pycrypt](https://github.com/robcox2910/pycrypt) | [Docs](https://robcox2910.github.io/pycrypt/) |
131
+ | PyNet | Computer networking | [pynet](https://github.com/robcox2910/pynet) | [Docs](https://robcox2910.github.io/pynet/) |
132
+ | PyWeb | HTTP web servers | [pyweb](https://github.com/robcox2910/pyweb) | [Docs](https://robcox2910.github.io/pyweb/) |
133
+ | PyGit | Version control | [pygit](https://github.com/robcox2910/pygit) | [Docs](https://robcox2910.github.io/pygit/) |
134
+ | PySearch | Search engines | [pysearch](https://github.com/robcox2910/pysearch) | [Docs](https://robcox2910.github.io/pysearch/) |
135
+ | PyMQ | Message queues | [pymq](https://github.com/robcox2910/pymq) | [Docs](https://robcox2910.github.io/pymq/) |
136
+ | PyLLM | Language models (the brain) | [pyllm](https://github.com/robcox2910/pyllm) | [Docs](https://robcox2910.github.io/pyllm/) |
137
+ | PyScheduler | Job scheduling | [pyscheduler](https://github.com/robcox2910/pyscheduler) | [Docs](https://robcox2910.github.io/pyscheduler/) |
138
+ | PyStack | Full-stack integration | [pystack](https://github.com/robcox2910/pystack) | [Docs](https://robcox2910.github.io/pystack/) |
139
+ <!-- series:end -->
140
+
141
+ <!-- docs:start -->
142
+ ## Documentation
143
+
144
+ Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pygraphdb/
145
+ <!-- docs:end -->
@@ -0,0 +1,113 @@
1
+ # PyGraphDB
2
+
3
+ An educational graph database library built from scratch in Python. Part of the **PyLearn** educational platform for kids aged 12+.
4
+
5
+ A graph database stores data as **nodes** (entities) and **edges** (relationships) -- like a social network map where people are dots and friendships are lines connecting them.
6
+
7
+ ## Features
8
+
9
+ - **Nodes & Edges** -- Create entities with labels and properties, connect them with typed relationships
10
+ - **CRUD Operations** -- Add, get, update, and delete nodes and edges
11
+ - **Graph Traversal** -- Explore your graph with BFS (breadth-first) and DFS (depth-first) algorithms
12
+ - **Shortest Path** -- Find the quickest route between two nodes (like GPS directions)
13
+ - **Pattern Matching** -- Search for nodes and edges by label, type, or properties
14
+ - **Cypher-like Queries** -- Ask questions with a simple query language inspired by Neo4j's Cypher
15
+ - **Persistence** -- Save and load your graph to/from JSON files
16
+ - **Neighbor & Degree** -- Find connected nodes and count connections
17
+
18
+ ## Quick Start
19
+
20
+ ```python
21
+ from pygraphdb import Graph, bfs, shortest_path, execute_query, find_nodes
22
+
23
+ # Create a social network
24
+ g = Graph()
25
+ g.add_node("alice", label="Person", properties={"name": "Alice", "age": 13})
26
+ g.add_node("bob", label="Person", properties={"name": "Bob", "age": 14})
27
+ g.add_node("charlie", label="Person", properties={"name": "Charlie", "age": 13})
28
+
29
+ g.add_edge("e1", "alice", "bob", rel_type="KNOWS")
30
+ g.add_edge("e2", "bob", "charlie", rel_type="KNOWS")
31
+
32
+ # Find Alice's friends
33
+ friends = g.get_neighbors("alice", direction="outgoing")
34
+ print([f.properties["name"] for f in friends]) # ['Bob']
35
+
36
+ # Traverse the whole network
37
+ visited = bfs(g, "alice")
38
+ print([n.properties["name"] for n in visited]) # ['Alice', 'Bob', 'Charlie']
39
+
40
+ # Find shortest path
41
+ path = shortest_path(g, "alice", "charlie")
42
+ print([n.properties["name"] for n in path]) # ['Alice', 'Bob', 'Charlie']
43
+
44
+ # Query with Cypher-like syntax
45
+ results = execute_query(g, 'MATCH (n:Person) WHERE n.name = "Alice" RETURN n')
46
+ print(results[0].properties["name"]) # 'Alice'
47
+
48
+ # Pattern matching
49
+ teenagers = find_nodes(g, label="Person")
50
+ print(len(teenagers)) # 3
51
+ ```
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ # Clone and install
57
+ git clone https://github.com/robcox2910/pygraphdb.git
58
+ cd pygraphdb
59
+ uv sync --all-extras
60
+
61
+ # Run tests
62
+ uv run pytest
63
+
64
+ # Lint & type check
65
+ uv run ruff check src/ tests/
66
+ uv run pyright src tests
67
+ ```
68
+
69
+ ## Releasing
70
+
71
+ Other projects in the series ([PyStack](https://github.com/robcox2910/pystack)) use a pinned release of this one,
72
+ not whatever is on `main`. To publish a change for them:
73
+
74
+ 1. Merge the change to `main`.
75
+ 2. Tag it: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`.
76
+ A change to the public API bumps the minor version (`v0.1.0` → `v0.2.0`);
77
+ anything else bumps the patch (`v0.1.0` → `v0.1.1`).
78
+ 3. In each project that uses it, edit the tag in `[tool.uv.sources]`, run
79
+ `uv lock --upgrade-package <name>`, and open a pull request so CI checks
80
+ the pair together.
81
+
82
+ <!-- series:start -->
83
+ ## Related Projects
84
+
85
+ This project is one of a series that builds real systems "from scratch in Python" to teach how they work. See the [series home](https://robcox2910.github.io/) for the full story.
86
+
87
+ | Project | What it teaches | Repository | Docs |
88
+ | --- | --- | --- | --- |
89
+ | PyOS | Operating systems | [py-os](https://github.com/robcox2910/py-os) | [Docs](https://robcox2910.github.io/py-os/) |
90
+ | Pebble | Compilers and programming languages | [pebble-lang](https://github.com/robcox2910/pebble-lang) | [Docs](https://robcox2910.github.io/pebble-lang/) |
91
+ | PyDB | Relational databases | [pydb](https://github.com/robcox2910/pydb) | [Docs](https://robcox2910.github.io/pydb/) |
92
+ | PyKV | Key-value stores | [pykv](https://github.com/robcox2910/pykv) | [Docs](https://robcox2910.github.io/pykv/) |
93
+ | PyDocDB | Document databases | [pydoc-db](https://github.com/robcox2910/pydoc-db) | [Docs](https://robcox2910.github.io/pydoc-db/) |
94
+ | **PyGraphDB** | **Graph databases** | [pygraphdb](https://github.com/robcox2910/pygraphdb) | [Docs](https://robcox2910.github.io/pygraphdb/) |
95
+ | PyTSDB | Time-series databases | [pytsdb](https://github.com/robcox2910/pytsdb) | [Docs](https://robcox2910.github.io/pytsdb/) |
96
+ | PyVecDB | Vector databases | [pyvecdb](https://github.com/robcox2910/pyvecdb) | [Docs](https://robcox2910.github.io/pyvecdb/) |
97
+ | PyColDB | Column-family databases | [pycoldb](https://github.com/robcox2910/pycoldb) | [Docs](https://robcox2910.github.io/pycoldb/) |
98
+ | PyCrypt | Cryptography | [pycrypt](https://github.com/robcox2910/pycrypt) | [Docs](https://robcox2910.github.io/pycrypt/) |
99
+ | PyNet | Computer networking | [pynet](https://github.com/robcox2910/pynet) | [Docs](https://robcox2910.github.io/pynet/) |
100
+ | PyWeb | HTTP web servers | [pyweb](https://github.com/robcox2910/pyweb) | [Docs](https://robcox2910.github.io/pyweb/) |
101
+ | PyGit | Version control | [pygit](https://github.com/robcox2910/pygit) | [Docs](https://robcox2910.github.io/pygit/) |
102
+ | PySearch | Search engines | [pysearch](https://github.com/robcox2910/pysearch) | [Docs](https://robcox2910.github.io/pysearch/) |
103
+ | PyMQ | Message queues | [pymq](https://github.com/robcox2910/pymq) | [Docs](https://robcox2910.github.io/pymq/) |
104
+ | PyLLM | Language models (the brain) | [pyllm](https://github.com/robcox2910/pyllm) | [Docs](https://robcox2910.github.io/pyllm/) |
105
+ | PyScheduler | Job scheduling | [pyscheduler](https://github.com/robcox2910/pyscheduler) | [Docs](https://robcox2910.github.io/pyscheduler/) |
106
+ | PyStack | Full-stack integration | [pystack](https://github.com/robcox2910/pystack) | [Docs](https://robcox2910.github.io/pystack/) |
107
+ <!-- series:end -->
108
+
109
+ <!-- docs:start -->
110
+ ## Documentation
111
+
112
+ Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pygraphdb/
113
+ <!-- docs:end -->
@@ -0,0 +1,21 @@
1
+ """Shared Hypothesis profiles for the series.
2
+
3
+ Two profiles are registered, and ``ci`` is loaded by default:
4
+
5
+ - ``ci`` runs 100 examples with no deadline, a fixed seed and no example
6
+ database, so the same code gives the same result on every machine and a
7
+ green build can be trusted.
8
+ - ``thorough`` runs 2000 examples with a random seed, to hunt for the rare
9
+ counterexample before a release.
10
+
11
+ Run the long one with ``uv run pytest --hypothesis-profile=thorough``.
12
+
13
+ This file is owned by ``series sync``. Edit it in the series home repo
14
+ (robcox2910.github.io), not here, or the next sync will put it back.
15
+ """
16
+
17
+ from hypothesis import settings
18
+
19
+ settings.register_profile("ci", max_examples=100, deadline=None, derandomize=True, database=None)
20
+ settings.register_profile("thorough", max_examples=2000, deadline=None, derandomize=False)
21
+ settings.load_profile("ci")
@@ -0,0 +1,52 @@
1
+ # Edges -- Friendships and Handshakes
2
+
3
+ ## The Analogy
4
+
5
+ At our party, people don't just stand around alone -- they interact! When Alice shakes hands with Bob, that handshake is an **edge**. It connects two people and describes their relationship.
6
+
7
+ An edge has:
8
+ - A **direction**: Alice shook Bob's hand (Alice -> Bob). This is a *directed* edge, like a one-way street.
9
+ - A **type**: What kind of relationship? "FRIENDS_WITH", "CLASSMATES", "SIBLINGS"
10
+ - **Properties**: Extra details -- "became friends in 2023", "best friends" vs "acquaintances"
11
+
12
+ ```python
13
+ from pygraphdb import Graph
14
+
15
+ g = Graph()
16
+ g.add_node("alice", label="Student", properties={"name": "Alice"})
17
+ g.add_node("bob", label="Student", properties={"name": "Bob"})
18
+
19
+ # The handshake!
20
+ g.add_edge("e1", "alice", "bob", rel_type="FRIENDS_WITH", properties={"since": 2023})
21
+ ```
22
+
23
+ ## Direction Matters
24
+
25
+ Our edges are **directed** -- like arrows, not lines. "Alice FOLLOWS Bob" doesn't mean "Bob FOLLOWS Alice." If you want a mutual friendship, you need two edges:
26
+
27
+ ```python
28
+ g.add_edge("e1", "alice", "bob", rel_type="FRIENDS_WITH")
29
+ g.add_edge("e2", "bob", "alice", rel_type="FRIENDS_WITH")
30
+ ```
31
+
32
+ ## Multiple Relationships
33
+
34
+ Two nodes can have multiple edges between them! Alice might KNOW Bob (they've met) AND WORK_WITH Bob (they're lab partners). Each relationship is a separate edge with its own type and properties.
35
+
36
+ ## Every Node Keeps Its Own Address Book
37
+
38
+ How do you find someone's friends? The slow way is to read *every*
39
+ friendship in the whole school and pick out the ones that mention them.
40
+ That gets slower and slower as the school grows.
41
+
42
+ PyGraphDB does it the fast way: every node keeps its own little **address
43
+ book** -- one list of the edges going out from it and one list of the edges
44
+ coming in. Asking "who are Alice's friends?" just opens Alice's address book.
45
+ It doesn't matter if the school has ten friendships or ten million; it's
46
+ just as quick.
47
+
48
+ Real graph databases (like Neo4j) call this **index-free adjacency**, and
49
+ it's the whole reason they're good at following connections. Every time an
50
+ edge is added or removed, both end nodes' address books are updated, so they
51
+ never get out of step. Neighbours come back in the order their edges were
52
+ added, so you get the same answer every time.
@@ -0,0 +1,52 @@
1
+ # Pattern Matching -- Finding Friends Who Like Pizza
2
+
3
+ ## The Analogy
4
+
5
+ Imagine you're at a huge party with 200 people. You want to find everyone who:
6
+ - Is a Student (not a Teacher or Parent)
7
+ - Likes pizza
8
+ - Is over 12 years old
9
+
10
+ You could walk up to every single person and ask... or you could use **pattern matching** to filter everyone instantly!
11
+
12
+ ## How It Works
13
+
14
+ Pattern matching lets you search through your graph for nodes or edges that match specific criteria:
15
+
16
+ ```python
17
+ from pygraphdb import Graph, find_nodes, find_edges
18
+
19
+ g = Graph()
20
+ g.add_node("1", label="Person", properties={"name": "Alice", "age": 13, "likes": "pizza"})
21
+ g.add_node("2", label="Person", properties={"name": "Bob", "age": 14, "likes": "tacos"})
22
+ g.add_node("3", label="Person", properties={"name": "Charlie", "age": 13, "likes": "pizza"})
23
+
24
+ # Find all pizza lovers
25
+ pizza_fans = find_nodes(g, properties={"likes": "pizza"})
26
+ # Result: Alice and Charlie
27
+
28
+ # Find all people (by label)
29
+ people = find_nodes(g, label="Person")
30
+ # Result: Alice, Bob, and Charlie
31
+ ```
32
+
33
+ ## Matching Edges Too
34
+
35
+ You can also search for specific relationships:
36
+
37
+ ```python
38
+ # Find all FRIENDS_WITH relationships
39
+ friendships = find_edges(g, rel_type="FRIENDS_WITH")
40
+
41
+ # Find all edges from Alice
42
+ alice_connections = find_edges(g, source_id="1")
43
+ ```
44
+
45
+ ## Combining Criteria
46
+
47
+ The real power comes from combining label AND property filters:
48
+
49
+ ```python
50
+ # Find 13-year-old people who like pizza
51
+ young_pizza_fans = find_nodes(g, label="Person", properties={"age": 13, "likes": "pizza"})
52
+ ```
@@ -0,0 +1,42 @@
1
+ # Nodes -- People at a Party
2
+
3
+ ## The Analogy
4
+
5
+ Imagine you're at a big party. Every person there is a **node**. Each person has:
6
+ - A **wristband number** (their unique ID -- no two people share one)
7
+ - A **group name tag** that says what kind of person they are: "Student", "Teacher", "Parent"
8
+ - A **fact sheet** with details about them: their name, age, favorite food, etc.
9
+
10
+ In our graph database, a Node works exactly the same way:
11
+
12
+ ```python
13
+ from pygraphdb import Node
14
+
15
+ # Create a person at the party
16
+ alice = Node(
17
+ node_id="alice-01", # Wristband number
18
+ label="Student", # Group name tag
19
+ properties={ # Fact sheet
20
+ "name": "Alice",
21
+ "age": 13,
22
+ "favorite_food": "pizza"
23
+ }
24
+ )
25
+ ```
26
+
27
+ ## Why Nodes Need Unique IDs
28
+
29
+ If two people at the party both had wristband #42, how would you tell them apart when someone says "bring me person #42"? You couldn't! That's why every node needs a unique ID.
30
+
31
+ ## Labels are Like Categories
32
+
33
+ Labels help you organize nodes into groups. At a school, you might have:
34
+ - `Student` nodes
35
+ - `Teacher` nodes
36
+ - `Classroom` nodes
37
+
38
+ This makes it easy to ask questions like "show me all the Students."
39
+
40
+ ## Properties are Flexible
41
+
42
+ Unlike a rigid spreadsheet where every row must have the same columns, node properties are flexible. One Student might have a `gpa` property while another doesn't -- and that's perfectly fine!