diff --git a/src/speckle_automate/automation_context.py b/src/speckle_automate/automation_context.py index d42ead13..19b0f6f4 100644 --- a/src/speckle_automate/automation_context.py +++ b/src/speckle_automate/automation_context.py @@ -5,7 +5,7 @@ import time from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Union import httpx from gql import gql @@ -20,7 +20,9 @@ ) from specklepy.api import operations from specklepy.api.client import SpeckleClient -from specklepy.core.api.models import Branch +from specklepy.core.api.inputs.model_inputs import CreateModelInput +from specklepy.core.api.inputs.version_inputs import CreateVersionInput +from specklepy.core.api.models.current import Model, Version from specklepy.logging.exceptions import SpeckleException from specklepy.objects.base import Base from specklepy.transports.memory import MemoryTransport @@ -101,22 +103,23 @@ def receive_version(self) -> Base: # TODO: this is a quick hack to keep implementation consistency. # Move to proper receive many versions version_id = self.automation_run_data.triggers[0].payload.version_id - commit = self.speckle_client.commit.get( - self.automation_run_data.project_id, version_id - ) - if not commit or not commit.referencedObject: + try: + version = self.speckle_client.version.get( + version_id, self.automation_run_data.project_id + ) + except SpeckleException as err: raise ValueError( f"""\ Could not receive specified version. - {"The commit has no referencedObject." if not commit.referencedObject else ""} Is your environment configured correctly? project_id: {self.automation_run_data.project_id} model_id: {self.automation_run_data.triggers[0].payload.model_id} version_id: {self.automation_run_data.triggers[0].payload.version_id} """ - ) + ) from err + base = operations.receive( - commit.referencedObject, self._server_transport, self._memory_transport + version.referenced_object, self._server_transport, self._memory_transport ) print( f"It took {self.elapsed():.2f} seconds to receive", @@ -124,45 +127,48 @@ def receive_version(self) -> Base: ) return base + def create_new_model_in_project( + self, model_name: str, model_description: Optional[str] = None + ) -> Model: + input = CreateModelInput( + name=model_name, + description=model_description, + project_id=self.automation_run_data.project_id, + ) + + return self.speckle_client.model.create(input) + + def get_model(self, model_id: str) -> Model: + """ + Args: + model_id (str): The id of the model to get + """ + return self.speckle_client.model.get( + model_id, self.automation_run_data.project_id + ) + def create_new_version_in_project( - self, root_object: Base, model_name: str, version_message: str = "" - ) -> Tuple[str, str]: + self, root_object: Base, model_id: str, version_message: str = "" + ) -> Version: """Save a base model to a new version on the project. Args: root_object (Base): The Speckle base object for the new version. - model_id (str): For now please use a `branchName`! + model_id (str): Id of model to create the new version on. version_message (str): The message for the new version. """ - branch = self.speckle_client.branch.get( - self.automation_run_data.project_id, model_name, 1 - ) - if isinstance(branch, Branch): - if not branch.id: - raise ValueError("Cannot use the branch without its id") - matching_trigger = [ - t - for t in self.automation_run_data.triggers - if t.payload.model_id == branch.id - ] - if matching_trigger: - raise ValueError( - f"The target model: {model_name} cannot match the model" - f" that triggered this automation:" - f" {matching_trigger[0].payload.model_id}" - ) - model_id = branch.id - - else: - # we just check if it exists - branch_create = self.speckle_client.branch.create( - self.automation_run_data.project_id, - model_name, + matching_trigger = [ + t + for t in self.automation_run_data.triggers + if t.payload.model_id == model_id + ] + if matching_trigger: + raise ValueError( + f"The target model: {model_id} cannot match the model" + f" that triggered this automation:" + f" {matching_trigger[0].payload.model_id}" ) - if isinstance(branch_create, Exception): - raise branch_create - model_id = branch_create root_object_id = operations.send( root_object, @@ -170,19 +176,17 @@ def create_new_version_in_project( use_default_cache=False, ) - version_id = self.speckle_client.commit.create( - stream_id=self.automation_run_data.project_id, + create_version_input = CreateVersionInput( object_id=root_object_id, - branch_name=model_name, + model_id=model_id, + project_id=self.automation_run_data.project_id, message=version_message, source_application="SpeckleAutomate", ) + version = self.speckle_client.version.create(create_version_input) - if isinstance(version_id, SpeckleException): - raise version_id - - self._automation_result.result_versions.append(version_id) - return model_id, version_id + self._automation_result.result_versions.append(version.id) + return version @property def context_view(self) -> Optional[str]: diff --git a/src/specklepy/api/resources/current/version_resource.py b/src/specklepy/api/resources/current/version_resource.py index f3d48da1..d1a946fc 100644 --- a/src/specklepy/api/resources/current/version_resource.py +++ b/src/specklepy/api/resources/current/version_resource.py @@ -42,7 +42,7 @@ def get_versions( model_id, project_id, limit=limit, cursor=cursor, filter=filter ) - def create(self, input: CreateVersionInput) -> str: + def create(self, input: CreateVersionInput) -> Version: metrics.track(metrics.SDK, self.account, {"name": "Version Create"}) return super().create(input) diff --git a/src/specklepy/core/api/credentials.py b/src/specklepy/core/api/credentials.py index 9999ce22..8f3eb66c 100644 --- a/src/specklepy/core/api/credentials.py +++ b/src/specklepy/core/api/credentials.py @@ -150,7 +150,7 @@ def get_accounts_for_server(host: str) -> List[Account]: for acc in all_accounts: moved_from = ( - acc.serverInfo.migration.movedFrom if acc.serverInfo.migration else None + acc.serverInfo.migration.moved_from if acc.serverInfo.migration else None ) if moved_from and host == urlparse(moved_from).netloc: diff --git a/src/specklepy/core/api/inputs/model_inputs.py b/src/specklepy/core/api/inputs/model_inputs.py index 0121fc02..2467e321 100644 --- a/src/specklepy/core/api/inputs/model_inputs.py +++ b/src/specklepy/core/api/inputs/model_inputs.py @@ -1,26 +1,26 @@ from typing import Optional, Sequence -from pydantic import BaseModel +from specklepy.core.api.models.graphql_base_model import GraphQLBaseModel -class CreateModelInput(BaseModel): +class CreateModelInput(GraphQLBaseModel): name: str description: Optional[str] = None - projectId: str + project_id: str -class DeleteModelInput(BaseModel): +class DeleteModelInput(GraphQLBaseModel): id: str - projectId: str + project_id: str -class UpdateModelInput(BaseModel): +class UpdateModelInput(GraphQLBaseModel): id: str name: Optional[str] = None description: Optional[str] = None - projectId: str + project_id: str -class ModelVersionsFilter(BaseModel): - priorityIds: Sequence[str] - priorityIdsOnly: Optional[bool] = None +class ModelVersionsFilter(GraphQLBaseModel): + priority_ids: Sequence[str] + priority_ids_only: Optional[bool] = None diff --git a/src/specklepy/core/api/inputs/project_inputs.py b/src/specklepy/core/api/inputs/project_inputs.py index 89f46deb..2b87358e 100644 --- a/src/specklepy/core/api/inputs/project_inputs.py +++ b/src/specklepy/core/api/inputs/project_inputs.py @@ -1,47 +1,46 @@ from typing import Optional, Sequence -from pydantic import BaseModel - from specklepy.core.api.enums import ProjectVisibility +from specklepy.core.api.models.graphql_base_model import GraphQLBaseModel -class ProjectCreateInput(BaseModel): +class ProjectCreateInput(GraphQLBaseModel): name: Optional[str] description: Optional[str] visibility: Optional[ProjectVisibility] -class ProjectInviteCreateInput(BaseModel): +class ProjectInviteCreateInput(GraphQLBaseModel): email: Optional[str] role: Optional[str] - serverRole: Optional[str] + server_role: Optional[str] userId: Optional[str] -class ProjectInviteUseInput(BaseModel): +class ProjectInviteUseInput(GraphQLBaseModel): accept: bool - projectId: str + project_id: str token: str -class ProjectModelsFilter(BaseModel): +class ProjectModelsFilter(GraphQLBaseModel): contributors: Optional[Sequence[str]] = None - excludeIds: Optional[Sequence[str]] = None + exclude_ids: Optional[Sequence[str]] = None ids: Optional[Sequence[str]] = None - onlyWithVersions: Optional[bool] = None + only_with_versions: Optional[bool] = None search: Optional[str] = None - sourceApps: Optional[Sequence[str]] = None + source_apps: Optional[Sequence[str]] = None -class ProjectUpdateInput(BaseModel): +class ProjectUpdateInput(GraphQLBaseModel): id: str name: Optional[str] = None description: Optional[str] = None - allowPublicComments: Optional[bool] = None + allow_public_comments: Optional[bool] = None visibility: Optional[ProjectVisibility] = None -class ProjectUpdateRoleInput(BaseModel): - userId: str - projectId: str +class ProjectUpdateRoleInput(GraphQLBaseModel): + user_id: str + project_id: str role: Optional[str] diff --git a/src/specklepy/core/api/inputs/user_inputs.py b/src/specklepy/core/api/inputs/user_inputs.py index 27898444..5ff29bf5 100644 --- a/src/specklepy/core/api/inputs/user_inputs.py +++ b/src/specklepy/core/api/inputs/user_inputs.py @@ -1,15 +1,15 @@ from typing import Optional, Sequence -from pydantic import BaseModel +from specklepy.core.api.models.graphql_base_model import GraphQLBaseModel -class UserUpdateInput(BaseModel): +class UserUpdateInput(GraphQLBaseModel): avatar: Optional[str] = None bio: Optional[str] = None company: Optional[str] = None name: Optional[str] = None -class UserProjectsFilter(BaseModel): +class UserProjectsFilter(GraphQLBaseModel): search: str - onlyWithRoles: Optional[Sequence[str]] = None + only_with_roles: Optional[Sequence[str]] = None diff --git a/src/specklepy/core/api/inputs/version_inputs.py b/src/specklepy/core/api/inputs/version_inputs.py index 4b169091..d5ab7e5b 100644 --- a/src/specklepy/core/api/inputs/version_inputs.py +++ b/src/specklepy/core/api/inputs/version_inputs.py @@ -1,37 +1,37 @@ from typing import Optional, Sequence -from pydantic import BaseModel +from specklepy.core.api.models.graphql_base_model import GraphQLBaseModel -class UpdateVersionInput(BaseModel): - versionId: str - projectId: str +class UpdateVersionInput(GraphQLBaseModel): + version_id: str + project_id: str message: Optional[str] -class MoveVersionsInput(BaseModel): - targetModelName: str - versionIds: Sequence[str] - projectId: str +class MoveVersionsInput(GraphQLBaseModel): + target_model_name: str + version_ids: Sequence[str] + project_id: str -class DeleteVersionsInput(BaseModel): - versionIds: Sequence[str] - projectId: str +class DeleteVersionsInput(GraphQLBaseModel): + version_ids: Sequence[str] + project_id: str -class CreateVersionInput(BaseModel): - objectId: str - modelId: str - projectId: str +class CreateVersionInput(GraphQLBaseModel): + object_id: str + model_id: str + project_id: str message: Optional[str] = None - sourceApplication: Optional[str] = "py" - totalChildrenCount: Optional[int] = None + source_application: Optional[str] = "py" + total_children_count: Optional[int] = None parents: Optional[Sequence[str]] = None -class MarkReceivedVersionInput(BaseModel): - versionId: str - projectId: str - sourceApplication: str +class MarkReceivedVersionInput(GraphQLBaseModel): + version_id: str + project_id: str + source_application: str message: Optional[str] = None diff --git a/src/specklepy/core/api/models/current.py b/src/specklepy/core/api/models/current.py index 65e5606c..3101263b 100644 --- a/src/specklepy/core/api/models/current.py +++ b/src/specklepy/core/api/models/current.py @@ -1,15 +1,14 @@ from datetime import datetime from typing import Generic, List, Optional, TypeVar -from pydantic import BaseModel - from specklepy.core.api.enums import ProjectVisibility from specklepy.core.api.models.deprecated import Streams +from specklepy.core.api.models.graphql_base_model import GraphQLBaseModel T = TypeVar("T") -class User(BaseModel): +class User(GraphQLBaseModel): id: str email: Optional[str] = None name: str @@ -30,18 +29,18 @@ def __str__(self) -> str: return self.__repr__() -class ResourceCollection(BaseModel, Generic[T]): - totalCount: int +class ResourceCollection(GraphQLBaseModel, Generic[T]): + total_count: int items: List[T] cursor: Optional[str] = None -class ServerMigration(BaseModel): - movedFrom: Optional[str] - movedTo: Optional[str] +class ServerMigration(GraphQLBaseModel): + moved_from: Optional[str] + moved_to: Optional[str] -class AuthStrategy(BaseModel): +class AuthStrategy(GraphQLBaseModel): color: Optional[str] icon: str id: str @@ -49,24 +48,24 @@ class AuthStrategy(BaseModel): url: str -class ServerConfiguration(BaseModel): - blobSizeLimitBytes: int - objectMultipartUploadSizeLimitBytes: int - objectSizeLimitBytes: int +class ServerConfiguration(GraphQLBaseModel): + blob_size_limit_bytes: int + object_multipart_upload_size_limit_bytes: int + object_size_limit_bytes: int # Keeping this one all Optionals at the minute, # because its used both as a deserialization model for GQL and Account Management -class ServerInfo(BaseModel): +class ServerInfo(GraphQLBaseModel): name: Optional[str] = None company: Optional[str] = None url: Optional[str] = None - adminContact: Optional[str] = None + admin_contact: Optional[str] = None description: Optional[str] = None - canonicalUrl: Optional[str] = None + canonical_url: Optional[str] = None roles: Optional[List[dict]] = None scopes: Optional[List[dict]] = None - authStrategies: Optional[List[dict]] = None + auth_strategies: Optional[List[dict]] = None version: Optional[str] = None frontend2: Optional[bool] = None migration: Optional[ServerMigration] = None @@ -74,7 +73,7 @@ class ServerInfo(BaseModel): # TODO separate gql model from account management model -class LimitedUser(BaseModel): +class LimitedUser(GraphQLBaseModel): """Limited user type, for showing public info about a user to another user.""" id: str @@ -86,23 +85,23 @@ class LimitedUser(BaseModel): role: Optional[str] -class PendingStreamCollaborator(BaseModel): +class PendingStreamCollaborator(GraphQLBaseModel): id: str - inviteId: str - streamId: Optional[str] = None + invite_id: str + stream_id: Optional[str] = None projectId: str - streamName: Optional[str] = None - projectName: str + stream_name: Optional[str] = None + project_name: str title: str role: str - invitedBy: LimitedUser + invited_by: LimitedUser user: Optional[LimitedUser] = None token: Optional[str] def __repr__(self): return ( - f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId:" - f" {self.streamId}, role: {self.role}, title: {self.title}, invitedBy:" + f"PendingStreamCollaborator( inviteId: {self.invite_id}, streamId:" + f" {self.stream_id}, role: {self.role}, title: {self.title}, invitedBy:" f" {self.user.name if self.user else None})" ) @@ -110,48 +109,48 @@ def __str__(self) -> str: return self.__repr__() -class ProjectCollaborator(BaseModel): +class ProjectCollaborator(GraphQLBaseModel): id: str role: str user: LimitedUser -class Version(BaseModel): - authorUser: Optional[LimitedUser] - createdAt: datetime +class Version(GraphQLBaseModel): + author_user: Optional[LimitedUser] + created_at: datetime id: str message: Optional[str] - previewUrl: str - referencedObject: str - sourceApplication: Optional[str] + preview_url: str + referenced_object: str + source_application: Optional[str] -class Model(BaseModel): +class Model(GraphQLBaseModel): author: Optional[LimitedUser] - createdAt: datetime + created_at: datetime description: Optional[str] - displayName: str + display_name: str id: str name: str - previewUrl: Optional[str] - updatedAt: datetime + preview_url: Optional[str] + updated_at: datetime class ModelWithVersions(Model): versions: ResourceCollection[Version] -class Project(BaseModel): - allowPublicComments: bool - createdAt: datetime +class Project(GraphQLBaseModel): + allow_public_comments: bool + created_at: datetime description: Optional[str] id: str name: str role: Optional[str] - sourceApps: List[str] - updatedAt: datetime + source_apps: List[str] + updated_at: datetime visibility: ProjectVisibility - workspaceId: Optional[str] + workspace_id: Optional[str] class ProjectWithModels(Project): @@ -159,14 +158,14 @@ class ProjectWithModels(Project): class ProjectWithTeam(Project): - invitedTeam: List[PendingStreamCollaborator] + invited_team: List[PendingStreamCollaborator] team: List[ProjectCollaborator] class ProjectCommentCollection(ResourceCollection[T], Generic[T]): - totalArchivedCount: int + total_archived_count: int -class UserSearchResultCollection(BaseModel): +class UserSearchResultCollection(GraphQLBaseModel): items: List[LimitedUser] cursor: Optional[str] = None diff --git a/src/specklepy/core/api/models/graphql_base_model.py b/src/specklepy/core/api/models/graphql_base_model.py new file mode 100644 index 00000000..a47ed374 --- /dev/null +++ b/src/specklepy/core/api/models/graphql_base_model.py @@ -0,0 +1,17 @@ +from pydantic import AliasGenerator, BaseModel, ConfigDict +from pydantic.alias_generators import to_camel + + +class GraphQLBaseModel(BaseModel): + """ + Parent class for all GraphQL Object Model classes + Sets-up a pydantic config to serialize properties using a camel case alias + """ + + model_config = ConfigDict( + alias_generator=AliasGenerator( + serialization_alias=to_camel, + validation_alias=to_camel, + ), + populate_by_name=True, + ) diff --git a/src/specklepy/core/api/models/subscription_messages.py b/src/specklepy/core/api/models/subscription_messages.py index 35e4ed70..1ba65af6 100644 --- a/src/specklepy/core/api/models/subscription_messages.py +++ b/src/specklepy/core/api/models/subscription_messages.py @@ -1,7 +1,5 @@ from typing import Optional -from pydantic import BaseModel - from specklepy.core.api.enums import ( ProjectModelsUpdatedMessageType, ProjectUpdatedMessageType, @@ -9,28 +7,29 @@ UserProjectsUpdatedMessageType, ) from specklepy.core.api.models.current import Model, Project, Version +from specklepy.core.api.models.graphql_base_model import GraphQLBaseModel -class UserProjectsUpdatedMessage(BaseModel): +class UserProjectsUpdatedMessage(GraphQLBaseModel): id: str type: UserProjectsUpdatedMessageType project: Optional[Project] -class ProjectModelsUpdatedMessage(BaseModel): +class ProjectModelsUpdatedMessage(GraphQLBaseModel): id: str type: ProjectModelsUpdatedMessageType model: Optional[Model] -class ProjectUpdatedMessage(BaseModel): +class ProjectUpdatedMessage(GraphQLBaseModel): id: str type: ProjectUpdatedMessageType project: Optional[Project] -class ProjectVersionsUpdatedMessage(BaseModel): +class ProjectVersionsUpdatedMessage(GraphQLBaseModel): id: str type: ProjectVersionsUpdatedMessageType - modelId: Optional[str] + model_id: Optional[str] version: Optional[Version] diff --git a/src/specklepy/core/api/resources/current/active_user_resource.py b/src/specklepy/core/api/resources/current/active_user_resource.py index 633818c7..f282514c 100644 --- a/src/specklepy/core/api/resources/current/active_user_resource.py +++ b/src/specklepy/core/api/resources/current/active_user_resource.py @@ -87,7 +87,7 @@ def _update(self, input: UserUpdateInput) -> User: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = {"input": input.model_dump(warnings="error", by_alias=True)} return self.make_request_and_parse_response( DataResponse[DataResponse[User]], QUERY, variables @@ -162,7 +162,9 @@ def get_projects( variables = { "limit": limit, "cursor": cursor, - "filter": filter.model_dump(warnings="error") if filter else None, + "filter": filter.model_dump(warnings="error", by_alias=True) + if filter + else None, } response = self.make_request_and_parse_response( diff --git a/src/specklepy/core/api/resources/current/model_resource.py b/src/specklepy/core/api/resources/current/model_resource.py index c9f59c06..a9a51f37 100644 --- a/src/specklepy/core/api/resources/current/model_resource.py +++ b/src/specklepy/core/api/resources/current/model_resource.py @@ -138,7 +138,7 @@ def get_with_versions( "versionsLimit": versions_limit, "versionsCursor": versions_cursor, "versionsFilter": ( - versions_filter.model_dump(warnings="error") + versions_filter.model_dump(warnings="error", by_alias=True) if versions_filter else None ), @@ -201,7 +201,9 @@ def get_models( "modelsLimit": models_limit, "modelsCursor": models_cursor, "modelsFilter": ( - models_filter.model_dump(warnings="error") if models_filter else None + models_filter.model_dump(warnings="error", by_alias=True) + if models_filter + else None ), } @@ -238,7 +240,7 @@ def create(self, input: CreateModelInput) -> Model: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( @@ -256,7 +258,7 @@ def delete(self, input: DeleteModelInput) -> bool: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = {"input": input.model_dump(warnings="error", by_alias=True)} return self.make_request_and_parse_response( DataResponse[DataResponse[bool]], QUERY, variables @@ -291,7 +293,7 @@ def update(self, input: UpdateModelInput) -> Model: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( diff --git a/src/specklepy/core/api/resources/current/project_invite_resource.py b/src/specklepy/core/api/resources/current/project_invite_resource.py index 6ea5a3ca..f8dbc867 100644 --- a/src/specklepy/core/api/resources/current/project_invite_resource.py +++ b/src/specklepy/core/api/resources/current/project_invite_resource.py @@ -103,7 +103,7 @@ def create( variables = { "projectId": project_id, - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( @@ -124,7 +124,7 @@ def use(self, input: ProjectInviteUseInput) -> bool: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( diff --git a/src/specklepy/core/api/resources/current/project_resource.py b/src/specklepy/core/api/resources/current/project_resource.py index 3c42f479..279a1845 100644 --- a/src/specklepy/core/api/resources/current/project_resource.py +++ b/src/specklepy/core/api/resources/current/project_resource.py @@ -118,7 +118,9 @@ def get_with_models( "modelsLimit": models_limit, "modelsCursor": models_cursor, "modelsFilter": ( - models_filter.model_dump(warnings="error") if models_filter else None + models_filter.model_dump(warnings="error", by_alias=True) + if models_filter + else None ), } @@ -218,7 +220,7 @@ def create(self, input: ProjectCreateInput) -> Project: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( @@ -248,7 +250,7 @@ def update(self, input: ProjectUpdateInput) -> Project: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( @@ -337,7 +339,7 @@ def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( diff --git a/src/specklepy/core/api/resources/current/server_resource.py b/src/specklepy/core/api/resources/current/server_resource.py index 07ffb1bc..9dff8485 100644 --- a/src/specklepy/core/api/resources/current/server_resource.py +++ b/src/specklepy/core/api/resources/current/server_resource.py @@ -61,10 +61,10 @@ def get(self) -> ServerInfo: query=query, return_type="serverInfo", schema=ServerInfo ) if isinstance(server_info, ServerInfo) and isinstance( - server_info.canonicalUrl, str + server_info.canonical_url, str ): r = requests.get( - server_info.canonicalUrl, headers={"User-Agent": "specklepy SDK"} + server_info.canonical_url, headers={"User-Agent": "specklepy SDK"} ) if "x-speckle-frontend-2" in r.headers: server_info.frontend2 = True diff --git a/src/specklepy/core/api/resources/current/version_resource.py b/src/specklepy/core/api/resources/current/version_resource.py index 8b3738b4..d6f04b9c 100644 --- a/src/specklepy/core/api/resources/current/version_resource.py +++ b/src/specklepy/core/api/resources/current/version_resource.py @@ -117,7 +117,9 @@ def get_versions( "modelId": model_id, "limit": limit, "cursor": cursor, - "filter": filter.model_dump(warnings="error") if filter else None, + "filter": ( + filter.model_dump(warnings="error", by_alias=True) if filter else None + ), } return self.make_request_and_parse_response( @@ -126,26 +128,39 @@ def get_versions( variables, ).data.data.data - def create(self, input: CreateVersionInput) -> str: + def create(self, input: CreateVersionInput) -> Version: QUERY = gql( """ mutation Create($input: CreateVersionInput!) { data:versionMutations { data:create(input: $input) { - data:id + id + referencedObject + message + sourceApplication + createdAt + previewUrl + authorUser { + id + name + bio + company + verified + role + avatar + } } } } """ ) - variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( - DataResponse[DataResponse[DataResponse[str]]], QUERY, variables - ).data.data.data + DataResponse[DataResponse[Version]], QUERY, variables + ).data.data def update(self, input: UpdateVersionInput) -> Version: QUERY = gql( @@ -174,7 +189,7 @@ def update(self, input: UpdateVersionInput) -> Version: """ ) - variables = {"input": input.model_dump(warnings="error")} + variables = {"input": input.model_dump(warnings="error", by_alias=True)} return self.make_request_and_parse_response( DataResponse[DataResponse[Version]], QUERY, variables @@ -194,7 +209,7 @@ def move_to_model(self, input: MoveVersionsInput) -> str: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( @@ -213,7 +228,7 @@ def delete(self, input: DeleteVersionsInput) -> bool: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( @@ -232,7 +247,7 @@ def received(self, input: MarkReceivedVersionInput) -> bool: ) variables = { - "input": input.model_dump(warnings="error"), + "input": input.model_dump(warnings="error", by_alias=True), } return self.make_request_and_parse_response( diff --git a/src/specklepy/objects/__init__.py b/src/specklepy/objects/__init__.py index 3e9cd2a1..68d9cc75 100644 --- a/src/specklepy/objects/__init__.py +++ b/src/specklepy/objects/__init__.py @@ -1,6 +1,7 @@ -from .data_objects import DataObject, QgisObject +from .data_objects import Base, DataObject, QgisObject __all__ = [ + "Base", "DataObject", "QgisObject", ] diff --git a/tests/integration/client/current/test_active_user_resource.py b/tests/integration/client/current/test_active_user_resource.py index 042adb43..6f6d5308 100644 --- a/tests/integration/client/current/test_active_user_resource.py +++ b/tests/integration/client/current/test_active_user_resource.py @@ -59,5 +59,5 @@ def test_active_user_get_projects_with_filter(self, client: SpeckleClient): assert isinstance(res, ResourceCollection) assert len(res.items) == 1 - assert res.totalCount == 1 + assert res.total_count == 1 assert res.items[0].id == p1.id diff --git a/tests/integration/client/current/test_model_resource.py b/tests/integration/client/current/test_model_resource.py index 0c3f0aee..9d3e14e4 100644 --- a/tests/integration/client/current/test_model_resource.py +++ b/tests/integration/client/current/test_model_resource.py @@ -32,7 +32,7 @@ def test_project(self, client: SpeckleClient) -> Project: def test_model(self, client: SpeckleClient, test_project: Project) -> Model: model = client.model.create( CreateModelInput( - name="Test Model", description="", projectId=test_project.id + name="Test Model", description="", project_id=test_project.id ) ) return model @@ -48,7 +48,7 @@ def test_model_create( self, client: SpeckleClient, test_project: Project, name: str, description: str ): input = CreateModelInput( - name=name, description=description, projectId=test_project.id + name=name, description=description, project_id=test_project.id ) result = client.model.create(input) @@ -65,8 +65,8 @@ def test_model_get( assert result.id == test_model.id assert result.name == test_model.name assert result.description == test_model.description - assert result.createdAt == test_model.createdAt - assert result.updatedAt == test_model.updatedAt + assert result.created_at == test_model.created_at + assert result.updated_at == test_model.updated_at def test_models_get_with_filter( self, client: SpeckleClient, test_model: Model, test_project: Project @@ -77,7 +77,7 @@ def test_models_get_with_filter( assert isinstance(result, ResourceCollection) assert len(result.items) == 1 - assert result.totalCount == 1 + assert result.total_count == 1 assert result.items[0].id == test_model.id def test_get_models( @@ -87,7 +87,7 @@ def test_get_models( assert isinstance(result, ResourceCollection) assert len(result.items) == 1 - assert result.totalCount == 1 + assert result.total_count == 1 assert result.items[0].id == test_model.id def test_project_get_models( @@ -99,7 +99,7 @@ def test_project_get_models( assert result.id == test_project.id assert isinstance(result.models, ResourceCollection) assert len(result.models.items) == 1 - assert result.models.totalCount == 1 + assert result.models.total_count == 1 assert result.models.items[0].id == test_model.id def test_project_get_models_with_filter( @@ -112,7 +112,7 @@ def test_project_get_models_with_filter( assert result.id == test_project.id assert isinstance(result.models, ResourceCollection) assert len(result.models.items) == 1 - assert result.models.totalCount == 1 + assert result.models.total_count == 1 assert result.models.items[0].id == test_model.id def test_model_update( @@ -125,7 +125,7 @@ def test_model_update( id=test_model.id, name=new_name, description=new_description, - projectId=test_project.id, + project_id=test_project.id, ) updated_model = client.model.update(update_data) @@ -134,12 +134,12 @@ def test_model_update( assert updated_model.id == test_model.id assert updated_model.name.lower() == new_name.lower() assert updated_model.description == new_description - assert updated_model.updatedAt >= test_model.updatedAt + assert updated_model.updated_at >= test_model.updated_at def test_model_delete( self, client: SpeckleClient, test_model: Model, test_project: Project ): - delete_data = DeleteModelInput(id=test_model.id, projectId=test_project.id) + delete_data = DeleteModelInput(id=test_model.id, project_id=test_project.id) response = client.model.delete(delete_data) assert response is True diff --git a/tests/integration/client/current/test_project_invite_resource.py b/tests/integration/client/current/test_project_invite_resource.py index e77d3141..33b32551 100644 --- a/tests/integration/client/current/test_project_invite_resource.py +++ b/tests/integration/client/current/test_project_invite_resource.py @@ -32,7 +32,7 @@ def created_invite( input = ProjectInviteCreateInput( email=second_client.account.userInfo.email, role=None, - serverRole=None, + server_role=None, userId=None, ) res = client.project_invite.create(project.id, input) @@ -45,7 +45,7 @@ def test_project_invite_create_by_email( input = ProjectInviteCreateInput( email=second_client.account.userInfo.email, role=None, - serverRole=None, + server_role=None, userId=None, ) res = client.project_invite.create(project.id, input) @@ -55,7 +55,7 @@ def test_project_invite_create_by_email( assert isinstance(res, ProjectWithTeam) assert res.id == project.id - assert len(res.invitedTeam) == 1 + assert len(res.invited_team) == 1 assert isinstance(invite.user, LimitedUser) assert invite.user.id == second_client.account.userInfo.id @@ -67,15 +67,15 @@ def test_project_invite_create_by_user_id( input = ProjectInviteCreateInput( email=None, role=None, - serverRole=None, + server_role=None, userId=second_client.account.userInfo.id, ) res = client.project_invite.create(project.id, input) assert isinstance(res, ProjectWithTeam) assert res.id == project.id - assert len(res.invitedTeam) == 1 - invited_team_member = res.invitedTeam[0].user + assert len(res.invited_team) == 1 + invited_team_member = res.invited_team[0].user assert isinstance(invited_team_member, LimitedUser) assert invited_team_member.id == second_client.account.userInfo.id @@ -89,7 +89,7 @@ def test_project_invite_get( project.id, created_invite.token ) assert isinstance(collaborator, PendingStreamCollaborator) - assert collaborator.inviteId == created_invite.inviteId + assert collaborator.invite_id == created_invite.invite_id assert isinstance(collaborator.user, LimitedUser) assert isinstance(created_invite.user, LimitedUser) @@ -115,7 +115,7 @@ def test_project_invite_use_member_added( assert created_invite.token input = ProjectInviteUseInput( - accept=True, projectId=created_invite.projectId, token=created_invite.token + accept=True, project_id=created_invite.projectId, token=created_invite.token ) res = second_client.project_invite.use(input) @@ -136,11 +136,11 @@ def test_project_invite_cancel_member_not_added( self, client: SpeckleClient, created_invite: PendingStreamCollaborator ): res = client.project_invite.cancel( - created_invite.projectId, created_invite.inviteId + created_invite.projectId, created_invite.invite_id ) assert isinstance(res, ProjectWithTeam) - assert len(res.invitedTeam) == 0 + assert len(res.invited_team) == 0 @pytest.mark.parametrize( "new_role", ["stream:owner", "stream:contributor", "stream:reviewer", None] @@ -156,15 +156,15 @@ def test_project_update_role( assert created_invite.token input = ProjectInviteUseInput( - accept=True, projectId=created_invite.projectId, token=created_invite.token + accept=True, project_id=created_invite.projectId, token=created_invite.token ) res = second_client.project_invite.use(input) invitee_id = second_client.account.userInfo.id assert invitee_id input = ProjectUpdateRoleInput( - userId=invitee_id, - projectId=project.id, + user_id=invitee_id, + project_id=project.id, role=new_role, ) res = client.project.update_role(input) diff --git a/tests/integration/client/current/test_project_resource.py b/tests/integration/client/current/test_project_resource.py index 05e25d5c..18be94f9 100644 --- a/tests/integration/client/current/test_project_resource.py +++ b/tests/integration/client/current/test_project_resource.py @@ -58,7 +58,7 @@ def test_project_get(self, client: SpeckleClient, test_project: Project): assert result.name == test_project.name assert result.description == test_project.description assert result.visibility == test_project.visibility - assert result.createdAt == test_project.createdAt + assert result.created_at == test_project.created_at def test_project_update(self, client: SpeckleClient, test_project: Project): new_name = "MY new name" diff --git a/tests/integration/client/current/test_subscription_resource.py b/tests/integration/client/current/test_subscription_resource.py index cf2d5a1d..3e3565a9 100644 --- a/tests/integration/client/current/test_subscription_resource.py +++ b/tests/integration/client/current/test_subscription_resource.py @@ -50,7 +50,7 @@ def test_model( ) -> Model: model1 = subscription_client.model.create( CreateModelInput( - name="Test Model 1", description="", projectId=test_project.id + name="Test Model 1", description="", project_id=test_project.id ) ) return model1 @@ -107,7 +107,7 @@ def callback(d: ProjectModelsUpdatedMessage): await asyncio.sleep(WAIT_PERIOD) # Give time to subscription to be setup input = CreateModelInput( - name="my model", description="myDescription", projectId=test_project.id + name="my model", description="myDescription", project_id=test_project.id ) created = subscription_client.model.create(input) diff --git a/tests/integration/client/current/test_version_resource.py b/tests/integration/client/current/test_version_resource.py index 498808f7..b357eb1a 100644 --- a/tests/integration/client/current/test_version_resource.py +++ b/tests/integration/client/current/test_version_resource.py @@ -33,7 +33,7 @@ def test_project(self, client: SpeckleClient) -> Project: def test_model_1(self, client: SpeckleClient, test_project: Project) -> Model: model1 = client.model.create( CreateModelInput( - name="Test Model 1", description="", projectId=test_project.id + name="Test Model 1", description="", project_id=test_project.id ) ) return model1 @@ -42,7 +42,7 @@ def test_model_1(self, client: SpeckleClient, test_project: Project) -> Model: def test_model_2(self, client: SpeckleClient, test_project: Project) -> Model: model2 = client.model.create( CreateModelInput( - name="Test Model 2", description="", projectId=test_project.id + name="Test Model 2", description="", project_id=test_project.id ) ) return model2 @@ -73,7 +73,7 @@ def test_versions_get( assert isinstance(result, ResourceCollection) assert len(result.items) == 1 - assert result.totalCount == 1 + assert result.total_count == 1 assert result.items[0].id == test_version.id def test_versions_get_with_filter( @@ -84,7 +84,7 @@ def test_versions_get_with_filter( test_version: Version, ): filter = ModelVersionsFilter( - priorityIds=[test_version.id], priorityIdsOnly=True + priority_ids=[test_version.id], priority_ids_only=True ) result = client.version.get_versions( @@ -93,16 +93,16 @@ def test_versions_get_with_filter( assert isinstance(result, ResourceCollection) assert len(result.items) == 1 - assert result.totalCount == 1 + assert result.total_count == 1 assert result.items[0].id == test_version.id def test_version_received( self, client: SpeckleClient, test_version: Version, test_project: Project ): input = MarkReceivedVersionInput( - versionId=test_version.id, - projectId=test_project.id, - sourceApplication="Integration test", + version_id=test_version.id, + project_id=test_project.id, + source_application="Integration test", ) result = client.version.received(input) @@ -120,7 +120,7 @@ def test_model_get_with_versions( assert isinstance(result, ModelWithVersions) assert result.id == test_model_1.id assert len(result.versions.items) == 1 - assert result.versions.totalCount == 1 + assert result.versions.total_count == 1 assert result.versions.items[0].id == test_version.id def test_model_get_with_versions_with_filter( @@ -131,7 +131,7 @@ def test_model_get_with_versions_with_filter( test_version: Version, ): filter = ModelVersionsFilter( - priorityIds=[test_version.id], priorityIdsOnly=True + priority_ids=[test_version.id], priority_ids_only=True ) result = client.model.get_with_versions( @@ -140,7 +140,7 @@ def test_model_get_with_versions_with_filter( assert isinstance(result, ModelWithVersions) assert len(result.versions.items) == 1 - assert result.versions.totalCount == 1 + assert result.versions.total_count == 1 assert isinstance(result.versions, ResourceCollection) assert result.versions.items[0].id == test_version.id @@ -149,14 +149,14 @@ def test_version_update( ): new_message = "MY new version message" input = UpdateVersionInput( - versionId=test_version.id, projectId=test_project.id, message=new_message + version_id=test_version.id, project_id=test_project.id, message=new_message ) updated_version = client.version.update(input) assert isinstance(updated_version, Version) assert updated_version.id == test_version.id assert updated_version.message == new_message - assert updated_version.previewUrl == test_version.previewUrl + assert updated_version.preview_url == test_version.preview_url def test_version_move_to_model( self, @@ -166,9 +166,9 @@ def test_version_move_to_model( test_model_2: Model, ): input = MoveVersionsInput( - targetModelName=test_model_2.name, - versionIds=[test_version.id], - projectId=test_project.id, + target_model_name=test_model_2.name, + version_ids=[test_version.id], + project_id=test_project.id, ) moved_model_id = client.version.move_to_model(input) @@ -179,13 +179,13 @@ def test_version_move_to_model( assert isinstance(moved_version, Version) assert moved_version.id == test_version.id assert moved_version.message == test_version.message - assert moved_version.previewUrl == test_version.previewUrl + assert moved_version.preview_url == test_version.preview_url def test_version_delete( self, client: SpeckleClient, test_version: Version, test_project: Project ): input = DeleteVersionsInput( - versionIds=[test_version.id], projectId=test_project.id + version_ids=[test_version.id], project_id=test_project.id ) response = client.version.delete(input) diff --git a/tests/integration/client/deprecated/test_stream.py b/tests/integration/client/deprecated/test_stream.py index 84ce5f7e..4eb317b7 100644 --- a/tests/integration/client/deprecated/test_stream.py +++ b/tests/integration/client/deprecated/test_stream.py @@ -172,7 +172,7 @@ def test_stream_invite_cancel( invites = client.stream.get_all_pending_invites(stream_id=stream.id) cancelled = client.stream.invite_cancel( - invite_id=invites[0].inviteId, stream_id=stream.id + invite_id=invites[0].invite_id, stream_id=stream.id ) assert cancelled is True diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 7ab22290..29234bfd 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -68,10 +68,9 @@ def create_version(client: SpeckleClient, project_id: str, model_id: str) -> Ver Base(applicationId="ASDF"), [remote], use_default_cache=False ) input = CreateVersionInput( - objectId=objectId, modelId=model_id, projectId=project_id + object_id=objectId, model_id=model_id, project_id=project_id ) - version_id = client.version.create(input) - return client.version.get(version_id, project_id) + return client.version.create(input) @pytest.fixture(scope="session") diff --git a/tests/integration/speckle_automate/test_automation_context.py b/tests/integration/speckle_automate/test_automation_context.py index b26c351c..597ee16f 100644 --- a/tests/integration/speckle_automate/test_automation_context.py +++ b/tests/integration/speckle_automate/test_automation_context.py @@ -19,6 +19,7 @@ ) from speckle_automate.schema import AutomateBase from specklepy.api.client import SpeckleClient +from specklepy.core.api.models.current import Model, Version from specklepy.objects.base import Base @@ -216,12 +217,11 @@ def test_create_version_in_project( ) -> None: root_object = Base() root_object.foo = "bar" - model_id, version_id = automation_context.create_new_version_in_project( + model, version = automation_context.create_new_version_in_project( root_object, "foobar" ) - - assert model_id is not None - assert version_id is not None + isinstance(model, Model) + isinstance(version, Version) @pytest.mark.skip( diff --git a/tests/unit/test_account_server_migration.py b/tests/unit/test_account_server_migration.py index a0af9c69..475495b8 100644 --- a/tests/unit/test_account_server_migration.py +++ b/tests/unit/test_account_server_migration.py @@ -19,7 +19,7 @@ def _create_account( serverInfo=ServerInfo( url=url, name="myServer", - migration=ServerMigration(movedTo=movedTo, movedFrom=movedFrom), + migration=ServerMigration(moved_to=movedTo, moved_from=movedFrom), ), userInfo=UserInfo(id=id), ) diff --git a/uv.lock b/uv.lock index 103c5973..6c541351 100644 --- a/uv.lock +++ b/uv.lock @@ -1383,7 +1383,6 @@ wheels = [ [[package]] name = "specklepy" -version = "2.21.3.dev74" source = { editable = "." } dependencies = [ { name = "appdirs" },