pycontextdb 0.4.3__tar.gz → 0.4.4__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.
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/CHANGELOG.md +6 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/PKG-INFO +1 -1
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/__init__.py +1 -1
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/postgres_store.py +29 -1
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/pyproject.toml +1 -1
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_pg_sql.py +33 -1
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/CODEOWNERS +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/copilot-instructions.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/pull_request_template.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/workflows/ci.yml +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.github/workflows/publish.yml +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.gitignore +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/.pre-commit-config.yaml +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/COMMERCIAL.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/CONTRIBUTING.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/LICENSE +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/NOTICE +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/OPEN_CORE.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/README.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/.cursorrules +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/CLAUDE.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/README.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/llms-full.txt +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/ai-tools/llms.txt +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/README.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/architecture.png +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/benchmarks.png +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/hero.png +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/performance.png +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/problem.png +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/assets/quickstart.png +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/benchmarks/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/benchmarks/run_benchmarks.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/benchmarks/trust_bakeoff.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/agents/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/agents/memory_bus.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/agents/rl_manager.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/cli.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/client.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/connectors/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/connectors/base.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/connectors/postgres.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/clock.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/config.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/exceptions.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/models.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/policy.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/core/slots.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/evolution.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/formation.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/retrieval.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/salience.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/dynamics/trust.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/base.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/causal.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/entity.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/semantic.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/graphs/temporal.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/act.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/autogen.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/crewai.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/langchain.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/livekit.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/openai_tools.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/pipecat.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/integrations/prompting.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/mcp.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/experiential.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/factual.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/memory/working.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/pool.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/audit.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/injection.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/pii_detector.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/privacy/retention.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/py.typed +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/serve.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/base.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/factory.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/pg_sql.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/sqlite_store.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/store/vector_index.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/embeddings.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/llm.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/contextdb/utils/migrations.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/api.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/architecture.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/connectors.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/index.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/memory_evolution.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/migrations.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/multi_tenant.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/postgres.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/prompts.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/quickstart.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/serve.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/docs/trust_policy.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/customer_support.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/multi_agent_team.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/phone_agent.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/examples/research_assistant.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/legal/CLA-entity.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/legal/CLA-individual.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/legal/signed/README.md +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/mkdocs.yml +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/scripts/check_open_core_boundary.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/scripts/check_release.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/conftest.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/evals/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/evals/test_trust_model.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/integration/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/integration/test_client.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/integration/test_langchain_integration.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/pg_util.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/test_version.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/__init__.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_audit.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_config.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_connectors.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_decision_scope.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_embeddings.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_evolution.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_formation.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_graphs.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_host_api.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_init.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_memory_bus.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_memory_evolution.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_models.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_open_core_boundary.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_pii_detector.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_postgres_concurrency.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_postgres_connector.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_postgres_memory_evolution.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_retention.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_retrieval.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_scoped_id_access.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_serve.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_store.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_trust_policy.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_vector_index.py +0 -0
- {pycontextdb-0.4.3 → pycontextdb-0.4.4}/tests/unit/test_vector_scope.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.4.4 — 2026-09-20
|
|
6
|
+
|
|
7
|
+
- PostgreSQL sibling composition now uses an ordered user-plus-entity cache
|
|
8
|
+
index, so a scoped recall does not scan other users that share the same
|
|
9
|
+
entity key.
|
|
10
|
+
|
|
5
11
|
## 0.4.3 — 2026-09-20
|
|
6
12
|
|
|
7
13
|
- High-cardinality Postgres recall now indexes cached IDs by user and entity,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pycontextdb
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: The unified context layer for AI agents — memory you can act on without treating wishes as facts
|
|
5
5
|
Project-URL: Homepage, https://github.com/atomsai/contextdb
|
|
6
6
|
Project-URL: Documentation, https://contextdb.dev
|
|
@@ -282,6 +282,10 @@ class PostgresStore(BaseStore):
|
|
|
282
282
|
self._index_items: dict[str, MemoryItem] = {}
|
|
283
283
|
self._index_ids_by_user: dict[str | None, set[str]] = {}
|
|
284
284
|
self._index_ids_by_entity: dict[str, dict[str, None]] = {}
|
|
285
|
+
self._index_ids_by_user_entity: dict[
|
|
286
|
+
tuple[str | None, str],
|
|
287
|
+
dict[str, None],
|
|
288
|
+
] = {}
|
|
285
289
|
self._embedding_dim = embedding_dim
|
|
286
290
|
self._embedding_model_id = embedding_model_id
|
|
287
291
|
self._index_loaded = False
|
|
@@ -389,6 +393,10 @@ class PostgresStore(BaseStore):
|
|
|
389
393
|
self._index_ids_by_user.setdefault(item.user_id, set()).add(item.id)
|
|
390
394
|
if item.entity_key is not None:
|
|
391
395
|
self._index_ids_by_entity.setdefault(item.entity_key, {})[item.id] = None
|
|
396
|
+
self._index_ids_by_user_entity.setdefault(
|
|
397
|
+
(item.user_id, item.entity_key),
|
|
398
|
+
{},
|
|
399
|
+
)[item.id] = None
|
|
392
400
|
|
|
393
401
|
def _discard_index_item(self, memory_id: str) -> None:
|
|
394
402
|
item = self._index_items.pop(memory_id, None)
|
|
@@ -405,11 +413,22 @@ class PostgresStore(BaseStore):
|
|
|
405
413
|
entity_ids.pop(memory_id, None)
|
|
406
414
|
if not entity_ids:
|
|
407
415
|
self._index_ids_by_entity.pop(item.entity_key, None)
|
|
416
|
+
scoped_entity_ids = self._index_ids_by_user_entity.get(
|
|
417
|
+
(item.user_id, item.entity_key)
|
|
418
|
+
)
|
|
419
|
+
if scoped_entity_ids is not None:
|
|
420
|
+
scoped_entity_ids.pop(memory_id, None)
|
|
421
|
+
if not scoped_entity_ids:
|
|
422
|
+
self._index_ids_by_user_entity.pop(
|
|
423
|
+
(item.user_id, item.entity_key),
|
|
424
|
+
None,
|
|
425
|
+
)
|
|
408
426
|
|
|
409
427
|
def _clear_index_items(self) -> None:
|
|
410
428
|
self._index_items.clear()
|
|
411
429
|
self._index_ids_by_user.clear()
|
|
412
430
|
self._index_ids_by_entity.clear()
|
|
431
|
+
self._index_ids_by_user_entity.clear()
|
|
413
432
|
|
|
414
433
|
def _scope_sql(self, user_id: str | None = None) -> tuple[str, list[Any]]:
|
|
415
434
|
clauses: list[str] = []
|
|
@@ -1101,8 +1120,17 @@ class PostgresStore(BaseStore):
|
|
|
1101
1120
|
await self._ensure_index()
|
|
1102
1121
|
excluded = exclude_ids or set()
|
|
1103
1122
|
items: list[MemoryItem] = []
|
|
1123
|
+
scope = self._resolve_scope(user_id)
|
|
1104
1124
|
for entity_key in dict.fromkeys(entity_keys):
|
|
1105
|
-
|
|
1125
|
+
entity_ids = (
|
|
1126
|
+
self._index_ids_by_entity.get(entity_key, {})
|
|
1127
|
+
if scope is None
|
|
1128
|
+
else self._index_ids_by_user_entity.get(
|
|
1129
|
+
(scope, entity_key),
|
|
1130
|
+
{},
|
|
1131
|
+
)
|
|
1132
|
+
)
|
|
1133
|
+
for memory_id in entity_ids:
|
|
1106
1134
|
item = self._index_items.get(memory_id)
|
|
1107
1135
|
if item is None:
|
|
1108
1136
|
continue
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pycontextdb"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.4"
|
|
8
8
|
description = "The unified context layer for AI agents — memory you can act on without treating wishes as facts"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -98,6 +98,16 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
|
|
|
98
98
|
)
|
|
99
99
|
for index in range(20)
|
|
100
100
|
]
|
|
101
|
+
foreign = [
|
|
102
|
+
MemoryItem(
|
|
103
|
+
content=f"foreign-{index}",
|
|
104
|
+
entity_key="entity-a",
|
|
105
|
+
user_id="user-2",
|
|
106
|
+
tenant_id="tenant-1",
|
|
107
|
+
agent_id="agent-1",
|
|
108
|
+
)
|
|
109
|
+
for index in range(1_000)
|
|
110
|
+
]
|
|
101
111
|
future = MemoryItem(
|
|
102
112
|
content="future",
|
|
103
113
|
entity_key="entity-a",
|
|
@@ -106,13 +116,15 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
|
|
|
106
116
|
agent_id="agent-1",
|
|
107
117
|
valid_from=now + timedelta(days=1),
|
|
108
118
|
)
|
|
109
|
-
indexed = [*entity_b, future, *entity_a]
|
|
119
|
+
indexed = [*foreign, *entity_b, future, *entity_a]
|
|
110
120
|
for item in indexed:
|
|
111
121
|
store._cache_index_item(item)
|
|
112
122
|
store._index_loaded = True
|
|
113
123
|
|
|
114
124
|
original_model_copy = MemoryItem.model_copy
|
|
125
|
+
original_scope_allows = store._item_scope_allows
|
|
115
126
|
copies = 0
|
|
127
|
+
scope_checks = 0
|
|
116
128
|
|
|
117
129
|
def counted_model_copy(
|
|
118
130
|
self: MemoryItem,
|
|
@@ -125,6 +137,13 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
|
|
|
125
137
|
return original_model_copy(self, update=update, deep=deep)
|
|
126
138
|
|
|
127
139
|
monkeypatch.setattr(MemoryItem, "model_copy", counted_model_copy)
|
|
140
|
+
|
|
141
|
+
def counted_scope_allows(item: MemoryItem, user_id: str | None) -> bool:
|
|
142
|
+
nonlocal scope_checks
|
|
143
|
+
scope_checks += 1
|
|
144
|
+
return original_scope_allows(item, user_id)
|
|
145
|
+
|
|
146
|
+
monkeypatch.setattr(store, "_item_scope_allows", counted_scope_allows)
|
|
128
147
|
results = await store.list_by_entities(
|
|
129
148
|
["entity-a", "entity-b"],
|
|
130
149
|
user_id="user-1",
|
|
@@ -135,6 +154,7 @@ async def test_list_by_entities_filters_and_copies_only_the_requested_limit(
|
|
|
135
154
|
|
|
136
155
|
assert len(results) == 5
|
|
137
156
|
assert copies == 5
|
|
157
|
+
assert scope_checks == 7
|
|
138
158
|
assert all(item.entity_key == "entity-a" for item in results)
|
|
139
159
|
assert entity_a[0].id not in {item.id for item in results}
|
|
140
160
|
results[0].metadata["nested"].append(99)
|
|
@@ -238,6 +258,12 @@ def test_postgres_secondary_indexes_follow_cache_lifecycle() -> None:
|
|
|
238
258
|
}
|
|
239
259
|
assert list(store._index_ids_by_entity["entity-a"]) == [first.id]
|
|
240
260
|
assert list(store._index_ids_by_entity["entity-b"]) == [second.id]
|
|
261
|
+
assert list(store._index_ids_by_user_entity["user-1", "entity-a"]) == [
|
|
262
|
+
first.id
|
|
263
|
+
]
|
|
264
|
+
assert list(store._index_ids_by_user_entity["user-2", "entity-b"]) == [
|
|
265
|
+
second.id
|
|
266
|
+
]
|
|
241
267
|
|
|
242
268
|
replacement = first.model_copy(
|
|
243
269
|
update={"user_id": "user-3", "entity_key": "entity-c"}
|
|
@@ -245,18 +271,24 @@ def test_postgres_secondary_indexes_follow_cache_lifecycle() -> None:
|
|
|
245
271
|
store._cache_index_item(replacement)
|
|
246
272
|
assert "user-1" not in store._index_ids_by_user
|
|
247
273
|
assert "entity-a" not in store._index_ids_by_entity
|
|
274
|
+
assert ("user-1", "entity-a") not in store._index_ids_by_user_entity
|
|
248
275
|
assert store._index_ids_by_user["user-3"] == {first.id}
|
|
249
276
|
assert list(store._index_ids_by_entity["entity-c"]) == [first.id]
|
|
277
|
+
assert list(store._index_ids_by_user_entity["user-3", "entity-c"]) == [
|
|
278
|
+
first.id
|
|
279
|
+
]
|
|
250
280
|
|
|
251
281
|
store._discard_index_item(first.id)
|
|
252
282
|
assert "user-3" not in store._index_ids_by_user
|
|
253
283
|
assert "entity-c" not in store._index_ids_by_entity
|
|
284
|
+
assert ("user-3", "entity-c") not in store._index_ids_by_user_entity
|
|
254
285
|
assert first.id not in store._index_items
|
|
255
286
|
|
|
256
287
|
store._clear_index_items()
|
|
257
288
|
assert store._index_items == {}
|
|
258
289
|
assert store._index_ids_by_user == {}
|
|
259
290
|
assert store._index_ids_by_entity == {}
|
|
291
|
+
assert store._index_ids_by_user_entity == {}
|
|
260
292
|
|
|
261
293
|
|
|
262
294
|
@pytest.mark.asyncio
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|