fromscratch-pydocdb 0.3.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 (51) hide show
  1. fromscratch_pydocdb-0.3.0/.github/workflows/ci.yml +14 -0
  2. fromscratch_pydocdb-0.3.0/.github/workflows/docs.yml +16 -0
  3. fromscratch_pydocdb-0.3.0/.github/workflows/release.yml +58 -0
  4. fromscratch_pydocdb-0.3.0/.gitignore +11 -0
  5. fromscratch_pydocdb-0.3.0/.pre-commit-config.yaml +56 -0
  6. fromscratch_pydocdb-0.3.0/CLAUDE.md +52 -0
  7. fromscratch_pydocdb-0.3.0/LICENSE +21 -0
  8. fromscratch_pydocdb-0.3.0/PKG-INFO +146 -0
  9. fromscratch_pydocdb-0.3.0/README.md +114 -0
  10. fromscratch_pydocdb-0.3.0/conftest.py +21 -0
  11. fromscratch_pydocdb-0.3.0/docs/concepts/aggregation.md +91 -0
  12. fromscratch_pydocdb-0.3.0/docs/concepts/collections.md +62 -0
  13. fromscratch_pydocdb-0.3.0/docs/concepts/documents.md +45 -0
  14. fromscratch_pydocdb-0.3.0/docs/concepts/indexing.md +58 -0
  15. fromscratch_pydocdb-0.3.0/docs/concepts/nested.md +60 -0
  16. fromscratch_pydocdb-0.3.0/docs/concepts/persistence.md +56 -0
  17. fromscratch_pydocdb-0.3.0/docs/concepts/projection.md +40 -0
  18. fromscratch_pydocdb-0.3.0/docs/concepts/queries.md +50 -0
  19. fromscratch_pydocdb-0.3.0/docs/index.md +44 -0
  20. fromscratch_pydocdb-0.3.0/docs/property-testing.md +381 -0
  21. fromscratch_pydocdb-0.3.0/mkdocs.yml +83 -0
  22. fromscratch_pydocdb-0.3.0/overrides/main.html +7 -0
  23. fromscratch_pydocdb-0.3.0/pyproject.toml +156 -0
  24. fromscratch_pydocdb-0.3.0/src/pydocdb/__init__.py +48 -0
  25. fromscratch_pydocdb-0.3.0/src/pydocdb/_version.py +24 -0
  26. fromscratch_pydocdb-0.3.0/src/pydocdb/aggregation.py +377 -0
  27. fromscratch_pydocdb-0.3.0/src/pydocdb/atomic.py +66 -0
  28. fromscratch_pydocdb-0.3.0/src/pydocdb/collection.py +467 -0
  29. fromscratch_pydocdb-0.3.0/src/pydocdb/document.py +145 -0
  30. fromscratch_pydocdb-0.3.0/src/pydocdb/errors.py +58 -0
  31. fromscratch_pydocdb-0.3.0/src/pydocdb/indexing.py +126 -0
  32. fromscratch_pydocdb-0.3.0/src/pydocdb/operators.py +187 -0
  33. fromscratch_pydocdb-0.3.0/src/pydocdb/persistence.py +125 -0
  34. fromscratch_pydocdb-0.3.0/src/pydocdb/projection.py +101 -0
  35. fromscratch_pydocdb-0.3.0/src/pydocdb/py.typed +0 -0
  36. fromscratch_pydocdb-0.3.0/src/pydocdb/query.py +188 -0
  37. fromscratch_pydocdb-0.3.0/tests/__init__.py +1 -0
  38. fromscratch_pydocdb-0.3.0/tests/strategies.py +82 -0
  39. fromscratch_pydocdb-0.3.0/tests/test_aggregation.py +231 -0
  40. fromscratch_pydocdb-0.3.0/tests/test_atomic.py +75 -0
  41. fromscratch_pydocdb-0.3.0/tests/test_collection.py +357 -0
  42. fromscratch_pydocdb-0.3.0/tests/test_document.py +127 -0
  43. fromscratch_pydocdb-0.3.0/tests/test_errors.py +46 -0
  44. fromscratch_pydocdb-0.3.0/tests/test_indexing.py +267 -0
  45. fromscratch_pydocdb-0.3.0/tests/test_model.py +242 -0
  46. fromscratch_pydocdb-0.3.0/tests/test_operators.py +156 -0
  47. fromscratch_pydocdb-0.3.0/tests/test_persistence.py +172 -0
  48. fromscratch_pydocdb-0.3.0/tests/test_projection.py +93 -0
  49. fromscratch_pydocdb-0.3.0/tests/test_properties.py +211 -0
  50. fromscratch_pydocdb-0.3.0/tests/test_query.py +116 -0
  51. fromscratch_pydocdb-0.3.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-pydocdb/
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,146 @@
1
+ Metadata-Version: 2.5
2
+ Name: fromscratch-pydocdb
3
+ Version: 0.3.0
4
+ Summary: An educational document store library built from scratch in Python
5
+ Project-URL: Homepage, https://github.com/robcox2910/pydoc-db
6
+ Project-URL: Repository, https://github.com/robcox2910/pydoc-db
7
+ Project-URL: Issues, https://github.com/robcox2910/pydoc-db/issues
8
+ Project-URL: Documentation, https://robcox2910.github.io/pydoc-db/
9
+ Author: Rob Cox
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: database,document,education,nosql,store
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
+ # PyDocDB
34
+
35
+ An educational document store (like MongoDB) built from scratch in Python. Part of the **PyLearn** platform for kids aged 12+.
36
+
37
+ PyDocDB stores JSON-like documents without requiring a fixed schema -- each document can have different fields, just like how every index card in a filing cabinet can have different information on it.
38
+
39
+ ## Features
40
+
41
+ - **Documents** -- Dict-like objects with auto-generated unique IDs
42
+ - **Collections** -- Named groups of documents with full CRUD operations
43
+ - **Query Language** -- Filter with operators: `$eq`, `$gt`, `$lt`, `$gte`, `$lte`, `$ne`, `$in`, `$exists`, `$and`, `$or`, `$not`
44
+ - **Nested Field Access** -- Query nested fields with dot notation (e.g., `"address.city"`)
45
+ - **Projection** -- Return only specific fields from documents
46
+ - **Indexing** -- Create indexes on fields for faster lookups
47
+ - **Persistence** -- Save/load collections to JSON files on disk
48
+ - **Aggregation** -- Pipeline with `$match`, `$group`, `$sort`, `$limit`, `$count`
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from pydocdb import Collection, save_collection, load_collection, aggregate
54
+
55
+ # Create a collection (like labeling a new folder in a filing cabinet)
56
+ students = Collection("students")
57
+
58
+ # Insert documents (like filing index cards)
59
+ students.insert_one({"name": "Alice", "age": 14, "grade": "9th"})
60
+ students.insert_one({"name": "Bob", "age": 13, "hobby": "chess"})
61
+ students.insert_one({"name": "Charlie", "age": 15, "grade": "10th"})
62
+
63
+ # Find documents with queries
64
+ teenagers = students.find({"age": {"$gte": 13}})
65
+ alice = students.find_one({"name": "Alice"})
66
+
67
+ # Update and delete
68
+ students.update_one({"name": "Alice"}, {"age": 15})
69
+ students.delete_one({"name": "Bob"})
70
+
71
+ # Use projection to get only specific fields
72
+ names = students.find({}, projection={"name": 1, "_id": 0})
73
+
74
+ # Aggregate data
75
+ result = aggregate(students.to_list(), [
76
+ {"$group": {"_id": "$grade", "count": {"$sum": 1}}},
77
+ {"$sort": {"count": -1}},
78
+ ])
79
+
80
+ # Save to disk and load later
81
+ save_collection(students, "students.json")
82
+ loaded = load_collection("students.json")
83
+ ```
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ uv add pydocdb
89
+ ```
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ git clone https://github.com/robcox2910/pydoc-db.git
95
+ cd pydoc-db
96
+ uv sync --all-extras
97
+ uv run pytest
98
+ uv run ruff check src/ tests/
99
+ uv run pyright src tests
100
+ ```
101
+
102
+ ## Releasing
103
+
104
+ Other projects in the series ([PyStack](https://github.com/robcox2910/pystack)) use a pinned release of this one,
105
+ not whatever is on `main`. To publish a change for them:
106
+
107
+ 1. Merge the change to `main`.
108
+ 2. Tag it: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`.
109
+ A change to the public API bumps the minor version (`v0.1.0` → `v0.2.0`);
110
+ anything else bumps the patch (`v0.1.0` → `v0.1.1`).
111
+ 3. In each project that uses it, edit the tag in `[tool.uv.sources]`, run
112
+ `uv lock --upgrade-package <name>`, and open a pull request so CI checks
113
+ the pair together.
114
+
115
+ <!-- series:start -->
116
+ ## Related Projects
117
+
118
+ 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.
119
+
120
+ | Project | What it teaches | Repository | Docs |
121
+ | --- | --- | --- | --- |
122
+ | PyOS | Operating systems | [py-os](https://github.com/robcox2910/py-os) | [Docs](https://robcox2910.github.io/py-os/) |
123
+ | Pebble | Compilers and programming languages | [pebble-lang](https://github.com/robcox2910/pebble-lang) | [Docs](https://robcox2910.github.io/pebble-lang/) |
124
+ | PyDB | Relational databases | [pydb](https://github.com/robcox2910/pydb) | [Docs](https://robcox2910.github.io/pydb/) |
125
+ | PyKV | Key-value stores | [pykv](https://github.com/robcox2910/pykv) | [Docs](https://robcox2910.github.io/pykv/) |
126
+ | **PyDocDB** | **Document databases** | [pydoc-db](https://github.com/robcox2910/pydoc-db) | [Docs](https://robcox2910.github.io/pydoc-db/) |
127
+ | PyGraphDB | Graph databases | [pygraphdb](https://github.com/robcox2910/pygraphdb) | [Docs](https://robcox2910.github.io/pygraphdb/) |
128
+ | PyTSDB | Time-series databases | [pytsdb](https://github.com/robcox2910/pytsdb) | [Docs](https://robcox2910.github.io/pytsdb/) |
129
+ | PyVecDB | Vector databases | [pyvecdb](https://github.com/robcox2910/pyvecdb) | [Docs](https://robcox2910.github.io/pyvecdb/) |
130
+ | PyColDB | Column-family databases | [pycoldb](https://github.com/robcox2910/pycoldb) | [Docs](https://robcox2910.github.io/pycoldb/) |
131
+ | PyCrypt | Cryptography | [pycrypt](https://github.com/robcox2910/pycrypt) | [Docs](https://robcox2910.github.io/pycrypt/) |
132
+ | PyNet | Computer networking | [pynet](https://github.com/robcox2910/pynet) | [Docs](https://robcox2910.github.io/pynet/) |
133
+ | PyWeb | HTTP web servers | [pyweb](https://github.com/robcox2910/pyweb) | [Docs](https://robcox2910.github.io/pyweb/) |
134
+ | PyGit | Version control | [pygit](https://github.com/robcox2910/pygit) | [Docs](https://robcox2910.github.io/pygit/) |
135
+ | PySearch | Search engines | [pysearch](https://github.com/robcox2910/pysearch) | [Docs](https://robcox2910.github.io/pysearch/) |
136
+ | PyMQ | Message queues | [pymq](https://github.com/robcox2910/pymq) | [Docs](https://robcox2910.github.io/pymq/) |
137
+ | PyLLM | Language models (the brain) | [pyllm](https://github.com/robcox2910/pyllm) | [Docs](https://robcox2910.github.io/pyllm/) |
138
+ | PyScheduler | Job scheduling | [pyscheduler](https://github.com/robcox2910/pyscheduler) | [Docs](https://robcox2910.github.io/pyscheduler/) |
139
+ | PyStack | Full-stack integration | [pystack](https://github.com/robcox2910/pystack) | [Docs](https://robcox2910.github.io/pystack/) |
140
+ <!-- series:end -->
141
+
142
+ <!-- docs:start -->
143
+ ## Documentation
144
+
145
+ Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pydoc-db/
146
+ <!-- docs:end -->
@@ -0,0 +1,114 @@
1
+ # PyDocDB
2
+
3
+ An educational document store (like MongoDB) built from scratch in Python. Part of the **PyLearn** platform for kids aged 12+.
4
+
5
+ PyDocDB stores JSON-like documents without requiring a fixed schema -- each document can have different fields, just like how every index card in a filing cabinet can have different information on it.
6
+
7
+ ## Features
8
+
9
+ - **Documents** -- Dict-like objects with auto-generated unique IDs
10
+ - **Collections** -- Named groups of documents with full CRUD operations
11
+ - **Query Language** -- Filter with operators: `$eq`, `$gt`, `$lt`, `$gte`, `$lte`, `$ne`, `$in`, `$exists`, `$and`, `$or`, `$not`
12
+ - **Nested Field Access** -- Query nested fields with dot notation (e.g., `"address.city"`)
13
+ - **Projection** -- Return only specific fields from documents
14
+ - **Indexing** -- Create indexes on fields for faster lookups
15
+ - **Persistence** -- Save/load collections to JSON files on disk
16
+ - **Aggregation** -- Pipeline with `$match`, `$group`, `$sort`, `$limit`, `$count`
17
+
18
+ ## Quick Start
19
+
20
+ ```python
21
+ from pydocdb import Collection, save_collection, load_collection, aggregate
22
+
23
+ # Create a collection (like labeling a new folder in a filing cabinet)
24
+ students = Collection("students")
25
+
26
+ # Insert documents (like filing index cards)
27
+ students.insert_one({"name": "Alice", "age": 14, "grade": "9th"})
28
+ students.insert_one({"name": "Bob", "age": 13, "hobby": "chess"})
29
+ students.insert_one({"name": "Charlie", "age": 15, "grade": "10th"})
30
+
31
+ # Find documents with queries
32
+ teenagers = students.find({"age": {"$gte": 13}})
33
+ alice = students.find_one({"name": "Alice"})
34
+
35
+ # Update and delete
36
+ students.update_one({"name": "Alice"}, {"age": 15})
37
+ students.delete_one({"name": "Bob"})
38
+
39
+ # Use projection to get only specific fields
40
+ names = students.find({}, projection={"name": 1, "_id": 0})
41
+
42
+ # Aggregate data
43
+ result = aggregate(students.to_list(), [
44
+ {"$group": {"_id": "$grade", "count": {"$sum": 1}}},
45
+ {"$sort": {"count": -1}},
46
+ ])
47
+
48
+ # Save to disk and load later
49
+ save_collection(students, "students.json")
50
+ loaded = load_collection("students.json")
51
+ ```
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ uv add pydocdb
57
+ ```
58
+
59
+ ## Development
60
+
61
+ ```bash
62
+ git clone https://github.com/robcox2910/pydoc-db.git
63
+ cd pydoc-db
64
+ uv sync --all-extras
65
+ uv run pytest
66
+ uv run ruff check src/ tests/
67
+ uv run pyright src tests
68
+ ```
69
+
70
+ ## Releasing
71
+
72
+ Other projects in the series ([PyStack](https://github.com/robcox2910/pystack)) use a pinned release of this one,
73
+ not whatever is on `main`. To publish a change for them:
74
+
75
+ 1. Merge the change to `main`.
76
+ 2. Tag it: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`.
77
+ A change to the public API bumps the minor version (`v0.1.0` → `v0.2.0`);
78
+ anything else bumps the patch (`v0.1.0` → `v0.1.1`).
79
+ 3. In each project that uses it, edit the tag in `[tool.uv.sources]`, run
80
+ `uv lock --upgrade-package <name>`, and open a pull request so CI checks
81
+ the pair together.
82
+
83
+ <!-- series:start -->
84
+ ## Related Projects
85
+
86
+ 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.
87
+
88
+ | Project | What it teaches | Repository | Docs |
89
+ | --- | --- | --- | --- |
90
+ | PyOS | Operating systems | [py-os](https://github.com/robcox2910/py-os) | [Docs](https://robcox2910.github.io/py-os/) |
91
+ | Pebble | Compilers and programming languages | [pebble-lang](https://github.com/robcox2910/pebble-lang) | [Docs](https://robcox2910.github.io/pebble-lang/) |
92
+ | PyDB | Relational databases | [pydb](https://github.com/robcox2910/pydb) | [Docs](https://robcox2910.github.io/pydb/) |
93
+ | PyKV | Key-value stores | [pykv](https://github.com/robcox2910/pykv) | [Docs](https://robcox2910.github.io/pykv/) |
94
+ | **PyDocDB** | **Document databases** | [pydoc-db](https://github.com/robcox2910/pydoc-db) | [Docs](https://robcox2910.github.io/pydoc-db/) |
95
+ | PyGraphDB | Graph databases | [pygraphdb](https://github.com/robcox2910/pygraphdb) | [Docs](https://robcox2910.github.io/pygraphdb/) |
96
+ | PyTSDB | Time-series databases | [pytsdb](https://github.com/robcox2910/pytsdb) | [Docs](https://robcox2910.github.io/pytsdb/) |
97
+ | PyVecDB | Vector databases | [pyvecdb](https://github.com/robcox2910/pyvecdb) | [Docs](https://robcox2910.github.io/pyvecdb/) |
98
+ | PyColDB | Column-family databases | [pycoldb](https://github.com/robcox2910/pycoldb) | [Docs](https://robcox2910.github.io/pycoldb/) |
99
+ | PyCrypt | Cryptography | [pycrypt](https://github.com/robcox2910/pycrypt) | [Docs](https://robcox2910.github.io/pycrypt/) |
100
+ | PyNet | Computer networking | [pynet](https://github.com/robcox2910/pynet) | [Docs](https://robcox2910.github.io/pynet/) |
101
+ | PyWeb | HTTP web servers | [pyweb](https://github.com/robcox2910/pyweb) | [Docs](https://robcox2910.github.io/pyweb/) |
102
+ | PyGit | Version control | [pygit](https://github.com/robcox2910/pygit) | [Docs](https://robcox2910.github.io/pygit/) |
103
+ | PySearch | Search engines | [pysearch](https://github.com/robcox2910/pysearch) | [Docs](https://robcox2910.github.io/pysearch/) |
104
+ | PyMQ | Message queues | [pymq](https://github.com/robcox2910/pymq) | [Docs](https://robcox2910.github.io/pymq/) |
105
+ | PyLLM | Language models (the brain) | [pyllm](https://github.com/robcox2910/pyllm) | [Docs](https://robcox2910.github.io/pyllm/) |
106
+ | PyScheduler | Job scheduling | [pyscheduler](https://github.com/robcox2910/pyscheduler) | [Docs](https://robcox2910.github.io/pyscheduler/) |
107
+ | PyStack | Full-stack integration | [pystack](https://github.com/robcox2910/pystack) | [Docs](https://robcox2910.github.io/pystack/) |
108
+ <!-- series:end -->
109
+
110
+ <!-- docs:start -->
111
+ ## Documentation
112
+
113
+ Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pydoc-db/
114
+ <!-- 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,91 @@
1
+ # Aggregation
2
+
3
+ ## The "Sorting and Counting Your Card Collection" Analogy
4
+
5
+ Imagine you have a huge pile of trading cards. You might want to:
6
+
7
+ 1. **Pick out** only the rare cards (`$match`)
8
+ 2. **Group** them by type -- Fire, Water, Grass (`$group`)
9
+ 3. **Sort** the groups by how many cards each has (`$sort`)
10
+ 4. **Look at only** the top 3 groups (`$limit`)
11
+ 5. **Count** how many groups there are (`$count`)
12
+
13
+ That's an aggregation pipeline! Each step transforms the data, and the output of one step becomes the input for the next.
14
+
15
+ ## Pipeline Stages
16
+
17
+ ### $match -- Filter Documents
18
+
19
+ ```python
20
+ from pydocdb import Collection, aggregate
21
+
22
+ products = Collection("products")
23
+ products.insert_many([
24
+ {"name": "Widget", "category": "A", "price": 10},
25
+ {"name": "Gadget", "category": "A", "price": 20},
26
+ {"name": "Doohickey", "category": "B", "price": 30},
27
+ ])
28
+
29
+ # Get only category A products
30
+ result = aggregate(products.to_list(), [
31
+ {"$match": {"category": "A"}}
32
+ ])
33
+ ```
34
+
35
+ ### $group -- Group and Summarize
36
+
37
+ ```python
38
+ # Group by category and sum prices
39
+ result = aggregate(products.to_list(), [
40
+ {"$group": {
41
+ "_id": "$category",
42
+ "total_price": {"$sum": "$price"},
43
+ "count": {"$sum": 1},
44
+ "avg_price": {"$avg": "$price"},
45
+ }}
46
+ ])
47
+ ```
48
+
49
+ Available accumulators: `$sum`, `$avg`, `$min`, `$max`
50
+
51
+ ### $sort -- Order Results
52
+
53
+ ```python
54
+ # Sort by price, highest first
55
+ result = aggregate(products.to_list(), [
56
+ {"$sort": {"price": -1}} # -1 = descending, 1 = ascending
57
+ ])
58
+ ```
59
+
60
+ ### $limit -- Take Only N Results
61
+
62
+ ```python
63
+ # Get the top 2 most expensive
64
+ result = aggregate(products.to_list(), [
65
+ {"$sort": {"price": -1}},
66
+ {"$limit": 2}
67
+ ])
68
+ ```
69
+
70
+ ### $count -- Count Documents
71
+
72
+ ```python
73
+ # Count how many products there are
74
+ result = aggregate(products.to_list(), [
75
+ {"$count": "total_products"}
76
+ ])
77
+ # [{"total_products": 3}]
78
+ ```
79
+
80
+ ## Multi-Stage Pipelines
81
+
82
+ The real power comes from chaining stages together:
83
+
84
+ ```python
85
+ result = aggregate(products.to_list(), [
86
+ {"$match": {"price": {"$gte": 15}}}, # Step 1: Filter expensive items
87
+ {"$group": {"_id": "$category", "total": {"$sum": "$price"}}}, # Step 2: Group by category
88
+ {"$sort": {"total": -1}}, # Step 3: Sort by total, highest first
89
+ {"$limit": 5}, # Step 4: Only top 5
90
+ ])
91
+ ```
@@ -0,0 +1,62 @@
1
+ # Collections
2
+
3
+ ## The Folder in a Filing Cabinet Analogy
4
+
5
+ A collection is like a **labeled folder** in a filing cabinet. The folder called "Students" holds all the student index cards. The folder called "Products" holds all the product cards. Each folder is independent -- adding a card to "Students" doesn't affect "Products."
6
+
7
+ ## CRUD Operations
8
+
9
+ CRUD stands for **C**reate, **R**ead, **U**pdate, **D**elete -- the four basic things you can do with data.
10
+
11
+ ### Create (Insert)
12
+
13
+ ```python
14
+ from pydocdb import Collection
15
+
16
+ students = Collection("students")
17
+
18
+ # Insert one document
19
+ alice = students.insert_one({"name": "Alice", "age": 14})
20
+
21
+ # Insert many at once
22
+ students.insert_many([
23
+ {"name": "Bob", "age": 13},
24
+ {"name": "Charlie", "age": 15},
25
+ ])
26
+ ```
27
+
28
+ ### Read (Find)
29
+
30
+ ```python
31
+ # Find all documents
32
+ all_students = students.find()
33
+
34
+ # Find with a query
35
+ teens = students.find({"age": {"$gte": 13}})
36
+
37
+ # Find just one
38
+ alice = students.find_one({"name": "Alice"})
39
+
40
+ # Find by ID
41
+ doc = students.find_by_id("some-id-here")
42
+ ```
43
+
44
+ ### Update
45
+
46
+ ```python
47
+ # Update one document
48
+ students.update_one({"name": "Alice"}, {"age": 15})
49
+
50
+ # Update many documents
51
+ students.update_many({"grade": "9th"}, {"year": 2024})
52
+ ```
53
+
54
+ ### Delete
55
+
56
+ ```python
57
+ # Delete one document
58
+ students.delete_one({"name": "Alice"})
59
+
60
+ # Delete many documents
61
+ students.delete_many({"graduated": True})
62
+ ```