From fb7e74eaa8d8bdae50021e0a24e7e6bf6bfc2a09 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 29 Oct 2025 16:28:09 +0100 Subject: [PATCH 01/18] refactor: Enable multi user mode by default if graph and vector db providers support it --- .env.template | 5 ++- cognee/context_global_variables.py | 43 ++++++++++++++++++- cognee/modules/search/methods/search.py | 6 +-- .../users/methods/get_authenticated_user.py | 3 +- .../relational_database_migration_example.py | 3 ++ logs/.gitkeep | 0 logs/README.md | 31 ------------- 7 files changed, 53 insertions(+), 38 deletions(-) delete mode 100644 logs/.gitkeep delete mode 100644 logs/README.md diff --git a/.env.template b/.env.template index 89ac06830..8e1bdd23f 100644 --- a/.env.template +++ b/.env.template @@ -169,8 +169,9 @@ REQUIRE_AUTHENTICATION=False # Vector: LanceDB # Graph: KuzuDB # -# It enforces LanceDB and KuzuDB use and uses them to create databases per Cognee user + dataset -ENABLE_BACKEND_ACCESS_CONTROL=False +# It enforces creation of databases per Cognee user + dataset. Does not work with some graph and database providers. +# Disable mode when using not supported graph/vector databases. +ENABLE_BACKEND_ACCESS_CONTROL=True ################################################################################ # ☁️ Cloud Sync Settings diff --git a/cognee/context_global_variables.py b/cognee/context_global_variables.py index d52de4b4e..8ad855724 100644 --- a/cognee/context_global_variables.py +++ b/cognee/context_global_variables.py @@ -4,6 +4,8 @@ from typing import Union from uuid import UUID from cognee.base_config import get_base_config +from cognee.infrastructure.databases.vector.config import get_vectordb_context_config +from cognee.infrastructure.databases.graph.config import get_graph_context_config from cognee.infrastructure.databases.utils import get_or_create_dataset_database from cognee.infrastructure.files.storage.config import file_storage_config from cognee.modules.users.methods import get_user @@ -14,11 +16,50 @@ vector_db_config = ContextVar("vector_db_config", default=None) graph_db_config = ContextVar("graph_db_config", default=None) session_user = ContextVar("session_user", default=None) +vector_dbs_with_multi_user_support = ["lancedb"] +graph_dbs_with_multi_user_support = ["kuzu"] + async def set_session_user_context_variable(user): session_user.set(user) +def check_multi_user_support(): + graph_db_config = get_graph_context_config() + vector_db_config = get_vectordb_context_config() + if ( + graph_db_config["graph_database_provider"] in graph_dbs_with_multi_user_support + and vector_db_config["vector_db_provider"] in vector_dbs_with_multi_user_support + ): + return True + else: + return False + + +def check_backend_access_control_mode(): + backend_access_control = os.environ.get("ENABLE_BACKEND_ACCESS_CONTROL", None) + if backend_access_control is None: + # If backend access control is not defined in environment variables, + # enable it by default if graph and vector DBs can support it, otherwise disable it + multi_user_support = check_multi_user_support() + if multi_user_support: + return "true" + else: + return "false" + elif backend_access_control.lower() == "true": + # If enabled, ensure that the current graph and vector DBs can support it + multi_user_support = check_multi_user_support() + if not multi_user_support: + raise EnvironmentError( + "ENABLE_BACKEND_ACCESS_CONTROL is set to true but the current graph and/or vector databases do not support multi-user access control. Please use supported databases or disable backend access control." + ) + else: + return "true" + else: + # If explicitly disabled, return false + return "false" + + async def set_database_global_context_variables(dataset: Union[str, UUID], user_id: UUID): """ If backend access control is enabled this function will ensure all datasets have their own databases, @@ -40,7 +81,7 @@ async def set_database_global_context_variables(dataset: Union[str, UUID], user_ base_config = get_base_config() - if not os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true": + if not check_backend_access_control_mode() == "true": return user = await get_user(user_id) diff --git a/cognee/modules/search/methods/search.py b/cognee/modules/search/methods/search.py index aab004924..e3d7c220e 100644 --- a/cognee/modules/search/methods/search.py +++ b/cognee/modules/search/methods/search.py @@ -1,4 +1,3 @@ -import os import json import asyncio from uuid import UUID @@ -9,6 +8,7 @@ from cognee.infrastructure.databases.graph import get_graph_engine from cognee.shared.logging_utils import get_logger from cognee.shared.utils import send_telemetry from cognee.context_global_variables import set_database_global_context_variables +from cognee.context_global_variables import check_backend_access_control_mode from cognee.modules.engine.models.node_set import NodeSet from cognee.modules.graph.cognee_graph.CogneeGraphElements import Edge @@ -74,7 +74,7 @@ async def search( ) # Use search function filtered by permissions if access control is enabled - if os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true": + if check_backend_access_control_mode() == "true": search_results = await authorized_search( query_type=query_type, query_text=query_text, @@ -156,7 +156,7 @@ async def search( ) else: # This is for maintaining backwards compatibility - if os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true": + if check_backend_access_control_mode() == "true": return_value = [] for search_result in search_results: prepared_search_results = await prepare_search_result(search_result) diff --git a/cognee/modules/users/methods/get_authenticated_user.py b/cognee/modules/users/methods/get_authenticated_user.py index d78215892..34d82586e 100644 --- a/cognee/modules/users/methods/get_authenticated_user.py +++ b/cognee/modules/users/methods/get_authenticated_user.py @@ -5,6 +5,7 @@ from ..models import User from ..get_fastapi_users import get_fastapi_users from .get_default_user import get_default_user from cognee.shared.logging_utils import get_logger +from cognee.context_global_variables import check_backend_access_control_mode logger = get_logger("get_authenticated_user") @@ -12,7 +13,7 @@ logger = get_logger("get_authenticated_user") # Check environment variable to determine authentication requirement REQUIRE_AUTHENTICATION = ( os.getenv("REQUIRE_AUTHENTICATION", "false").lower() == "true" - or os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true" + or check_backend_access_control_mode() == "true" ) fastapi_users = get_fastapi_users() diff --git a/examples/python/relational_database_migration_example.py b/examples/python/relational_database_migration_example.py index 7e87347bc..98482cb4b 100644 --- a/examples/python/relational_database_migration_example.py +++ b/examples/python/relational_database_migration_example.py @@ -31,6 +31,9 @@ from cognee.infrastructure.databases.vector.pgvector import ( async def main(): + # Disable backend access control to migrate relational data + os.environ["ENABLE_BACKEND_ACCESS_CONTROL"] = "false" + # Clean all data stored in Cognee await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) diff --git a/logs/.gitkeep b/logs/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/logs/README.md b/logs/README.md deleted file mode 100644 index 96ef613b5..000000000 --- a/logs/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Logs Directory - -This directory contains the application logs for Cognee. - -## Log Files - -- Log files are named by date in the format `YYYY-MM-DD_HH-MM-SS.log` -- Logs are stored in plain text format with a consistent structure -- Each log entry includes: - - Timestamp (ISO format) - - Log level (padded to consistent width) - - Message - - Additional context (if any) - - Logger name (in square brackets) -- Exception tracebacks are included for error logs - -## Sample Log Entry - -``` -2025-03-27T13:05:27.481446Z [INFO ] Structured log message user_id=user123 action=login status=success [TestLogger] -``` - -## Retention Policy - -The system automatically keeps only the 10 most recent log files. Older log files are automatically deleted when new log files are created. This prevents excessive disk usage in long-running deployments. - -## Usage - -Logs are automatically generated by the application's logging mechanism. No manual actions are required to use this feature. - -The logs directory structure is preserved in version control, but the log files themselves are gitignored. From 6a7660a7c10892657422307b90788d0d6f80b8ab Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 29 Oct 2025 16:31:42 +0100 Subject: [PATCH 02/18] refactor: Return logs folder --- logs/.gitkeep | 0 logs/README.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 logs/.gitkeep create mode 100644 logs/README.md diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/logs/README.md b/logs/README.md new file mode 100644 index 000000000..96ef613b5 --- /dev/null +++ b/logs/README.md @@ -0,0 +1,31 @@ +# Logs Directory + +This directory contains the application logs for Cognee. + +## Log Files + +- Log files are named by date in the format `YYYY-MM-DD_HH-MM-SS.log` +- Logs are stored in plain text format with a consistent structure +- Each log entry includes: + - Timestamp (ISO format) + - Log level (padded to consistent width) + - Message + - Additional context (if any) + - Logger name (in square brackets) +- Exception tracebacks are included for error logs + +## Sample Log Entry + +``` +2025-03-27T13:05:27.481446Z [INFO ] Structured log message user_id=user123 action=login status=success [TestLogger] +``` + +## Retention Policy + +The system automatically keeps only the 10 most recent log files. Older log files are automatically deleted when new log files are created. This prevents excessive disk usage in long-running deployments. + +## Usage + +Logs are automatically generated by the application's logging mechanism. No manual actions are required to use this feature. + +The logs directory structure is preserved in version control, but the log files themselves are gitignored. From 6572cf5cb9bcd7bc3906dc9149a22759069e79b2 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 29 Oct 2025 16:35:44 +0100 Subject: [PATCH 03/18] refactor: use boolean instead of string --- cognee/context_global_variables.py | 10 +++++----- cognee/modules/search/methods/search.py | 4 ++-- cognee/modules/users/methods/get_authenticated_user.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cognee/context_global_variables.py b/cognee/context_global_variables.py index 8ad855724..b4b848192 100644 --- a/cognee/context_global_variables.py +++ b/cognee/context_global_variables.py @@ -43,9 +43,9 @@ def check_backend_access_control_mode(): # enable it by default if graph and vector DBs can support it, otherwise disable it multi_user_support = check_multi_user_support() if multi_user_support: - return "true" + return True else: - return "false" + return False elif backend_access_control.lower() == "true": # If enabled, ensure that the current graph and vector DBs can support it multi_user_support = check_multi_user_support() @@ -54,10 +54,10 @@ def check_backend_access_control_mode(): "ENABLE_BACKEND_ACCESS_CONTROL is set to true but the current graph and/or vector databases do not support multi-user access control. Please use supported databases or disable backend access control." ) else: - return "true" + return True else: # If explicitly disabled, return false - return "false" + return False async def set_database_global_context_variables(dataset: Union[str, UUID], user_id: UUID): @@ -81,7 +81,7 @@ async def set_database_global_context_variables(dataset: Union[str, UUID], user_ base_config = get_base_config() - if not check_backend_access_control_mode() == "true": + if not check_backend_access_control_mode(): return user = await get_user(user_id) diff --git a/cognee/modules/search/methods/search.py b/cognee/modules/search/methods/search.py index e3d7c220e..4a67093e8 100644 --- a/cognee/modules/search/methods/search.py +++ b/cognee/modules/search/methods/search.py @@ -74,7 +74,7 @@ async def search( ) # Use search function filtered by permissions if access control is enabled - if check_backend_access_control_mode() == "true": + if check_backend_access_control_mode(): search_results = await authorized_search( query_type=query_type, query_text=query_text, @@ -156,7 +156,7 @@ async def search( ) else: # This is for maintaining backwards compatibility - if check_backend_access_control_mode() == "true": + if check_backend_access_control_mode(): return_value = [] for search_result in search_results: prepared_search_results = await prepare_search_result(search_result) diff --git a/cognee/modules/users/methods/get_authenticated_user.py b/cognee/modules/users/methods/get_authenticated_user.py index 34d82586e..3cc16f3a8 100644 --- a/cognee/modules/users/methods/get_authenticated_user.py +++ b/cognee/modules/users/methods/get_authenticated_user.py @@ -13,7 +13,7 @@ logger = get_logger("get_authenticated_user") # Check environment variable to determine authentication requirement REQUIRE_AUTHENTICATION = ( os.getenv("REQUIRE_AUTHENTICATION", "false").lower() == "true" - or check_backend_access_control_mode() == "true" + or check_backend_access_control_mode() ) fastapi_users = get_fastapi_users() From d1581e9ebab143930acf1cec404dda083fb06a9f Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 29 Oct 2025 17:36:56 +0100 Subject: [PATCH 04/18] refactor: disable permissions for code graph example --- examples/python/code_graph_example.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/python/code_graph_example.py b/examples/python/code_graph_example.py index 431069050..1b476a2c3 100644 --- a/examples/python/code_graph_example.py +++ b/examples/python/code_graph_example.py @@ -1,5 +1,7 @@ import argparse import asyncio +import os + import cognee from cognee import SearchType from cognee.shared.logging_utils import setup_logging, ERROR @@ -8,6 +10,9 @@ from cognee.api.v1.cognify.code_graph_pipeline import run_code_graph_pipeline async def main(repo_path, include_docs): + # Disable permissions feature for this example + os.environ["ENABLE_BACKEND_ACCESS_CONTROL"] = "false" + run_status = False async for run_status in run_code_graph_pipeline(repo_path, include_docs=include_docs): run_status = run_status From eec96e4f1fb30e692b6e448aa9b1e553c0fead98 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 29 Oct 2025 19:14:53 +0100 Subject: [PATCH 05/18] refactor: fix search result for library test --- cognee/tests/test_library.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cognee/tests/test_library.py b/cognee/tests/test_library.py index 81f81ee61..893b836c0 100755 --- a/cognee/tests/test_library.py +++ b/cognee/tests/test_library.py @@ -90,15 +90,17 @@ async def main(): ) search_results = await cognee.search( - query_type=SearchType.GRAPH_COMPLETION, query_text="What information do you contain?" + query_type=SearchType.GRAPH_COMPLETION, + query_text="What information do you contain?", + dataset_ids=[pipeline_run_obj.dataset_id], ) - assert "Mark" in search_results[0], ( + assert "Mark" in search_results[0]["search_result"][0], ( "Failed to update document, no mention of Mark in search results" ) - assert "Cindy" in search_results[0], ( + assert "Cindy" in search_results[0]["search_result"][0], ( "Failed to update document, no mention of Cindy in search results" ) - assert "Artificial intelligence" not in search_results[0], ( + assert "Artificial intelligence" not in search_results[0]["search_result"][0], ( "Failed to update document, Artificial intelligence still mentioned in search results" ) From ee0ecd52d8115396523c1a62b2c99b3178f5d182 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 30 Oct 2025 16:25:34 +0100 Subject: [PATCH 06/18] refactor: Rewrite tests to work with multi-user mode by default --- .../users/test_conditional_authentication.py | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/cognee/tests/unit/modules/users/test_conditional_authentication.py b/cognee/tests/unit/modules/users/test_conditional_authentication.py index c4368d796..6568c3cb0 100644 --- a/cognee/tests/unit/modules/users/test_conditional_authentication.py +++ b/cognee/tests/unit/modules/users/test_conditional_authentication.py @@ -107,29 +107,10 @@ class TestConditionalAuthenticationIntegration: # REQUIRE_AUTHENTICATION should be a boolean assert isinstance(REQUIRE_AUTHENTICATION, bool) - # Currently should be False (optional authentication) - assert not REQUIRE_AUTHENTICATION - class TestConditionalAuthenticationEnvironmentVariables: """Test environment variable handling.""" - def test_require_authentication_default_false(self): - """Test that REQUIRE_AUTHENTICATION defaults to false when imported with no env vars.""" - with patch.dict(os.environ, {}, clear=True): - # Remove module from cache to force fresh import - module_name = "cognee.modules.users.methods.get_authenticated_user" - if module_name in sys.modules: - del sys.modules[module_name] - - # Import after patching environment - module will see empty environment - from cognee.modules.users.methods.get_authenticated_user import ( - REQUIRE_AUTHENTICATION, - ) - - importlib.invalidate_caches() - assert not REQUIRE_AUTHENTICATION - def test_require_authentication_true(self): """Test that REQUIRE_AUTHENTICATION=true is parsed correctly when imported.""" with patch.dict(os.environ, {"REQUIRE_AUTHENTICATION": "true"}): @@ -145,50 +126,6 @@ class TestConditionalAuthenticationEnvironmentVariables: assert REQUIRE_AUTHENTICATION - def test_require_authentication_false_explicit(self): - """Test that REQUIRE_AUTHENTICATION=false is parsed correctly when imported.""" - with patch.dict(os.environ, {"REQUIRE_AUTHENTICATION": "false"}): - # Remove module from cache to force fresh import - module_name = "cognee.modules.users.methods.get_authenticated_user" - if module_name in sys.modules: - del sys.modules[module_name] - - # Import after patching environment - module will see REQUIRE_AUTHENTICATION=false - from cognee.modules.users.methods.get_authenticated_user import ( - REQUIRE_AUTHENTICATION, - ) - - assert not REQUIRE_AUTHENTICATION - - def test_require_authentication_case_insensitive(self): - """Test that environment variable parsing is case insensitive when imported.""" - test_cases = ["TRUE", "True", "tRuE", "FALSE", "False", "fAlSe"] - - for case in test_cases: - with patch.dict(os.environ, {"REQUIRE_AUTHENTICATION": case}): - # Remove module from cache to force fresh import - module_name = "cognee.modules.users.methods.get_authenticated_user" - if module_name in sys.modules: - del sys.modules[module_name] - - # Import after patching environment - from cognee.modules.users.methods.get_authenticated_user import ( - REQUIRE_AUTHENTICATION, - ) - - expected = case.lower() == "true" - assert REQUIRE_AUTHENTICATION == expected, f"Failed for case: {case}" - - def test_current_require_authentication_value(self): - """Test that the current REQUIRE_AUTHENTICATION module value is as expected.""" - from cognee.modules.users.methods.get_authenticated_user import ( - REQUIRE_AUTHENTICATION, - ) - - # The module-level variable should currently be False (set at import time) - assert isinstance(REQUIRE_AUTHENTICATION, bool) - assert not REQUIRE_AUTHENTICATION - class TestConditionalAuthenticationEdgeCases: """Test edge cases and error scenarios.""" From 9d8430cfb08e17467390ff53e57d330885c6c7d9 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 30 Oct 2025 16:52:04 +0100 Subject: [PATCH 07/18] refactor: Update unit tests for require auth --- .../test_conditional_authentication_endpoints.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cognee/tests/unit/api/test_conditional_authentication_endpoints.py b/cognee/tests/unit/api/test_conditional_authentication_endpoints.py index 2eabee91a..6cc37ef38 100644 --- a/cognee/tests/unit/api/test_conditional_authentication_endpoints.py +++ b/cognee/tests/unit/api/test_conditional_authentication_endpoints.py @@ -1,3 +1,4 @@ +import os import pytest from unittest.mock import patch, AsyncMock, MagicMock from uuid import uuid4 @@ -5,8 +6,6 @@ from fastapi.testclient import TestClient from types import SimpleNamespace import importlib -from cognee.api.client import app - # Fixtures for reuse across test classes @pytest.fixture @@ -32,6 +31,10 @@ def mock_authenticated_user(): ) +# To turn off authentication we need to set the environment variable before importing the module +# Also both require_authentication and backend access control must be false +os.environ["REQUIRE_AUTHENTICATION"] = "false" +os.environ["ENABLE_BACKEND_ACCESS_CONTROL"] = "false" gau_mod = importlib.import_module("cognee.modules.users.methods.get_authenticated_user") @@ -40,6 +43,8 @@ class TestConditionalAuthenticationEndpoints: @pytest.fixture def client(self): + from cognee.api.client import app + """Create a test client.""" return TestClient(app) @@ -133,6 +138,8 @@ class TestConditionalAuthenticationBehavior: @pytest.fixture def client(self): + from cognee.api.client import app + return TestClient(app) @pytest.mark.parametrize( @@ -209,6 +216,8 @@ class TestConditionalAuthenticationErrorHandling: @pytest.fixture def client(self): + from cognee.api.client import app + return TestClient(app) @patch.object(gau_mod, "get_default_user", new_callable=AsyncMock) @@ -232,7 +241,7 @@ class TestConditionalAuthenticationErrorHandling: # The exact error message may vary depending on the actual database connection # The important thing is that we get a 500 error when user creation fails - def test_current_environment_configuration(self): + def test_current_environment_configuration(self, client): """Test that current environment configuration is working properly.""" # This tests the actual module state without trying to change it from cognee.modules.users.methods.get_authenticated_user import ( From e061f34a28bf9cd27453ea6aac8abf215c7bde8f Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 30 Oct 2025 17:13:10 +0100 Subject: [PATCH 08/18] fix: Resolve issue with dataset names for example --- examples/python/feedback_enrichment_minimal_example.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/python/feedback_enrichment_minimal_example.py b/examples/python/feedback_enrichment_minimal_example.py index 11ef20830..8954bd5f6 100644 --- a/examples/python/feedback_enrichment_minimal_example.py +++ b/examples/python/feedback_enrichment_minimal_example.py @@ -67,7 +67,6 @@ async def run_feedback_enrichment_memify(last_n: int = 5): extraction_tasks=extraction_tasks, enrichment_tasks=enrichment_tasks, data=[{}], # A placeholder to prevent fetching the entire graph - dataset="feedback_enrichment_minimal", ) From 45bb3130c695260af9ccb00e756a0b4d22f0a85b Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 30 Oct 2025 17:40:00 +0100 Subject: [PATCH 09/18] fix: Use same dataset name accross cognee calls --- cognee/tests/test_feedback_enrichment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognee/tests/test_feedback_enrichment.py b/cognee/tests/test_feedback_enrichment.py index 02d90db32..378cb0e45 100644 --- a/cognee/tests/test_feedback_enrichment.py +++ b/cognee/tests/test_feedback_enrichment.py @@ -133,7 +133,7 @@ async def main(): extraction_tasks=extraction_tasks, enrichment_tasks=enrichment_tasks, data=[{}], - dataset="feedback_enrichment_test_memify", + dataset=dataset_name, ) nodes_after, edges_after = await graph_engine.get_graph_data() From 1b483276b0077c57eefdc7e7aa93d58501cf7840 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 30 Oct 2025 18:04:27 +0100 Subject: [PATCH 10/18] fix: disable backend access control for rel db test --- cognee/tests/test_relational_db_migration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cognee/tests/test_relational_db_migration.py b/cognee/tests/test_relational_db_migration.py index 2b69ce854..4557e9e2f 100644 --- a/cognee/tests/test_relational_db_migration.py +++ b/cognee/tests/test_relational_db_migration.py @@ -27,6 +27,9 @@ def normalize_node_name(node_name: str) -> str: async def setup_test_db(): + # Disable backend access control to migrate relational data + os.environ["ENABLE_BACKEND_ACCESS_CONTROL"] = "false" + await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) From 3c09433adead92f8a09093069a6d88de63c28409 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Fri, 31 Oct 2025 13:57:12 +0100 Subject: [PATCH 11/18] fix: Resolve docling test --- cognee/tests/test_add_docling_document.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cognee/tests/test_add_docling_document.py b/cognee/tests/test_add_docling_document.py index 2c82af66f..c5aa4e9d1 100644 --- a/cognee/tests/test_add_docling_document.py +++ b/cognee/tests/test_add_docling_document.py @@ -39,12 +39,12 @@ async def main(): answer = await cognee.search("Do programmers change light bulbs?") assert len(answer) != 0 - lowercase_answer = answer[0].lower() + lowercase_answer = answer[0]["search_result"][0].lower() assert ("no" in lowercase_answer) or ("none" in lowercase_answer) answer = await cognee.search("What colours are there in the presentation table?") assert len(answer) != 0 - lowercase_answer = answer[0].lower() + lowercase_answer = answer[0]["search_result"][0].lower() assert ( ("red" in lowercase_answer) and ("blue" in lowercase_answer) From 00a1fe71d76ae62deac5832751366061f21bc96f Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Fri, 31 Oct 2025 14:33:07 +0100 Subject: [PATCH 12/18] fix: Use multi-user mode search --- examples/python/agentic_reasoning_procurement_example.py | 2 +- examples/python/memify_coding_agent_example.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/python/agentic_reasoning_procurement_example.py b/examples/python/agentic_reasoning_procurement_example.py index 5aa3caa70..4e9d2d7e4 100644 --- a/examples/python/agentic_reasoning_procurement_example.py +++ b/examples/python/agentic_reasoning_procurement_example.py @@ -168,7 +168,7 @@ async def run_procurement_example(): for q in questions: print(f"Question: \n{q}") results = await procurement_system.search_memory(q, search_categories=[category]) - top_answer = results[category][0] + top_answer = results[category][0]["search_result"][0] print(f"Answer: \n{top_answer.strip()}\n") research_notes[category].append({"question": q, "answer": top_answer}) diff --git a/examples/python/memify_coding_agent_example.py b/examples/python/memify_coding_agent_example.py index 1fd3b1528..4a087ba61 100644 --- a/examples/python/memify_coding_agent_example.py +++ b/examples/python/memify_coding_agent_example.py @@ -89,7 +89,7 @@ async def main(): ) print("Coding rules created by memify:") - for coding_rule in coding_rules: + for coding_rule in coding_rules[0]["search_result"][0]: print("- " + coding_rule) # Visualize new graph with added memify context From 4c8b8211979fc12b6e46a911eba1c2e2610187d8 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Fri, 31 Oct 2025 14:55:52 +0100 Subject: [PATCH 13/18] fix: resolve test failing --- cognee/tests/test_search_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognee/tests/test_search_db.py b/cognee/tests/test_search_db.py index e24abd0f5..bcc4529a9 100644 --- a/cognee/tests/test_search_db.py +++ b/cognee/tests/test_search_db.py @@ -146,7 +146,7 @@ async def main(): assert len(search_results) == 1, ( f"{name}: expected single-element list, got {len(search_results)}" ) - text = search_results[0] + text = search_results[0]["search_result"][0] assert isinstance(text, str), f"{name}: element should be a string" assert text.strip(), f"{name}: string should not be empty" assert "netherlands" in text.lower(), ( From f368a1a4d5d79a4485bc52ff5d9f16fc909942d2 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Fri, 31 Oct 2025 20:10:05 +0100 Subject: [PATCH 14/18] fix: set tests to not use multi-user mode --- .github/workflows/search_db_tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/search_db_tests.yml b/.github/workflows/search_db_tests.yml index e3e46dd97..118c1c06c 100644 --- a/.github/workflows/search_db_tests.yml +++ b/.github/workflows/search_db_tests.yml @@ -84,6 +84,7 @@ jobs: GRAPH_DATABASE_PROVIDER: 'neo4j' VECTOR_DB_PROVIDER: 'lancedb' DB_PROVIDER: 'sqlite' + ENABLE_BACKEND_ACCESS_CONTROL: 'false' GRAPH_DATABASE_URL: ${{ steps.neo4j.outputs.neo4j-url }} GRAPH_DATABASE_USERNAME: ${{ steps.neo4j.outputs.neo4j-username }} GRAPH_DATABASE_PASSWORD: ${{ steps.neo4j.outputs.neo4j-password }} @@ -135,6 +136,7 @@ jobs: EMBEDDING_API_VERSION: ${{ secrets.EMBEDDING_API_VERSION }} GRAPH_DATABASE_PROVIDER: 'kuzu' VECTOR_DB_PROVIDER: 'pgvector' + ENABLE_BACKEND_ACCESS_CONTROL: 'false' DB_PROVIDER: 'postgres' DB_NAME: 'cognee_db' DB_HOST: '127.0.0.1' @@ -197,6 +199,7 @@ jobs: GRAPH_DATABASE_URL: ${{ steps.neo4j.outputs.neo4j-url }} GRAPH_DATABASE_USERNAME: ${{ steps.neo4j.outputs.neo4j-username }} GRAPH_DATABASE_PASSWORD: ${{ steps.neo4j.outputs.neo4j-password }} + ENABLE_BACKEND_ACCESS_CONTROL: 'false' DB_NAME: cognee_db DB_HOST: 127.0.0.1 DB_PORT: 5432 From 2ab2cffd07ed35a268362af48c2fb668674509f1 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Mon, 3 Nov 2025 16:37:03 +0100 Subject: [PATCH 15/18] chore: update test_search_db to work with all graph providers --- cognee/tests/test_search_db.py | 8 +++++++- examples/python/simple_example.py | 8 -------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cognee/tests/test_search_db.py b/cognee/tests/test_search_db.py index bcc4529a9..ea3f0ea44 100644 --- a/cognee/tests/test_search_db.py +++ b/cognee/tests/test_search_db.py @@ -146,7 +146,13 @@ async def main(): assert len(search_results) == 1, ( f"{name}: expected single-element list, got {len(search_results)}" ) - text = search_results[0]["search_result"][0] + + from cognee.context_global_variables import check_backend_access_control_mode + + if check_backend_access_control_mode(): + text = search_results[0]["search_result"][0] + else: + text = search_results[0] assert isinstance(text, str), f"{name}: element should be a string" assert text.strip(), f"{name}: string should not be empty" assert "netherlands" in text.lower(), ( diff --git a/examples/python/simple_example.py b/examples/python/simple_example.py index c13e48f85..237a8295e 100644 --- a/examples/python/simple_example.py +++ b/examples/python/simple_example.py @@ -59,14 +59,6 @@ async def main(): for result_text in search_results: print(result_text) - # Example output: - # ({'id': UUID('bc338a39-64d6-549a-acec-da60846dd90d'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 1, 211808, tzinfo=datetime.timezone.utc), 'name': 'natural language processing', 'description': 'An interdisciplinary subfield of computer science and information retrieval.'}, {'relationship_name': 'is_a_subfield_of', 'source_node_id': UUID('bc338a39-64d6-549a-acec-da60846dd90d'), 'target_node_id': UUID('6218dbab-eb6a-5759-a864-b3419755ffe0'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 15, 473137, tzinfo=datetime.timezone.utc)}, {'id': UUID('6218dbab-eb6a-5759-a864-b3419755ffe0'), 'updated_at': datetime.datetime(2024, 11, 21, 12, 23, 1, 211808, tzinfo=datetime.timezone.utc), 'name': 'computer science', 'description': 'The study of computation and information processing.'}) - # (...) - # It represents nodes and relationships in the knowledge graph: - # - The first element is the source node (e.g., 'natural language processing'). - # - The second element is the relationship between nodes (e.g., 'is_a_subfield_of'). - # - The third element is the target node (e.g., 'computer science'). - if __name__ == "__main__": logger = setup_logging(log_level=ERROR) From c81d06d364d3b1f114fb2e7e81db856e7389386d Mon Sep 17 00:00:00 2001 From: Igor Ilic <30923996+dexters1@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:37:52 +0100 Subject: [PATCH 16/18] Update cognee/context_global_variables.py Co-authored-by: Pavel Zorin --- cognee/context_global_variables.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cognee/context_global_variables.py b/cognee/context_global_variables.py index b4b848192..6f3965441 100644 --- a/cognee/context_global_variables.py +++ b/cognee/context_global_variables.py @@ -41,11 +41,7 @@ def check_backend_access_control_mode(): if backend_access_control is None: # If backend access control is not defined in environment variables, # enable it by default if graph and vector DBs can support it, otherwise disable it - multi_user_support = check_multi_user_support() - if multi_user_support: - return True - else: - return False + return check_multi_user_support() elif backend_access_control.lower() == "true": # If enabled, ensure that the current graph and vector DBs can support it multi_user_support = check_multi_user_support() From 53521c2068319d340c3f2b396dbbc7f3f9c80523 Mon Sep 17 00:00:00 2001 From: Igor Ilic <30923996+dexters1@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:42:51 +0100 Subject: [PATCH 17/18] Update cognee/context_global_variables.py Co-authored-by: Pavel Zorin --- cognee/context_global_variables.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cognee/context_global_variables.py b/cognee/context_global_variables.py index 6f3965441..3afbf6ff2 100644 --- a/cognee/context_global_variables.py +++ b/cognee/context_global_variables.py @@ -27,13 +27,10 @@ async def set_session_user_context_variable(user): def check_multi_user_support(): graph_db_config = get_graph_context_config() vector_db_config = get_vectordb_context_config() - if ( + return ( graph_db_config["graph_database_provider"] in graph_dbs_with_multi_user_support and vector_db_config["vector_db_provider"] in vector_dbs_with_multi_user_support - ): - return True - else: - return False + ) def check_backend_access_control_mode(): From 46c509778f89d5bebbcbe5f7578159e45841ca2d Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Tue, 4 Nov 2025 12:06:16 +0100 Subject: [PATCH 18/18] refactor: Rename access control functions --- cognee/context_global_variables.py | 17 +++++++---------- cognee/modules/search/methods/search.py | 6 +++--- .../users/methods/get_authenticated_user.py | 4 ++-- cognee/tests/test_search_db.py | 4 ++-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/cognee/context_global_variables.py b/cognee/context_global_variables.py index 3afbf6ff2..f17c9187a 100644 --- a/cognee/context_global_variables.py +++ b/cognee/context_global_variables.py @@ -24,7 +24,7 @@ async def set_session_user_context_variable(user): session_user.set(user) -def check_multi_user_support(): +def multi_user_support_possible(): graph_db_config = get_graph_context_config() vector_db_config = get_vectordb_context_config() return ( @@ -33,24 +33,21 @@ def check_multi_user_support(): ) -def check_backend_access_control_mode(): +def backend_access_control_enabled(): backend_access_control = os.environ.get("ENABLE_BACKEND_ACCESS_CONTROL", None) if backend_access_control is None: # If backend access control is not defined in environment variables, # enable it by default if graph and vector DBs can support it, otherwise disable it - return check_multi_user_support() + return multi_user_support_possible() elif backend_access_control.lower() == "true": # If enabled, ensure that the current graph and vector DBs can support it - multi_user_support = check_multi_user_support() + multi_user_support = multi_user_support_possible() if not multi_user_support: raise EnvironmentError( "ENABLE_BACKEND_ACCESS_CONTROL is set to true but the current graph and/or vector databases do not support multi-user access control. Please use supported databases or disable backend access control." ) - else: - return True - else: - # If explicitly disabled, return false - return False + return True + return False async def set_database_global_context_variables(dataset: Union[str, UUID], user_id: UUID): @@ -74,7 +71,7 @@ async def set_database_global_context_variables(dataset: Union[str, UUID], user_ base_config = get_base_config() - if not check_backend_access_control_mode(): + if not backend_access_control_enabled(): return user = await get_user(user_id) diff --git a/cognee/modules/search/methods/search.py b/cognee/modules/search/methods/search.py index 4a67093e8..5e465b239 100644 --- a/cognee/modules/search/methods/search.py +++ b/cognee/modules/search/methods/search.py @@ -8,7 +8,7 @@ from cognee.infrastructure.databases.graph import get_graph_engine from cognee.shared.logging_utils import get_logger from cognee.shared.utils import send_telemetry from cognee.context_global_variables import set_database_global_context_variables -from cognee.context_global_variables import check_backend_access_control_mode +from cognee.context_global_variables import backend_access_control_enabled from cognee.modules.engine.models.node_set import NodeSet from cognee.modules.graph.cognee_graph.CogneeGraphElements import Edge @@ -74,7 +74,7 @@ async def search( ) # Use search function filtered by permissions if access control is enabled - if check_backend_access_control_mode(): + if backend_access_control_enabled(): search_results = await authorized_search( query_type=query_type, query_text=query_text, @@ -156,7 +156,7 @@ async def search( ) else: # This is for maintaining backwards compatibility - if check_backend_access_control_mode(): + if backend_access_control_enabled(): return_value = [] for search_result in search_results: prepared_search_results = await prepare_search_result(search_result) diff --git a/cognee/modules/users/methods/get_authenticated_user.py b/cognee/modules/users/methods/get_authenticated_user.py index 3cc16f3a8..d6d701737 100644 --- a/cognee/modules/users/methods/get_authenticated_user.py +++ b/cognee/modules/users/methods/get_authenticated_user.py @@ -5,7 +5,7 @@ from ..models import User from ..get_fastapi_users import get_fastapi_users from .get_default_user import get_default_user from cognee.shared.logging_utils import get_logger -from cognee.context_global_variables import check_backend_access_control_mode +from cognee.context_global_variables import backend_access_control_enabled logger = get_logger("get_authenticated_user") @@ -13,7 +13,7 @@ logger = get_logger("get_authenticated_user") # Check environment variable to determine authentication requirement REQUIRE_AUTHENTICATION = ( os.getenv("REQUIRE_AUTHENTICATION", "false").lower() == "true" - or check_backend_access_control_mode() + or backend_access_control_enabled() ) fastapi_users = get_fastapi_users() diff --git a/cognee/tests/test_search_db.py b/cognee/tests/test_search_db.py index ea3f0ea44..bd11dc62e 100644 --- a/cognee/tests/test_search_db.py +++ b/cognee/tests/test_search_db.py @@ -147,9 +147,9 @@ async def main(): f"{name}: expected single-element list, got {len(search_results)}" ) - from cognee.context_global_variables import check_backend_access_control_mode + from cognee.context_global_variables import backend_access_control_enabled - if check_backend_access_control_mode(): + if backend_access_control_enabled(): text = search_results[0]["search_result"][0] else: text = search_results[0]