Compare commits

...
Sign in to create a new pull request.

11 commits

Author SHA1 Message Date
hajdul88
b32d29e908
Merge branch 'dev' into bug-KuzuAdpter 2025-09-18 11:55:36 +02:00
hajdul88
626897387d ruff format 2025-09-18 11:55:13 +02:00
hajdul88
16243e5495
Merge branch 'dev' into bug-KuzuAdpter 2025-09-18 10:32:11 +02:00
Chaitany
fdeeb7ea24
Merge branch 'main' into bug-KuzuAdpter 2025-09-18 02:46:41 +05:30
patelchaitany
b6ba7c09d6 This commit is realated to issue #1436 and I removed that bug and the
output format is same as the othere adpaters get_filtered_graph_data.
2025-09-18 02:10:12 +05:30
Vasilije
9de69d2bab
docs: Add docstrings for permission related functions. (#1427)
<!-- .github/pull_request_template.md -->

## Description
<!-- 
Please provide a clear, human-generated description of the changes in
this PR.
DO NOT use AI-generated descriptions. We want to understand your thought
process and reasoning.
-->
Added docs that were missing from permission related functions in the
code.

## Type of Change
<!-- Please check the relevant option -->
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Code refactoring
- [ ] Performance improvement
- [ ] Other (please specify):

## Changes Made
<!-- List the specific changes made in this PR -->
- 
- 
- 

## Testing
<!-- Describe how you tested your changes -->

## Screenshots/Videos (if applicable)
<!-- Add screenshots or videos to help explain your changes -->

## Pre-submission Checklist
<!-- Please check all boxes that apply before submitting your PR -->
- [ ] **I have tested my changes thoroughly before submitting this PR**
- [ ] **This PR contains minimal changes necessary to address the
issue/feature**
- [ ] My code follows the project's coding standards and style
guidelines
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have added necessary documentation (if applicable)
- [ ] All new and existing tests pass
- [ ] I have searched existing PRs to ensure this change hasn't been
submitted already
- [ ] I have linked any relevant issues in the description
- [ ] My commits have clear and descriptive messages

## Related Issues
<!-- Link any related issues using "Fixes #issue_number" or "Relates to
#issue_number" -->

## Additional Notes
<!-- Add any additional notes, concerns, or context for reviewers -->

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.
2025-09-17 13:36:47 -07:00
Vasilije
50c5bd96e3
docs: Updated some docs, not a lot was necessary (#1428)
<!-- .github/pull_request_template.md -->

## Description
<!-- 
Please provide a clear, human-generated description of the changes in
this PR.
DO NOT use AI-generated descriptions. We want to understand your thought
process and reasoning.
-->
Updated very little docs, since the two files I was supposed to look at
already have docs written. Expanding these would be unnecessary imo, not
a lot of value can be added.

## Type of Change
<!-- Please check the relevant option -->
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Code refactoring
- [ ] Performance improvement
- [ ] Other (please specify):

## Changes Made
<!-- List the specific changes made in this PR -->
- 
- 
- 

## Testing
<!-- Describe how you tested your changes -->

## Screenshots/Videos (if applicable)
<!-- Add screenshots or videos to help explain your changes -->

## Pre-submission Checklist
<!-- Please check all boxes that apply before submitting your PR -->
- [ ] **I have tested my changes thoroughly before submitting this PR**
- [ ] **This PR contains minimal changes necessary to address the
issue/feature**
- [ ] My code follows the project's coding standards and style
guidelines
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have added necessary documentation (if applicable)
- [ ] All new and existing tests pass
- [ ] I have searched existing PRs to ensure this change hasn't been
submitted already
- [ ] I have linked any relevant issues in the description
- [ ] My commits have clear and descriptive messages

## Related Issues
<!-- Link any related issues using "Fixes #issue_number" or "Relates to
#issue_number" -->

## Additional Notes
<!-- Add any additional notes, concerns, or context for reviewers -->

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.
2025-09-17 13:35:29 -07:00
Andrej Milićević
f4b22dfe04
Merge branch 'main' into feature/cog-2427-add-docstrings-to-permission-related-functions 2025-09-17 18:13:24 +02:00
Andrej Milicevic
475749b8de docs: Updated some docs, not a lot was necessary 2025-09-17 11:47:37 +02:00
Andrej Milicevic
293a0e0053 Fix formatiing 2025-09-17 10:45:36 +02:00
Andrej Milicevic
30df102656 docs: Add docstrings for permission related functions. 2025-09-17 10:42:11 +02:00
24 changed files with 237 additions and 11 deletions

View file

@ -1277,7 +1277,6 @@ class KuzuAdapter(GraphDBInterface):
A tuple containing a list of filtered node properties and a list of filtered edge
properties.
"""
where_clauses = []
params = {}
@ -1288,16 +1287,50 @@ class KuzuAdapter(GraphDBInterface):
params[param_name] = values
where_clause = " AND ".join(where_clauses)
nodes_query = f"MATCH (n:Node) WHERE {where_clause} RETURN properties(n)"
nodes_query = (
f"MATCH (n:Node) WHERE {where_clause} RETURN n.id, {{properties: n.properties}}"
)
edges_query = f"""
MATCH (n1:Node)-[r:EDGE]->(n2:Node)
WHERE {where_clause.replace("n.", "n1.")} AND {where_clause.replace("n.", "n2.")}
RETURN properties(r)
RETURN n1.id, n2.id, r.relationship_name, r.properties
"""
nodes, edges = await asyncio.gather(
self.query(nodes_query, params), self.query(edges_query, params)
)
return ([n[0] for n in nodes], [e[0] for e in edges])
formatted_nodes = []
for n in nodes:
if n[0]:
node_id = str(n[0])
props = n[1]
if props.get("properties"):
try:
additional_props = json.loads(props["properties"])
props.update(additional_props)
del props["properties"]
except json.JSONDecodeError:
logger.warning(f"Failed to parse properties JSON for node {node_id}")
formatted_nodes.append((node_id, props))
if not formatted_nodes:
logger.warning("No nodes found in the database")
return [], []
formatted_edges = []
for e in edges:
if e and len(e) >= 3:
source_id = str(e[0])
target_id = str(e[1])
rel_type = str(e[2])
props = {}
if len(e) > 3 and e[3]:
try:
props = json.loads(e[3])
except (json.JSONDecodeError, TypeError):
logger.warning(
f"Failed to parse edge properties for {source_id}->{target_id}"
)
formatted_edges.append((source_id, target_id, rel_type, props))
return formatted_nodes, formatted_edges
async def get_graph_metrics(self, include_optional=False) -> Dict[str, Any]:
"""

View file

@ -6,6 +6,15 @@ from .create_dataset import create_dataset
async def create_authorized_dataset(dataset_name: str, user: User) -> Dataset:
"""
Create a new dataset and give all permissions on this dataset to the given user.
Args:
dataset_name: Name of the dataset.
user: The user object.
Returns:
Dataset: The new authorized dataset.
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -15,7 +15,7 @@ async def get_authorized_dataset(
Get a specific dataset with permissions for a user.
Args:
user_id (UUID): user id
user: User object
dataset_id (UUID): dataset id
permission_type (str): permission type(read, write, delete, share), default is read

View file

@ -11,6 +11,17 @@ from ..models import Dataset
async def get_authorized_dataset_by_name(
dataset_name: str, user: User, permission_type: str
) -> Optional[Dataset]:
"""
Get a specific dataset with the given name, with permissions for a given user.
Args:
dataset_name: Name of the dataset.
user: User object.
permission_type (str): permission type(read, write, delete, share), default is read
Returns:
Optional[Dataset]: dataset with permissions
"""
authorized_datasets = await get_authorized_existing_datasets([], permission_type, user)
return next((dataset for dataset in authorized_datasets if dataset.name == dataset_name), None)

View file

@ -23,8 +23,6 @@ async def retrieve_existing_edges(
chunk_graphs (list[KnowledgeGraph]): List of knowledge graphs corresponding to each
data chunk. Each graph contains nodes (entities) and edges (relationships) that
were extracted from the chunk content.
graph_engine (GraphDBInterface): Interface to the graph database that will be queried
to check for existing edges. Must implement the has_edges() method.
Returns:
dict[str, bool]: A mapping of edge keys to boolean values indicating existence.

View file

@ -11,6 +11,19 @@ from cognee.modules.data.methods import (
async def resolve_authorized_user_dataset(dataset_id: UUID, dataset_name: str, user: User):
"""
Function handles creation and dataset authorization if dataset already exist for Cognee.
Verifies that provided user has necessary permission for provided Dataset.
If Dataset does not exist creates the Dataset and gives permission for the user creating the dataset.
Args:
dataset_id: Id of the dataset.
dataset_name: Name of the dataset.
user: Cognee User request is being processed for, if None default user will be used.
Returns:
Tuple[User, Dataset]: A tuple containing the user and the authorized dataset.
"""
if not user:
user = await get_default_user()

View file

@ -25,7 +25,7 @@ async def resolve_authorized_user_datasets(
datasets: Dataset names or Dataset UUID (in case Datasets already exist)
Returns:
Tuple[User, List[Dataset]]: A tuple containing the user and the list of authorized datasets.
"""
# If no user is provided use default user
if user is None:

View file

@ -9,6 +9,18 @@ from uuid import UUID
async def authorized_give_permission_on_datasets(
principal_id: UUID, dataset_ids: Union[List[UUID], UUID], permission_name: str, owner_id: UUID
):
"""
Give permission to certain datasets to a user.
The request owner must have the necessary permission to share the datasets.
Args:
principal_id: Id of user to whom datasets are shared
dataset_ids: Ids of datasets to share
permission_name: Name of permission to give
owner_id: Id of the request owner
Returns:
None
"""
# If only a single dataset UUID is provided transform it to a list
if not isinstance(dataset_ids, list):
dataset_ids = [dataset_ids]

View file

@ -10,6 +10,17 @@ logger = get_logger()
async def check_permission_on_dataset(user: User, permission_type: str, dataset_id: UUID):
"""
Check if a user has a specific permission on a dataset.
Args:
user: User whose permission is checked
permission_type: Type of permission to check
dataset_id: Id of the dataset
Returns:
None
"""
if user is None:
user = await get_default_user()

View file

@ -11,6 +11,16 @@ logger = get_logger()
async def get_all_user_permission_datasets(user: User, permission_type: str) -> list[Dataset]:
"""
Return a list of datasets the user has permission for.
If the user is part of a tenant, return datasets his roles have permission for.
Args:
user
permission_type
Returns:
list[Dataset]: List of datasets user has permission for
"""
datasets = list()
# Get all datasets User has explicit access to
datasets.extend(await get_principal_datasets(user, permission_type))

View file

@ -8,6 +8,16 @@ from ...models import ACL, Permission
async def get_document_ids_for_user(user_id: UUID, datasets: list[str] = None) -> list[str]:
"""
Return a list of documents ids for which the user has read permission.
If datasets are specified, return only documents from those datasets.
Args:
user_id: Id of the user
datasets: List of datasets
Returns:
list[str]: List of documents for which the user has read permission
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -6,6 +6,15 @@ from ...models.Principal import Principal
async def get_principal(principal_id: UUID):
"""
Return information about a user based on their id
Args:
principal_id: Id of the user
Returns:
principal: Information about the user (principal)
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -9,6 +9,17 @@ from ...models.ACL import ACL
async def get_principal_datasets(principal: Principal, permission_type: str) -> list[Dataset]:
"""
Return a list of datasets for which the user (principal) has a certain permission.
Args:
principal: Information about the user
permission_type: Type of permission
Returns:
list[Dataset]: List of datasets for which the user (principal)
has the permission (permission_type).
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -9,6 +9,16 @@ from ...models.Role import Role
async def get_role(tenant_id: UUID, role_name: str):
"""
Return the role with the name role_name of the given tenant.
Args:
tenant_id: Id of the given tenant
role_name: Name of the role
Returns
The role for the given tenant.
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -15,9 +15,9 @@ async def get_specific_user_permission_datasets(
Return a list of datasets user has given permission for. If a list of datasets is provided,
verify for which datasets user has appropriate permission for and return list of datasets he has permission for.
Args:
user_id:
permission_type:
dataset_ids:
user_id: Id of the user.
permission_type: Type of the permission.
dataset_ids: Ids of the provided datasets
Returns:
list[Dataset]: List of datasets user has permission for

View file

@ -8,6 +8,15 @@ from ...models.Tenant import Tenant
async def get_tenant(tenant_id: UUID):
"""
Return information about the tenant based on the given id.
Args:
tenant_id: Id of the given tenant
Returns
Information about the given tenant.
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -16,6 +16,15 @@ from cognee.modules.users.models import (
async def give_default_permission_to_role(role_id: UUID, permission_name: str):
"""
Give the permission with given name to the role with the given id as a default permission.
Args:
role_id: Id of the role
permission_name: Name of the permission
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -16,6 +16,15 @@ from cognee.modules.users.models import (
async def give_default_permission_to_tenant(tenant_id: UUID, permission_name: str):
"""
Give the permission with given name to the tenant with the given id as a default permission.
Args:
tenant_id: Id of the tenant
permission_name: Name of the permission
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:
tenant = (

View file

@ -16,6 +16,15 @@ from cognee.modules.users.models import (
async def give_default_permission_to_user(user_id: UUID, permission_name: str):
"""
Give the permission with given name to the user with the given id as a default permission.
Args:
user_id: Id of the tenant
permission_name: Name of the permission
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:
user = (await session.execute(select(User).where(User.id == user_id))).scalars().first()

View file

@ -24,6 +24,16 @@ async def give_permission_on_dataset(
dataset_id: UUID,
permission_name: str,
):
"""
Give a specific permission on a dataset to a user.
Args:
principal: User who is being given the permission on the dataset
dataset_id: Id of the dataset
permission_name: Name of permission to give
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:

View file

@ -21,6 +21,17 @@ from cognee.modules.users.models import (
async def add_user_to_role(user_id: UUID, role_id: UUID, owner_id: UUID):
"""
Add a user with the given id to the role with the given id.
Args:
user_id: Id of the user.
role_id: Id of the role.
owner_id: Id of the request owner.
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:
user = (await session.execute(select(User).where(User.id == user_id))).scalars().first()

View file

@ -16,6 +16,16 @@ async def create_role(
role_name: str,
owner_id: UUID,
):
"""
Create a new role with the given name, if the request owner with the given id
has the necessary permission.
Args:
role_name: Name of the new role.
owner_id: Id of the request owner.
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:
user = await get_user(owner_id)

View file

@ -13,6 +13,18 @@ from cognee.modules.users.exceptions import (
async def add_user_to_tenant(user_id: UUID, tenant_id: UUID, owner_id: UUID):
"""
Add a user with the given id to the tenant with the given id.
This can only be successful if the request owner with the given id is the tenant owner.
Args:
user_id: Id of the user.
tenant_id: Id of the tenant.
owner_id: Id of the request owner.
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:
user = await get_user(user_id)

View file

@ -8,6 +8,16 @@ from cognee.modules.users.methods import get_user
async def create_tenant(tenant_name: str, user_id: UUID):
"""
Create a new tenant with the given name, for the user with the given id.
This user is the owner of the tenant.
Args:
tenant_name: Name of the new tenant.
user_id: Id of the user.
Returns:
None
"""
db_engine = get_relational_engine()
async with db_engine.get_async_session() as session:
try: