aevum-store-postgres 0.2.0__tar.gz → 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 (16) hide show
  1. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/.gitignore +31 -31
  2. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/PKG-INFO +1 -1
  3. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/README.md +21 -21
  4. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/pyproject.toml +60 -60
  5. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/__init__.py +30 -30
  6. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/consent.py +110 -110
  7. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/ledger.py +198 -198
  8. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/migrate.py +134 -134
  9. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/schema.py +33 -33
  10. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/store.py +110 -110
  11. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/tests/conftest.py +164 -164
  12. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/tests/test_ledger.py +160 -160
  13. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/tests/test_pg_consent.py +121 -121
  14. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/tests/test_pg_migrate.py +96 -96
  15. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/tests/test_pg_store.py +117 -117
  16. {aevum_store_postgres-0.2.0 → aevum_store_postgres-0.3.0}/src/aevum/store/postgres/py.typed +0 -0
@@ -1,31 +1,31 @@
1
- # Python
2
- __pycache__/
3
- *.pyc
4
- *.pyo
5
- *.pyd
6
- .venv/
7
- *.egg-info/
8
-
9
- # Build
10
- dist/
11
- build/
12
-
13
- # Tools
14
- .mypy_cache/
15
- .ruff_cache/
16
- .pytest_cache/
17
- .hypothesis/
18
-
19
- # IDE
20
- .vscode/
21
- .idea/
22
- *.swp
23
- *.swo
24
-
25
- # OS
26
- .DS_Store
27
- Thumbs.db
28
-
29
- # Verify scripts (run locally, never commit)
30
- verify_phase*.py
31
- scripts/verify_phase*.py
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .venv/
7
+ *.egg-info/
8
+
9
+ # Build
10
+ dist/
11
+ build/
12
+
13
+ # Tools
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .pytest_cache/
17
+ .hypothesis/
18
+
19
+ # IDE
20
+ .vscode/
21
+ .idea/
22
+ *.swp
23
+ *.swo
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Verify scripts (run locally, never commit)
30
+ verify_phase*.py
31
+ scripts/verify_phase*.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aevum-store-postgres
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Aevum — PostgreSQL GraphStore + ConsentLedger backend (team deployments).
5
5
  Project-URL: Homepage, https://aevum.build
6
6
  Project-URL: Repository, https://github.com/aevum-labs/aevum
@@ -1,21 +1,21 @@
1
- # aevum-store-postgres
2
-
3
- PostgreSQL-backed graph store for Aevum. Suitable for team deployments with shared state, concurrent writers, and durable persistence.
4
-
5
- ```bash
6
- pip install aevum-store-postgres
7
- ```
8
-
9
- ```python
10
- import psycopg
11
- from aevum.core import Engine
12
- from aevum.store.postgres import PostgresStore
13
- from aevum.store.postgres.store import initialize_schema
14
-
15
- conn = psycopg.connect("postgresql://user:pass@localhost/aevum")
16
- initialize_schema(conn)
17
- engine = Engine(graph_store=PostgresStore(conn))
18
- ```
19
-
20
- For single-node deployments without PostgreSQL, use `aevum-store-oxigraph` instead.
21
- See the [main repository README](https://github.com/aevum-labs/aevum) for backend selection guidance.
1
+ # aevum-store-postgres
2
+
3
+ PostgreSQL-backed graph store for Aevum. Suitable for team deployments with shared state, concurrent writers, and durable persistence.
4
+
5
+ ```bash
6
+ pip install aevum-store-postgres
7
+ ```
8
+
9
+ ```python
10
+ import psycopg
11
+ from aevum.core import Engine
12
+ from aevum.store.postgres import PostgresStore
13
+ from aevum.store.postgres.store import initialize_schema
14
+
15
+ conn = psycopg.connect("postgresql://user:pass@localhost/aevum")
16
+ initialize_schema(conn)
17
+ engine = Engine(graph_store=PostgresStore(conn))
18
+ ```
19
+
20
+ For single-node deployments without PostgreSQL, use `aevum-store-oxigraph` instead.
21
+ See the [main repository README](https://github.com/aevum-labs/aevum) for backend selection guidance.
@@ -1,60 +1,60 @@
1
- [project]
2
- name = "aevum-store-postgres"
3
- version = "0.2.0"
4
- description = "Aevum — PostgreSQL GraphStore + ConsentLedger backend (team deployments)."
5
- readme = "README.md"
6
- requires-python = ">=3.11"
7
- license = { text = "Apache-2.0" }
8
- classifiers = [
9
- "Development Status :: 3 - Alpha",
10
- "Intended Audience :: Developers",
11
- "License :: OSI Approved :: Apache Software License",
12
- "Programming Language :: Python :: 3.11",
13
- "Programming Language :: Python :: 3.12",
14
- "Programming Language :: Python :: 3.13",
15
- "Typing :: Typed",
16
- ]
17
- dependencies = [
18
- "aevum-core",
19
- "psycopg[binary]>=3.1,<4.0",
20
- ]
21
-
22
- [project.scripts]
23
- aevum-store-migrate = "aevum.store.postgres.migrate:main"
24
-
25
- [project.urls]
26
- Homepage = "https://aevum.build"
27
- Repository = "https://github.com/aevum-labs/aevum"
28
-
29
- [build-system]
30
- requires = ["hatchling"]
31
- build-backend = "hatchling.build"
32
-
33
- [tool.hatch.build.targets.wheel]
34
- packages = ["src/aevum"]
35
-
36
- [tool.uv.sources]
37
- aevum-core = { workspace = true }
38
-
39
- [tool.pytest.ini_options]
40
- testpaths = ["tests"]
41
- asyncio_mode = "auto"
42
- addopts = "--tb=short"
43
- pythonpath = ["src", "tests"]
44
-
45
- [tool.mypy]
46
- strict = true
47
- python_version = "3.11"
48
- mypy_path = "src"
49
- explicit_package_bases = true
50
- ignore_missing_imports = true
51
-
52
- [tool.ruff]
53
- line-length = 130
54
-
55
- [tool.ruff.lint]
56
- select = ["E", "F", "UP", "B", "SIM", "I", "ANN"]
57
- ignore = ["ANN401"]
58
-
59
- [tool.ruff.lint.per-file-ignores]
60
- "tests/**" = ["ANN"]
1
+ [project]
2
+ name = "aevum-store-postgres"
3
+ version = "0.3.0"
4
+ description = "Aevum — PostgreSQL GraphStore + ConsentLedger backend (team deployments)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "Apache-2.0" }
8
+ classifiers = [
9
+ "Development Status :: 3 - Alpha",
10
+ "Intended Audience :: Developers",
11
+ "License :: OSI Approved :: Apache Software License",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Typing :: Typed",
16
+ ]
17
+ dependencies = [
18
+ "aevum-core",
19
+ "psycopg[binary]>=3.1,<4.0",
20
+ ]
21
+
22
+ [project.scripts]
23
+ aevum-store-migrate = "aevum.store.postgres.migrate:main"
24
+
25
+ [project.urls]
26
+ Homepage = "https://aevum.build"
27
+ Repository = "https://github.com/aevum-labs/aevum"
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/aevum"]
35
+
36
+ [tool.uv.sources]
37
+ aevum-core = { workspace = true }
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
41
+ asyncio_mode = "auto"
42
+ addopts = "--tb=short"
43
+ pythonpath = ["src", "tests"]
44
+
45
+ [tool.mypy]
46
+ strict = true
47
+ python_version = "3.11"
48
+ mypy_path = "src"
49
+ explicit_package_bases = true
50
+ ignore_missing_imports = true
51
+
52
+ [tool.ruff]
53
+ line-length = 130
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "UP", "B", "SIM", "I", "ANN"]
57
+ ignore = ["ANN401"]
58
+
59
+ [tool.ruff.lint.per-file-ignores]
60
+ "tests/**" = ["ANN"]
@@ -1,30 +1,30 @@
1
- """
2
- aevum.store.postgres -- PostgreSQL backends for graph, consent, and ledger.
3
-
4
- Usage:
5
- from aevum.store.postgres import PostgresStore, PostgresConsentLedger, PostgresLedger
6
- from aevum.store.postgres.ledger import initialize_ledger_schema
7
- import psycopg
8
-
9
- conn = psycopg.connect("postgresql://user:pass@host/dbname")
10
- initialize_schema(conn) # graph + consent DDL
11
- initialize_ledger_schema(conn) # ledger DDL
12
-
13
- store = PostgresStore(conn)
14
- consent = PostgresConsentLedger(conn)
15
- ledger = PostgresLedger(conn, sigchain)
16
-
17
- engine = Engine(
18
- graph_store=store,
19
- consent_ledger=consent,
20
- ledger=ledger,
21
- )
22
- """
23
-
24
- from aevum.store.postgres.consent import PostgresConsentLedger
25
- from aevum.store.postgres.ledger import PostgresLedger
26
- from aevum.store.postgres.store import PostgresStore
27
-
28
- __version__ = "0.1.0"
29
-
30
- __all__ = ["PostgresStore", "PostgresConsentLedger", "PostgresLedger"]
1
+ """
2
+ aevum.store.postgres -- PostgreSQL backends for graph, consent, and ledger.
3
+
4
+ Usage:
5
+ from aevum.store.postgres import PostgresStore, PostgresConsentLedger, PostgresLedger
6
+ from aevum.store.postgres.ledger import initialize_ledger_schema
7
+ import psycopg
8
+
9
+ conn = psycopg.connect("postgresql://user:pass@host/dbname")
10
+ initialize_schema(conn) # graph + consent DDL
11
+ initialize_ledger_schema(conn) # ledger DDL
12
+
13
+ store = PostgresStore(conn)
14
+ consent = PostgresConsentLedger(conn)
15
+ ledger = PostgresLedger(conn, sigchain)
16
+
17
+ engine = Engine(
18
+ graph_store=store,
19
+ consent_ledger=consent,
20
+ ledger=ledger,
21
+ )
22
+ """
23
+
24
+ from aevum.store.postgres.consent import PostgresConsentLedger
25
+ from aevum.store.postgres.ledger import PostgresLedger
26
+ from aevum.store.postgres.store import PostgresStore
27
+
28
+ __version__ = "0.1.0"
29
+
30
+ __all__ = ["PostgresStore", "PostgresConsentLedger", "PostgresLedger"]
@@ -1,110 +1,110 @@
1
- """
2
- PostgresConsentLedger — ConsentLedgerProtocol backed by PostgreSQL.
3
-
4
- Grants stored as JSONB in aevum_consent_grants.
5
- OR-Set semantics: revocation wins over concurrent grants.
6
- Expiration checked in Python (matching InMemoryConsentLedger behaviour).
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import contextlib
12
- import json
13
- import threading
14
- from datetime import UTC, datetime
15
- from typing import Any
16
-
17
- from aevum.core.consent.models import ConsentGrant
18
-
19
-
20
- class PostgresConsentLedger:
21
- """
22
- ConsentLedgerProtocol implementation backed by PostgreSQL.
23
-
24
- Args:
25
- conn: An open psycopg.Connection (autocommit=True recommended).
26
- lock: Optional shared Lock. Pass the same lock used by PostgresStore
27
- when both share one connection.
28
- """
29
-
30
- def __init__(self, conn: Any, lock: threading.Lock | None = None) -> None:
31
- self._conn = conn
32
- self._lock = lock or threading.Lock()
33
-
34
- # ── ConsentLedgerProtocol ──────────────────────────────────────────────────
35
-
36
- def add_grant(self, grant: ConsentGrant) -> None:
37
- """Upsert a consent grant. Replaces any prior record with the same grant_id."""
38
- sql = """
39
- INSERT INTO aevum_consent_grants (grant_id, grant_data)
40
- VALUES (%s, %s::jsonb)
41
- ON CONFLICT (grant_id) DO UPDATE SET grant_data = EXCLUDED.grant_data
42
- """
43
- with self._lock, self._conn.cursor() as cur:
44
- cur.execute(sql, (grant.grant_id, json.dumps(grant.model_dump())))
45
-
46
- def revoke_grant(self, grant_id: str) -> None:
47
- """Mark a grant as revoked (immutable update via JSONB merge)."""
48
- sql = """
49
- UPDATE aevum_consent_grants
50
- SET grant_data = jsonb_set(grant_data, '{revocation_status}', '"revoked"')
51
- WHERE grant_id = %s
52
- """
53
- with self._lock, self._conn.cursor() as cur:
54
- cur.execute(sql, (grant_id,))
55
-
56
- def has_consent(
57
- self,
58
- *,
59
- subject_id: str,
60
- operation: str,
61
- grantee_id: str,
62
- purpose: str | None = None,
63
- ) -> bool:
64
- """
65
- Return True if an active, unexpired grant covers this operation.
66
-
67
- Mirrors InMemoryConsentLedger: loads candidates from DB, checks
68
- expiration and operation in Python to stay consistent with the spec.
69
- """
70
- sql = """
71
- SELECT grant_data
72
- FROM aevum_consent_grants
73
- WHERE grant_data->>'subject_id' = %s
74
- AND grant_data->>'grantee_id' = %s
75
- AND grant_data->>'revocation_status' = 'active'
76
- """
77
- with self._lock, self._conn.cursor() as cur:
78
- cur.execute(sql, (subject_id, grantee_id))
79
- rows = cur.fetchall()
80
-
81
- now = datetime.now(UTC)
82
- for row in rows:
83
- raw = row[0]
84
- d: dict[str, Any] = json.loads(raw) if isinstance(raw, str) else raw
85
- if operation not in d.get("operations", []):
86
- continue
87
- try:
88
- expires = datetime.fromisoformat(
89
- d["expires_at"].replace("Z", "+00:00")
90
- )
91
- if now > expires:
92
- continue
93
- except (KeyError, ValueError):
94
- continue
95
- return True
96
- return False
97
-
98
- def all_grants(self) -> list[ConsentGrant]:
99
- """Return all stored grants (active, revoked, and expired)."""
100
- sql = "SELECT grant_data FROM aevum_consent_grants"
101
- with self._lock, self._conn.cursor() as cur:
102
- cur.execute(sql)
103
- rows = cur.fetchall()
104
- grants: list[ConsentGrant] = []
105
- for row in rows:
106
- raw = row[0]
107
- d: dict[str, Any] = json.loads(raw) if isinstance(raw, str) else raw
108
- with contextlib.suppress(Exception):
109
- grants.append(ConsentGrant(**d))
110
- return grants
1
+ """
2
+ PostgresConsentLedger — ConsentLedgerProtocol backed by PostgreSQL.
3
+
4
+ Grants stored as JSONB in aevum_consent_grants.
5
+ OR-Set semantics: revocation wins over concurrent grants.
6
+ Expiration checked in Python (matching InMemoryConsentLedger behaviour).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import contextlib
12
+ import json
13
+ import threading
14
+ from datetime import UTC, datetime
15
+ from typing import Any
16
+
17
+ from aevum.core.consent.models import ConsentGrant
18
+
19
+
20
+ class PostgresConsentLedger:
21
+ """
22
+ ConsentLedgerProtocol implementation backed by PostgreSQL.
23
+
24
+ Args:
25
+ conn: An open psycopg.Connection (autocommit=True recommended).
26
+ lock: Optional shared Lock. Pass the same lock used by PostgresStore
27
+ when both share one connection.
28
+ """
29
+
30
+ def __init__(self, conn: Any, lock: threading.Lock | None = None) -> None:
31
+ self._conn = conn
32
+ self._lock = lock or threading.Lock()
33
+
34
+ # ── ConsentLedgerProtocol ──────────────────────────────────────────────────
35
+
36
+ def add_grant(self, grant: ConsentGrant) -> None:
37
+ """Upsert a consent grant. Replaces any prior record with the same grant_id."""
38
+ sql = """
39
+ INSERT INTO aevum_consent_grants (grant_id, grant_data)
40
+ VALUES (%s, %s::jsonb)
41
+ ON CONFLICT (grant_id) DO UPDATE SET grant_data = EXCLUDED.grant_data
42
+ """
43
+ with self._lock, self._conn.cursor() as cur:
44
+ cur.execute(sql, (grant.grant_id, json.dumps(grant.model_dump())))
45
+
46
+ def revoke_grant(self, grant_id: str) -> None:
47
+ """Mark a grant as revoked (immutable update via JSONB merge)."""
48
+ sql = """
49
+ UPDATE aevum_consent_grants
50
+ SET grant_data = jsonb_set(grant_data, '{revocation_status}', '"revoked"')
51
+ WHERE grant_id = %s
52
+ """
53
+ with self._lock, self._conn.cursor() as cur:
54
+ cur.execute(sql, (grant_id,))
55
+
56
+ def has_consent(
57
+ self,
58
+ *,
59
+ subject_id: str,
60
+ operation: str,
61
+ grantee_id: str,
62
+ purpose: str | None = None,
63
+ ) -> bool:
64
+ """
65
+ Return True if an active, unexpired grant covers this operation.
66
+
67
+ Mirrors InMemoryConsentLedger: loads candidates from DB, checks
68
+ expiration and operation in Python to stay consistent with the spec.
69
+ """
70
+ sql = """
71
+ SELECT grant_data
72
+ FROM aevum_consent_grants
73
+ WHERE grant_data->>'subject_id' = %s
74
+ AND grant_data->>'grantee_id' = %s
75
+ AND grant_data->>'revocation_status' = 'active'
76
+ """
77
+ with self._lock, self._conn.cursor() as cur:
78
+ cur.execute(sql, (subject_id, grantee_id))
79
+ rows = cur.fetchall()
80
+
81
+ now = datetime.now(UTC)
82
+ for row in rows:
83
+ raw = row[0]
84
+ d: dict[str, Any] = json.loads(raw) if isinstance(raw, str) else raw
85
+ if operation not in d.get("operations", []):
86
+ continue
87
+ try:
88
+ expires = datetime.fromisoformat(
89
+ d["expires_at"].replace("Z", "+00:00")
90
+ )
91
+ if now > expires:
92
+ continue
93
+ except (KeyError, ValueError):
94
+ continue
95
+ return True
96
+ return False
97
+
98
+ def all_grants(self) -> list[ConsentGrant]:
99
+ """Return all stored grants (active, revoked, and expired)."""
100
+ sql = "SELECT grant_data FROM aevum_consent_grants"
101
+ with self._lock, self._conn.cursor() as cur:
102
+ cur.execute(sql)
103
+ rows = cur.fetchall()
104
+ grants: list[ConsentGrant] = []
105
+ for row in rows:
106
+ raw = row[0]
107
+ d: dict[str, Any] = json.loads(raw) if isinstance(raw, str) else raw
108
+ with contextlib.suppress(Exception):
109
+ grants.append(ConsentGrant(**d))
110
+ return grants