Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions .github/workflows/.build_and_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.10'.
python-version: ['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
Expand All @@ -20,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: |
Expand All @@ -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:
Expand All @@ -46,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: |
Expand All @@ -59,4 +86,4 @@ jobs:
./build_and_push.sh
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
CI_DEPLOY: true
CI_DEPLOY: true
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.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 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
Expand Down
111 changes: 107 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,19 +208,115 @@ 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": <result>, "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

- **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

Expand All @@ -231,9 +327,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:
Expand All @@ -253,5 +354,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.
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 '
Expand Down Expand Up @@ -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",
Expand All @@ -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',
)
Loading