Compare commits

...

1 Commits

Author SHA1 Message Date
Dogukan Karatas 3bcdf723b0 feat (api): projects with permissions (#430)
Publish Python Package / test (push) Has been cancelled
Publish Python Package / Build and Publish Python Package (push) Has been cancelled
* adds project with permissions

* removes the project resource with permissions

* fix the tests
2025-06-06 16:07:48 +02:00
10 changed files with 324 additions and 6 deletions
@@ -11,7 +11,11 @@ from specklepy.core.api.models import (
ResourceCollection,
User,
)
from specklepy.core.api.models.current import PermissionCheckResult, Workspace
from specklepy.core.api.models.current import (
PermissionCheckResult,
ProjectWithPermissions,
Workspace,
)
from specklepy.core.api.resources import ActiveUserResource as CoreResource
from specklepy.logging import metrics
@@ -51,6 +55,22 @@ class ActiveUserResource(CoreResource):
metrics.track(metrics.SDK, self.account, {"name": "Active User Get Projects"})
return super().get_projects(limit=limit, cursor=cursor, filter=filter)
def get_projects_with_permissions(
self,
*,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[UserProjectsFilter] = None,
) -> ResourceCollection[ProjectWithPermissions]:
metrics.track(
metrics.SDK,
self.account,
{"name": "Active User Get Projects With Permissions"},
)
return super().get_projects_with_permissions(
limit=limit, cursor=cursor, filter=filter
)
def get_project_invites(self) -> List[PendingStreamCollaborator]:
metrics.track(
metrics.SDK, self.account, {"name": "Active User Get Project Invites"}
@@ -7,7 +7,11 @@ from specklepy.core.api.inputs.project_inputs import (
ProjectUpdateRoleInput,
WorkspaceProjectCreateInput,
)
from specklepy.core.api.models import Project, ProjectWithModels, ProjectWithTeam
from specklepy.core.api.models import (
Project,
ProjectWithModels,
ProjectWithTeam,
)
from specklepy.core.api.models.current import ProjectPermissionChecks
from specklepy.core.api.resources import ProjectResource as CoreResource
from specklepy.logging import metrics
@@ -1,7 +1,12 @@
from typing import Optional
from specklepy.core.api.inputs.project_inputs import WorksaceProjectsFilter
from specklepy.core.api.models.current import Project, ResourceCollection, Workspace
from specklepy.core.api.models.current import (
Project,
ProjectWithPermissions,
ResourceCollection,
Workspace,
)
from specklepy.core.api.resources import WorkspaceResource as CoreResource
from specklepy.logging import metrics
@@ -30,3 +35,19 @@ class WorkspaceResource(CoreResource):
) -> ResourceCollection[Project]:
metrics.track(metrics.SDK, self.account, {"name": "Workspace Get Projects"})
return super().get_projects(workspace_id, limit, cursor, filter)
def get_projects_with_permissions(
self,
workspace_id: str,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[WorksaceProjectsFilter] = None,
) -> ResourceCollection[ProjectWithPermissions]:
metrics.track(
metrics.SDK,
self.account,
{"name": "Workspace Get Projects With Permissions"},
)
return super().get_projects_with_permissions(
workspace_id, limit, cursor, filter
)
@@ -8,6 +8,7 @@ from specklepy.core.api.models.current import (
ProjectCollaborator,
ProjectCommentCollection,
ProjectWithModels,
ProjectWithPermissions,
ProjectWithTeam,
ResourceCollection,
ServerConfiguration,
@@ -39,6 +40,7 @@ __all__ = [
"ModelWithVersions",
"Project",
"ProjectWithModels",
"ProjectWithPermissions",
"ProjectWithTeam",
"ProjectCommentCollection",
"UserSearchResultCollection",
+4
View File
@@ -176,6 +176,10 @@ class ProjectWithModels(Project):
models: ResourceCollection[Model]
class ProjectWithPermissions(Project):
permissions: ProjectPermissionChecks
class ProjectWithTeam(Project):
invited_team: List[PendingStreamCollaborator]
team: List[ProjectCollaborator]
@@ -13,7 +13,11 @@ from specklepy.core.api.models import (
ResourceCollection,
User,
)
from specklepy.core.api.models.current import PermissionCheckResult, Workspace
from specklepy.core.api.models.current import (
PermissionCheckResult,
ProjectWithPermissions,
Workspace,
)
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
from specklepy.logging.exceptions import GraphQLException
@@ -338,3 +342,84 @@ class ActiveUserResource(ResourceBase):
)
return response.data.data
def get_projects_with_permissions(
self,
*,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[UserProjectsFilter] = None,
) -> ResourceCollection[ProjectWithPermissions]:
"""
Gets the currently active user's projects with their permissions.
This is useful for checking what actions can be performed on each project.
"""
QUERY = gql(
"""
query User($limit : Int!, $cursor: String, $filter: UserProjectsFilter) {
data:activeUser {
data:projects(limit: $limit, cursor: $cursor, filter: $filter) {
totalCount
cursor
items {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
permissions {
canCreateModel {
code
authorized
message
}
canDelete {
code
authorized
message
}
canLoad {
code
authorized
message
}
canPublish {
code
authorized
message
}
}
}
}
}
}
"""
)
variables = {
"limit": limit,
"cursor": cursor,
"filter": filter.model_dump(warnings="error", by_alias=True)
if filter
else None,
}
response = self.make_request_and_parse_response(
DataResponse[
Optional[DataResponse[ResourceCollection[ProjectWithPermissions]]]
],
QUERY,
variables,
)
if response.data is None:
raise GraphQLException(
"GraphQL response indicated that the ActiveUser could not be found"
)
return response.data.data
@@ -9,7 +9,11 @@ from specklepy.core.api.inputs.project_inputs import (
ProjectUpdateRoleInput,
WorkspaceProjectCreateInput,
)
from specklepy.core.api.models import Project, ProjectWithModels, ProjectWithTeam
from specklepy.core.api.models import (
Project,
ProjectWithModels,
ProjectWithTeam,
)
from specklepy.core.api.models.current import ProjectPermissionChecks
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
@@ -3,7 +3,12 @@ from typing import Optional
from gql import gql
from specklepy.core.api.inputs.project_inputs import WorksaceProjectsFilter
from specklepy.core.api.models.current import Project, ResourceCollection, Workspace
from specklepy.core.api.models.current import (
Project,
ProjectWithPermissions,
ResourceCollection,
Workspace,
)
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
@@ -104,3 +109,72 @@ class WorkspaceResource(ResourceBase):
return self.make_request_and_parse_response(
DataResponse[DataResponse[ResourceCollection[Project]]], QUERY, variables
).data.data
def get_projects_with_permissions(
self,
workspace_id: str,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[WorksaceProjectsFilter] = None,
) -> ResourceCollection[ProjectWithPermissions]:
QUERY = gql(
"""
query Workspace($workspaceId: String!, $limit: Int!, $cursor: String, $filter: WorkspaceProjectsFilter) {
data:workspace(id: $workspaceId) {
data:projects(limit: $limit, cursor: $cursor, filter: $filter) {
cursor
items {
allowPublicComments
createdAt
description
id
name
role
sourceApps
updatedAt
visibility
workspaceId
permissions {
canCreateModel {
code
authorized
message
}
canDelete {
code
authorized
message
}
canLoad {
code
authorized
message
}
canPublish {
code
authorized
message
}
}
}
totalCount
}
}
}
""" # noqa: E501
)
variables = {
"workspaceId": workspace_id,
"limit": limit,
"cursor": cursor,
"filter": filter.model_dump(warnings="error", by_alias=True)
if filter
else None,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[ResourceCollection[ProjectWithPermissions]]],
QUERY,
variables,
).data.data
@@ -0,0 +1,85 @@
import pytest
from specklepy.api.client import SpeckleClient
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter
from specklepy.core.api.models.current import (
Project,
ProjectWithPermissions,
ResourceCollection,
)
@pytest.mark.run()
class TestActiveUserResourcePermissions:
@pytest.fixture()
def test_project(self, client: SpeckleClient) -> Project:
project = client.project.create(
ProjectCreateInput(
name="test project for active user permissions",
description="test description",
visibility=None,
)
)
return project
def test_active_user_get_projects_with_permissions(
self, client: SpeckleClient, test_project: Project
):
result = client.active_user.get_projects_with_permissions()
assert isinstance(result, ResourceCollection)
assert len(result.items) >= 1
test_project_with_permissions = None
for project in result.items:
if project.id == test_project.id:
test_project_with_permissions = project
break
assert test_project_with_permissions is not None
assert isinstance(test_project_with_permissions, ProjectWithPermissions)
assert hasattr(test_project_with_permissions, "permissions")
assert test_project_with_permissions.permissions is not None
assert test_project_with_permissions.id == test_project.id
assert test_project_with_permissions.name == test_project.name
permissions = test_project_with_permissions.permissions
assert hasattr(permissions, "can_create_model")
assert hasattr(permissions, "can_delete")
assert hasattr(permissions, "can_load")
assert hasattr(permissions, "can_publish")
assert permissions.can_create_model.authorized is True
assert permissions.can_delete.authorized is True
assert permissions.can_load.authorized is True
assert permissions.can_publish.authorized is True
def test_active_user_get_projects_with_permissions_with_filter(
self, client: SpeckleClient, test_project: Project
):
"""test getting active user's projects with permissions using a filter."""
filter = UserProjectsFilter(search=test_project.name)
result = client.active_user.get_projects_with_permissions(filter=filter)
assert isinstance(result, ResourceCollection)
assert len(result.items) >= 1
assert result.total_count >= 1
project_with_permissions = result.items[0]
assert isinstance(project_with_permissions, ProjectWithPermissions)
assert project_with_permissions.id == test_project.id
assert hasattr(project_with_permissions, "permissions")
assert project_with_permissions.permissions is not None
def test_active_user_projects_with_permissions_method_exists(
self, client: SpeckleClient
):
"""test that the method exists and is callable on active user resource."""
assert hasattr(client.active_user, "get_projects_with_permissions")
method = client.active_user.get_projects_with_permissions
assert callable(method)
@@ -0,0 +1,19 @@
import pytest
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import GraphQLException
@pytest.mark.run()
class TestWorkspaceResourcePermissions:
def test_get_projects_with_permissions(self, client: SpeckleClient):
with pytest.raises(GraphQLException):
client.workspace.get_projects_with_permissions("not a real id")
def test_get_projects_with_permissions_method_exists(self, client: SpeckleClient):
"""
test that the method exists with the correct signature.
"""
assert hasattr(client.workspace, "get_projects_with_permissions")
method = client.workspace.get_projects_with_permissions
assert callable(method)