From 5454ba21e6068083dbec0ffe2e892cf47a129969 Mon Sep 17 00:00:00 2001 From: Eugene&Monty <136487467+EugeneSukharev1988@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:56:00 -0500 Subject: [PATCH 1/3] init --- .github/workflows/.build_and_push.yml | 33 +++++++- README.md | 113 +++++++++++++++++++++++++- tests/test_schema_tools_helpers.py | 6 +- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/.github/workflows/.build_and_push.yml b/.github/workflows/.build_and_push.yml index 393efee..2bce551 100644 --- a/.github/workflows/.build_and_push.yml +++ b/.github/workflows/.build_and_push.yml @@ -9,9 +9,36 @@ on: - master release: types: [published] - + jobs: test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # setup.py declares python_requires='>=3.9'. + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package and dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Run tests + run: python -m unittest discover -s tests -v + + package: + name: Packaging dry run + needs: test runs-on: ubuntu-latest steps: - name: Checkout code @@ -36,7 +63,7 @@ jobs: CI_DEPLOY: false # Dry run, no upload build-and-deploy: - needs: test + needs: [test, package] runs-on: ubuntu-latest if: github.event_name == 'release' steps: @@ -59,4 +86,4 @@ jobs: ./build_and_push.sh env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - CI_DEPLOY: true \ No newline at end of file + CI_DEPLOY: true diff --git a/README.md b/README.md index b1396ae..9f1ea32 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ pip install montycat ```python import asyncio +from typing import Optional + from montycat import Engine, Keyspace, Schema # setup connection @@ -115,7 +117,7 @@ class SalesSchema(Schema): class ProductionSchema(Schema): items: list - work_order: str | None + work_order: Optional[str] # or `str | None` on Python 3.10+ async def main(): # create store and keyspaces using runtime migration @@ -208,12 +210,108 @@ matching_values = await Sales.semantic_search_get_values_where( # value hits: {"__key__", "__score__", "__value__"} ``` +## 📨 Response Shape + +Every call returns the same envelope, so there is one thing to check everywhere: + +```python +# {"status": True, "payload": , "error": None} +# {"status": False, "payload": None, "error": "Governance permission denied: ..."} + +res = await Sales.insert_value(sale) +if res["status"]: + print(res["payload"]) +``` + +`payload` is `None` for commands that only acknowledge, the new key for inserts, and a +list for lookups and semantic searches. **Keys are u128 and always arrive as strings** — +keep them that way; Python `int` will hold one, but round-tripping through JSON or a +float will not. Invalid arguments raise `ValueError` before anything touches the network; +server-side failures come back in `error` with `"status": False`. + +## 📡 Real-Time Subscriptions + +Subscribe to one key or to a whole keyspace and get pushed every change — the reactive +core behind live dashboards, async ETL, and event-driven services. + +```python +def on_change(event): + print("changed:", event) + +# Whole keyspace: omit both key and custom_key. +task, stop = await Sales.subscribe(callback=on_change) + +# Or watch a single key (custom_key is hashed for you). +# Passing key and custom_key together raises ValueError; omitting callback does too. +one_task, one_stop = await Sales.subscribe( + key="30442970696809394303186116932586352271", + callback=on_change, +) + +# Stop listening and let the task finish. +stop.set() +await task +``` + +`subscribe` returns `(task, stop_event)` — an `asyncio.Task` running the stream and an +`asyncio.Event` that ends it. Subscriptions use the **subscription port**, which defaults +to `port + 1` — that is the second port (`21211`) published in the Docker command above. +Override it with `subscription_port=` if your deployment maps it elsewhere. + +## 🔐 TLS + +Pass `tls=True` to negotiate an encrypted connection. It applies to commands and +subscriptions alike: + +```python +connection = Engine( + host="127.0.0.1", + port=21210, + username="USER", + password="12345", + store="Departments", + tls=True, +) +``` + +> **Note.** The client accepts self-signed certificates, which is convenient for local +> and internal deployments but means the server identity is not verified. Terminate TLS +> at a trusted proxy if you need certificate validation. + +## 👥 Owners & Access + +Governance policies below are written against *owners*, so create them first. A +superowner provisions an owner, then grants data access — optionally narrowed to +specific keyspaces: + +```python +from montycat import Permission + +await connection.create_owner("alice", "alice-password") + +await connection.grant_to("alice", Permission.READ) # whole store +await connection.grant_to("alice", Permission.WRITE, keyspaces=["Sales"]) # scoped + +await connection.list_owners() + +await connection.revoke_from("alice", Permission.WRITE, keyspaces=["Sales"]) +await connection.remove_owner("alice") +``` + +`Permission` is `READ`, `WRITE`, or `ALL`; plain strings work too and are normalized +(`" ALL "` → `all`), with an unknown token raising `ValueError`. `grant_to` and +`revoke_from` apply to the engine's `store`. This governs **data access**; to delegate +*administrative* capabilities such as provisioning keyspaces or managing schemas, see +[Data-mesh governance](#data-mesh-governance-for-shared-and-multi-tenant-deployments) at +the end of this document. + ## 🔗 Links - 🌐 **Website & Docs** — https://montygovernance.com - 📦 **PyPI** — https://pypi.org/project/montycat/ - 🐳 **Docker Hub** — https://hub.docker.com/r/montygovernance/montycat - 💻 **Source** — https://github.com/MontyGovernance/montycat_python +- 📝 **Changelog** — [CHANGELOG.md](CHANGELOG.md) ## ❓ FAQ @@ -231,9 +329,14 @@ operate the data products they own. - Grant, revoke, or explicitly deny keyspace provisioning/removal, schema, semantic, snapshot, and access-management capabilities. -- Inspect effective permissions and history, or preview a grant/revoke before applying it. +- Inspect effective permissions and policy history, or preview a grant/revoke before + applying it. - Validate, plan, apply, and export JSON or YAML policy manifests for repeatable infrastructure-as-code workflows. +- Constrain storage types for provisioning, removal, schema, access, and semantic + management. Snapshot management is always in-memory, so it takes no storage-type + qualifier. +- Constrain semantic models during keyspace provisioning and semantic management. For example, a superowner can separately constrain keyspace provisioning and semantic management within one store: @@ -253,5 +356,7 @@ await connection.policy_grant( await connection.policy_view(owner="alice", store="catalog") ``` -Superowners may also call `policy_validate`, `policy_plan`, `policy_apply`, and -`policy_export` with JSON or YAML policy documents. +Use `policy_explain` to inspect an authorization decision and `policy_history` to audit +changes. Superowners can manage policies directly with `policy_grant`, `policy_revoke`, +`policy_deny`, and `policy_remove_denial`, or use `policy_validate`, `policy_plan`, +`policy_apply`, and `policy_export` with JSON or YAML documents. diff --git a/tests/test_schema_tools_helpers.py b/tests/test_schema_tools_helpers.py index 21f04c3..046a534 100644 --- a/tests/test_schema_tools_helpers.py +++ b/tests/test_schema_tools_helpers.py @@ -1,4 +1,5 @@ import unittest +from typing import Optional import orjson @@ -42,7 +43,10 @@ class SchemaTests(unittest.TestCase): class Event(Schema): name: str count: int - note: str | None + # Optional[...] rather than PEP 604 `str | None`: annotations are + # evaluated at class-creation time and `|` on types needs Python 3.10, + # while setup.py declares support from 3.9. + note: Optional[str] class LinkedEvent(Schema): parent: Pointer From 24ef622ccf623fed49d27f0caa404c85d9d99de6 Mon Sep 17 00:00:00 2001 From: Eugene&Monty <136487467+EugeneSukharev1988@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:03:19 -0500 Subject: [PATCH 2/3] changelog --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ setup.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8802a47..a6bbcc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,40 @@ All notable changes to the Montycat Python client are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.1] - 2026-07-29 + +Documentation, tests, and CI only — no library code changed, so upgrading from +1.1.0 is optional. + +### Added + +- README sections for behavior that was previously undocumented: response shape + (`{"status", "payload", "error"}` and u128 keys arriving as strings), + real-time subscriptions returning `(task, stop_event)` on the `port + 1` + subscription port, TLS via `tls=True`, and owner/access management with + `create_owner`, `grant_to`, `revoke_from`, and `Permission`. +- CI now installs the package and runs the test suite on Python 3.9 through + 3.13. The `test` job previously ran only a packaging dry run, so no test had + ever executed in CI. +- Changelog link in the README. + +### Changed + +- The CI packaging dry run moved into its own `package` job, and the release job + now depends on both `test` and `package`. + +### Fixed + +- The test suite annotated a schema field as `str | None`, which is PEP 604 + syntax evaluated at class-creation time and therefore requires Python 3.10, + even though `setup.py` declares support from 3.9. Now `Optional[str]`, so the + declared floor is real and testable. +- The README quick start used the same 3.10-only syntax, which would fail for + any 3.9 reader copying it. +- The governance section omitted the storage-type and semantic-model constraint + bullets and the `policy_explain` / `policy_history` paragraph that the Dart, + Node, and Rust clients document. + ## [1.1.0] - 2026-07-28 ### Added diff --git a/setup.py b/setup.py index 46c2cb4..55eb00f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='montycat', - version='1.1.0', + version='1.1.1', description=( 'Self-hosted vector database + NoSQL with built-in AI semantic search — the async ' 'Python client for Montycat. A Rust-powered, AI-native Pinecone / Weaviate / Chroma ' From fc571832ba4eeff5daa7012acdf0030108ea8589 Mon Sep 17 00:00:00 2001 From: Eugene&Monty <136487467+EugeneSukharev1988@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:33:00 -0500 Subject: [PATCH 3/3] py 3.10 min --- .github/workflows/.build_and_push.yml | 8 ++++---- CHANGELOG.md | 14 +++++++------- README.md | 6 ++---- setup.py | 3 +-- tests/test_schema_tools_helpers.py | 6 +----- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/workflows/.build_and_push.yml b/.github/workflows/.build_and_push.yml index 2bce551..88ec31e 100644 --- a/.github/workflows/.build_and_push.yml +++ b/.github/workflows/.build_and_push.yml @@ -17,8 +17,8 @@ jobs: strategy: fail-fast: false matrix: - # setup.py declares python_requires='>=3.9'. - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + # setup.py declares python_requires='>=3.10'. + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - name: Checkout code uses: actions/checkout@v4 @@ -47,7 +47,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.10' - name: Install dependencies run: | @@ -73,7 +73,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.10' - name: Install dependencies run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index a6bbcc9..310fbeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,24 +17,24 @@ Documentation, tests, and CI only — no library code changed, so upgrading from real-time subscriptions returning `(task, stop_event)` on the `port + 1` subscription port, TLS via `tls=True`, and owner/access management with `create_owner`, `grant_to`, `revoke_from`, and `Permission`. -- CI now installs the package and runs the test suite on Python 3.9 through +- CI now installs the package and runs the test suite on Python 3.10 through 3.13. The `test` job previously ran only a packaging dry run, so no test had ever executed in CI. - Changelog link in the README. ### Changed +- **Declared Python support is now 3.10+, corrected from 3.9+.** The package has + never been importable on 3.9: `core/schema.py` and `store_classes/kv.py` both + import `types.UnionType` at module scope, which was added in 3.10, so any 3.9 + install failed with `ImportError` on the first `import montycat`. The metadata + and the README FAQ advertised a version that never worked. Nothing that + currently runs is affected, and 3.9 reached end of life in October 2025. - The CI packaging dry run moved into its own `package` job, and the release job now depends on both `test` and `package`. ### Fixed -- The test suite annotated a schema field as `str | None`, which is PEP 604 - syntax evaluated at class-creation time and therefore requires Python 3.10, - even though `setup.py` declares support from 3.9. Now `Optional[str]`, so the - declared floor is real and testable. -- The README quick start used the same 3.10-only syntax, which would fail for - any 3.9 reader copying it. - The governance section omitted the storage-type and semantic-model constraint bullets and the `policy_explain` / `policy_history` paragraph that the Dart, Node, and Rust clients document. diff --git a/README.md b/README.md index 9f1ea32..fcb318b 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,6 @@ pip install montycat ```python import asyncio -from typing import Optional - from montycat import Engine, Keyspace, Schema # setup connection @@ -117,7 +115,7 @@ class SalesSchema(Schema): class ProductionSchema(Schema): items: list - work_order: Optional[str] # or `str | None` on Python 3.10+ + work_order: str | None async def main(): # create store and keyspaces using runtime migration @@ -318,7 +316,7 @@ the end of this document. - **Is Montycat a vector database or a NoSQL database?** Both — one engine. Store records and query them by *meaning* (vector / semantic search) or by key/schema, without running two systems. - **Do I need OpenAI or an embedding API?** No. Embeddings run on-device in the `montycat-semantic` server. No API keys, no per-query bill, no data egress. - **Is it a Pinecone / Weaviate / Chroma / Qdrant alternative?** Yes — self-hosted and open-source, with a NoSQL store built in. -- **Which Python versions?** 3.9+ — fully async (`asyncio`). +- **Which Python versions?** 3.10+ — fully async (`asyncio`). ## Data-mesh governance for shared and multi-tenant deployments diff --git a/setup.py b/setup.py index 55eb00f..dc98741 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -55,5 +54,5 @@ "pinecone-alternative weaviate-alternative chroma-alternative qdrant-alternative " "redis-alternative data-mesh async asyncio rust realtime key-value cache montycat" ), - python_requires='>=3.9', + python_requires='>=3.10', ) diff --git a/tests/test_schema_tools_helpers.py b/tests/test_schema_tools_helpers.py index 046a534..21f04c3 100644 --- a/tests/test_schema_tools_helpers.py +++ b/tests/test_schema_tools_helpers.py @@ -1,5 +1,4 @@ import unittest -from typing import Optional import orjson @@ -43,10 +42,7 @@ class SchemaTests(unittest.TestCase): class Event(Schema): name: str count: int - # Optional[...] rather than PEP 604 `str | None`: annotations are - # evaluated at class-creation time and `|` on types needs Python 3.10, - # while setup.py declares support from 3.9. - note: Optional[str] + note: str | None class LinkedEvent(Schema): parent: Pointer