Compare commits

...

19 Commits

Author SHA1 Message Date
Jonathon Broughton 89280acf53 Update fixtures.py
Minor upgrade of ruff breaks without this fix
2025-02-10 14:57:26 +00:00
Chuck Driesler 78c55b787f chore(automate): improve error message when automate fails to receive a model version (#376)
Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-01-24 11:28:15 +00:00
Jedd Morgan 34f2dc2ab6 author now optional (#377) 2025-01-24 11:01:09 +00:00
Gergő Jedlicska d71b616e2b Merge pull request #363 from specklesystems/jrm/filter-tests
Added tests for filters
2024-12-11 14:21:54 +01:00
Jedd Morgan 35750f12c5 Merge branch 'main' into jrm/filter-tests 2024-12-11 13:05:18 +00:00
Jedd Morgan 82b6dbbe78 isort 2024-12-11 12:09:56 +00:00
Jedd Morgan 883be4b27b reexport inputs 2024-12-11 12:07:37 +00:00
Gergő Jedlicska 8dcc67fb31 Merge pull request #365 from specklesystems/jedd/cxpla-132-update-connectors-usage-of-user-queries-to-use-activeuser
updated active user streams
2024-12-10 15:57:04 +01:00
Jedd Morgan ed84820995 fix 2024-12-10 14:27:19 +00:00
Jedd Morgan 5c3dcb7bc0 updated active user streams 2024-12-10 14:14:31 +00:00
Gergő Jedlicska a0e10aae99 Merge pull request #362 from specklesystems/jrm/deps
Updated dependencies
2024-12-09 12:26:56 +01:00
Jedd Morgan bbea2a0d76 Aligned pre-commit-hooks 2024-12-09 11:24:38 +00:00
Jedd Morgan a05ac3479b Black reformat 2024-12-09 11:17:26 +00:00
Jedd Morgan 0bd972945e update dependencies 2024-12-09 11:14:54 +00:00
Jedd Morgan f200544065 Moved UserProjectsFilter to UserInputs 2024-12-09 10:58:31 +00:00
Jedd Morgan 68ce9823ae added filter tests 2024-12-09 10:56:17 +00:00
Mucahit Bilal GOKER a920352407 fix(api): rename 'onlyWithRole' to 'onlyWithRoles' in UserProjectsFilter (#361) 2024-12-06 15:53:10 +00:00
Chuck Driesler bd38dfacc7 fix(automate): include project id in run reporting (#356) 2024-11-26 14:48:43 +00:00
Chuck Driesler 281483f0fc fix(automate): add success result case (#355) 2024-11-21 12:07:48 +00:00
26 changed files with 1009 additions and 771 deletions
+3 -3
View File
@@ -2,7 +2,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
hooks:
- id: ruff
rev: v0.1.6
rev: v0.8.2
- repo: https://github.com/commitizen-tools/commitizen
hooks:
@@ -13,12 +13,12 @@ repos:
rev: v3.13.0
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.11.0
rev: 24.10.0
hooks:
- id: black
# It is recommended to specify the latest version of Python
Generated
+788 -706
View File
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -15,7 +15,7 @@ packages = [
[tool.poetry.dependencies]
python = ">=3.8.0, <4.0"
python = ">=3.9.0, <4.0"
pydantic = "^2.5"
appdirs = "^1.4.4"
gql = { extras = ["requests", "websockets"], version = "^3.3.0" }
@@ -26,19 +26,19 @@ attrs = "^23.1.0"
httpx = "^0.25.0"
[tool.poetry.group.dev.dependencies]
black = "23.11.0"
isort = "^5.7.0"
black = "24.10.0"
isort = "^5.13.2"
pytest = "^7.1.3"
pytest-asyncio = "^0.23.0"
pytest-ordering = "^0.6"
pytest-cov = "^3.0.0"
devtools = "^0.8.0"
pylint = "^2.14.4"
pylint = "^3.3.2"
pydantic-settings = "^2.3.0"
mypy = "^0.982"
pre-commit = "^2.20.0"
commitizen = "^2.38.0"
ruff = "^0.4.4"
commitizen = "^3.13.0"
ruff = "^0.8.2"
types-deprecated = "^1.2.9"
types-ujson = "^5.6.0.0"
types-requests = "^2.28.11.5"
@@ -60,8 +60,7 @@ exclude = '''
'''
include = '\.pyi?$'
line-length = 88
target-version = ["py37", "py38", "py39", "py310", "py311"]
target-version = ["py39", "py310", "py311", "py312", "py313"]
[tool.commitizen]
name = "cz_conventional_commits"
+32 -2
View File
@@ -101,8 +101,17 @@ class AutomationContext:
commit = self.speckle_client.commit.get(
self.automation_run_data.project_id, version_id
)
if not commit.referencedObject:
raise ValueError("The commit has no referencedObject, cannot receive it.")
if not commit or not commit.referencedObject:
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}
"""
)
base = operations.receive(
commit.referencedObject, self._server_transport, self._memory_transport
)
@@ -206,6 +215,7 @@ class AutomationContext:
query = gql(
"""
mutation AutomateFunctionRunStatusReport(
$projectId: String!
$functionRunId: String!
$status: AutomateRunStatus!
$statusMessage: String
@@ -213,6 +223,7 @@ class AutomationContext:
$contextView: String
){
automateFunctionRunStatusReport(input: {
projectId: $projectId
functionRunId: $functionRunId
status: $status
statusMessage: $statusMessage
@@ -236,6 +247,7 @@ class AutomationContext:
object_results = None
params = {
"projectId": self.automation_run_data.project_id,
"functionRunId": self.automation_run_data.function_run_id,
"status": self.run_status.value,
"statusMessage": self._automation_result.status_message,
@@ -355,6 +367,24 @@ class AutomationContext:
visual_overrides,
)
def attach_success_to_objects(
self,
category: str,
object_ids: Union[str, List[str]],
message: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
visual_overrides: Optional[Dict[str, Any]] = None,
) -> None:
"""Add a new success case to the run results."""
self.attach_result_to_objects(
ObjectResultLevel.SUCCESS,
category,
object_ids,
message,
metadata,
visual_overrides,
)
def attach_info_to_objects(
self,
category: str,
+5 -4
View File
@@ -1,4 +1,5 @@
"""Some useful helpers for working with automation data."""
import secrets
import string
@@ -21,10 +22,10 @@ class TestAutomationEnvironment(BaseSettings):
extra="ignore",
)
token: str = Field()
server_url: str = Field()
project_id: str = Field()
automation_id: str = Field()
token: str = Field(default="")
server_url: str = Field(default="")
project_id: str = Field(default="")
automation_id: str = Field(default="")
@pytest.fixture()
+4 -8
View File
@@ -61,15 +61,13 @@ def _parse_input_data(
def execute_automate_function(
automate_function: AutomateFunction[T],
input_schema: type[T],
) -> None:
...
) -> None: ...
@overload
def execute_automate_function(
automate_function: AutomateFunctionWithoutInputs,
) -> None:
...
) -> None: ...
class AutomateGenerateJsonSchema(GenerateJsonSchema):
@@ -146,16 +144,14 @@ def run_function(
automation_context: AutomationContext,
automate_function: AutomateFunction[T],
inputs: T,
) -> AutomationContext:
...
) -> AutomationContext: ...
@overload
def run_function(
automation_context: AutomationContext,
automate_function: AutomateFunctionWithoutInputs,
) -> AutomationContext:
...
) -> AutomationContext: ...
def run_function(
+1
View File
@@ -69,6 +69,7 @@ class AutomationStatus(str, Enum):
class ObjectResultLevel(str, Enum):
"""Possible status message levels for object reports."""
SUCCESS = "SUCCESS"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
+2 -2
View File
@@ -7,12 +7,12 @@ from specklepy.api.resources import (
OtherUserResource,
ProjectInviteResource,
ProjectResource,
ServerResource,
SubscriptionResource,
VersionResource,
branch,
commit,
object,
server,
stream,
subscriptions,
user,
@@ -69,7 +69,7 @@ class SpeckleClient(CoreSpeckleClient):
self.account = Account()
def _init_resources(self) -> None:
self.server = server.Resource(
self.server = ServerResource(
account=self.account, basepath=self.url, client=self.httpclient
)
@@ -3,8 +3,7 @@ from typing import List, Optional, overload
from deprecated import deprecated
from specklepy.core.api.inputs.project_inputs import UserProjectsFilter
from specklepy.core.api.inputs.user_inputs import UserUpdateInput
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
from specklepy.core.api.models import (
PendingStreamCollaborator,
Project,
@@ -44,12 +43,10 @@ class ActiveUserResource(CoreResource):
company: Optional[str] = None,
bio: Optional[str] = None,
avatar: Optional[str] = None,
) -> User:
...
) -> User: ...
@overload
def update(self, *, input: UserUpdateInput) -> User:
...
def update(self, *, input: UserUpdateInput) -> User: ...
def update(
self,
@@ -4,6 +4,6 @@ from specklepy.api.resources import ServerResource
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
@deprecated(reason="Renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION)
@deprecated(reason="Renamed to ServerResource", version=FE1_DEPRECATION_VERSION)
class Resource(ServerResource):
"""Renamed to ServerResource"""
+42
View File
@@ -0,0 +1,42 @@
from specklepy.core.api.inputs.model_inputs import (
CreateModelInput,
DeleteModelInput,
ModelVersionsFilter,
UpdateModelInput,
)
from specklepy.core.api.inputs.project_inputs import (
ProjectCreateInput,
ProjectInviteCreateInput,
ProjectInviteUseInput,
ProjectModelsFilter,
ProjectUpdateInput,
ProjectUpdateRoleInput,
)
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
from specklepy.core.api.inputs.version_inputs import (
CreateVersionInput,
DeleteVersionsInput,
MarkReceivedVersionInput,
MoveVersionsInput,
UpdateVersionInput,
)
__all__ = [
"CreateModelInput",
"DeleteModelInput",
"UpdateModelInput",
"ModelVersionsFilter",
"ProjectCreateInput",
"ProjectInviteCreateInput",
"ProjectInviteUseInput",
"ProjectModelsFilter",
"ProjectUpdateInput",
"ProjectUpdateRoleInput",
"UserProjectsFilter",
"UserUpdateInput",
"UpdateVersionInput",
"MoveVersionsInput",
"DeleteVersionsInput",
"CreateVersionInput",
"MarkReceivedVersionInput",
]
@@ -45,8 +45,3 @@ class ProjectUpdateRoleInput(BaseModel):
userId: str
projectId: str
role: Optional[str]
class UserProjectsFilter(BaseModel):
search: str
onlyWithRole: Optional[Sequence[str]] = None
+6 -1
View File
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Sequence
from pydantic import BaseModel
@@ -8,3 +8,8 @@ class UserUpdateInput(BaseModel):
bio: Optional[str] = None
company: Optional[str] = None
name: Optional[str] = None
class UserProjectsFilter(BaseModel):
search: str
onlyWithRoles: Optional[Sequence[str]] = None
+1 -1
View File
@@ -126,7 +126,7 @@ class Version(BaseModel):
class Model(BaseModel):
author: LimitedUser
author: Optional[LimitedUser]
createdAt: datetime
description: Optional[str]
displayName: str
@@ -4,8 +4,7 @@ from typing import List, Optional, overload
from deprecated import deprecated
from gql import gql
from specklepy.core.api.inputs.project_inputs import UserProjectsFilter
from specklepy.core.api.inputs.user_inputs import UserUpdateInput
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
from specklepy.core.api.models import (
ActivityCollection,
PendingStreamCollaborator,
@@ -100,12 +99,10 @@ class ActiveUserResource(ResourceBase):
company: Optional[str] = None,
bio: Optional[str] = None,
avatar: Optional[str] = None,
) -> User:
...
) -> User: ...
@overload
def update(self, *, input: UserUpdateInput) -> User:
...
def update(self, *, input: UserUpdateInput) -> User: ...
def update(
self,
@@ -127,9 +127,11 @@ class ModelResource(ResourceBase):
"modelId": model_id,
"versionsLimit": versions_limit,
"versionsCursor": versions_cursor,
"versionsFilter": versions_filter.model_dump(warnings="error")
if versions_filter
else None,
"versionsFilter": (
versions_filter.model_dump(warnings="error")
if versions_filter
else None
),
}
return self.make_request_and_parse_response(
@@ -179,9 +181,9 @@ class ModelResource(ResourceBase):
"projectId": project_id,
"modelsLimit": models_limit,
"modelsCursor": models_cursor,
"modelsFilter": models_filter.model_dump(warnings="error")
if models_filter
else None,
"modelsFilter": (
models_filter.model_dump(warnings="error") if models_filter else None
),
}
return self.make_request_and_parse_response(
@@ -108,9 +108,9 @@ class ProjectResource(ResourceBase):
"projectId": project_id,
"modelsLimit": models_limit,
"modelsCursor": models_cursor,
"modelsFilter": models_filter.model_dump(warnings="error")
if models_filter
else None,
"modelsFilter": (
models_filter.model_dump(warnings="error") if models_filter else None
),
}
return self.make_request_and_parse_response(
@@ -111,7 +111,7 @@ class Resource(ResourceBase):
query = gql(
"""
query User($stream_limit: Int!) {
user {
activeUser {
id
bio
name
@@ -149,7 +149,7 @@ class Resource(ResourceBase):
params = {"stream_limit": stream_limit}
return self.make_request(
query=query, params=params, return_type=["user", "streams", "items"]
query=query, params=params, return_type=["activeUser", "streams", "items"]
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
@@ -1,6 +1,7 @@
"""
Provides uniform and consistent path helpers for `specklepy`
"""
import os
import sys
from pathlib import Path
+3 -3
View File
@@ -295,9 +295,9 @@ class RevitParameter(Base, speckle_type="Objects.BuiltElements.Revit.Parameter")
value: Any = None
applicationUnitType: Optional[str] = None # eg UnitType UT_Length
applicationUnit: Optional[str] = None # DisplayUnitType eg DUT_MILLIMITERS
applicationInternalName: Optional[
str
] = None # BuiltInParameterName or GUID for shared parameter
applicationInternalName: Optional[str] = (
None # BuiltInParameterName or GUID for shared parameter
)
isShared: bool = False
isReadOnly: bool = False
isTypeParameter: bool = False
@@ -2,7 +2,7 @@ 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 UserUpdateInput
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
from specklepy.core.api.models import ResourceCollection, User
@@ -42,3 +42,21 @@ class TestActiveUserResource:
assert len(res.items) == len(existing.items) + 2
assert any(project.id == p1.id for project in res.items)
assert any(project.id == p2.id for project in res.items)
def test_active_user_get_projects_with_filter(self, client: SpeckleClient):
# Since the client may be reused for other tests,
# this test does rely on no other test creating a project with "Search for me" in its name
p1 = client.project.create(
ProjectCreateInput(name="Search for me!", description=None, visibility=None)
)
_ = client.project.create(
ProjectCreateInput(name="But not me!", description=None, visibility=None)
)
filter = UserProjectsFilter(search="Search for me")
res = client.active_user.get_projects(filter=filter)
assert isinstance(res, ResourceCollection)
assert len(res.items) == 1
assert res.totalCount == 1
assert res.items[0].id == p1.id
@@ -6,7 +6,10 @@ from specklepy.core.api.inputs.model_inputs import (
DeleteModelInput,
UpdateModelInput,
)
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
from specklepy.core.api.inputs.project_inputs import (
ProjectCreateInput,
ProjectModelsFilter,
)
from specklepy.core.api.models.current import (
Model,
Project,
@@ -65,6 +68,18 @@ class TestModelResource:
assert result.createdAt == test_model.createdAt
assert result.updatedAt == test_model.updatedAt
def test_models_get_with_filter(
self, client: SpeckleClient, test_model: Model, test_project: Project
):
filter = ProjectModelsFilter(search=test_model.name)
result = client.model.get_models(test_project.id, models_filter=filter)
assert isinstance(result, ResourceCollection)
assert len(result.items) == 1
assert result.totalCount == 1
assert result.items[0].id == test_model.id
def test_get_models(
self, client: SpeckleClient, test_project: Project, test_model: Model
):
@@ -82,6 +97,20 @@ class TestModelResource:
assert isinstance(result, ProjectWithModels)
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.items[0].id == test_model.id
def test_project_get_models_with_filter(
self, client: SpeckleClient, test_project: Project, test_model: Model
):
filter = ProjectModelsFilter(search=test_model.name)
result = client.project.get_with_models(test_project.id, models_filter=filter)
assert isinstance(result, ProjectWithModels)
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.items[0].id == test_model.id
@@ -1,7 +1,7 @@
import pytest
from specklepy.api.client import SpeckleClient
from specklepy.core.api.inputs.model_inputs import CreateModelInput
from specklepy.core.api.inputs.model_inputs import CreateModelInput, ModelVersionsFilter
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
from specklepy.core.api.inputs.version_inputs import (
DeleteVersionsInput,
@@ -76,6 +76,26 @@ class TestVersionResource:
assert result.totalCount == 1
assert result.items[0].id == test_version.id
def test_versions_get_with_filter(
self,
client: SpeckleClient,
test_model_1: Model,
test_project: Project,
test_version: Version,
):
filter = ModelVersionsFilter(
priorityIds=[test_version.id], priorityIdsOnly=True
)
result = client.version.get_versions(
test_model_1.id, test_project.id, filter=filter
)
assert isinstance(result, ResourceCollection)
assert len(result.items) == 1
assert result.totalCount == 1
assert result.items[0].id == test_version.id
def test_version_received(
self, client: SpeckleClient, test_version: Version, test_project: Project
):
@@ -103,6 +123,27 @@ class TestVersionResource:
assert result.versions.totalCount == 1
assert result.versions.items[0].id == test_version.id
def test_model_get_with_versions_with_filter(
self,
client: SpeckleClient,
test_model_1: Model,
test_project: Project,
test_version: Version,
):
filter = ModelVersionsFilter(
priorityIds=[test_version.id], priorityIdsOnly=True
)
result = client.model.get_with_versions(
test_model_1.id, test_project.id, versions_filter=filter
)
assert isinstance(result, ModelWithVersions)
assert len(result.versions.items) == 1
assert result.versions.totalCount == 1
assert isinstance(result.versions, ResourceCollection)
assert result.versions.items[0].id == test_version.id
def test_version_update(
self, client: SpeckleClient, test_version: Version, test_project: Project
):
@@ -1,4 +1,5 @@
"""Run integration tests with a speckle server."""
import os
from pathlib import Path
from typing import Dict
+1 -1
View File
@@ -72,7 +72,7 @@ def line(point, interval):
start=point,
end=point,
domain=interval,
units="none"
units="none",
# These attributes are not handled in C#
# bbox=None,
# length=None
+1
View File
@@ -1,6 +1,7 @@
"""
Provides uniform and consistent path helpers for `specklepy`
"""
import os
import sys
from importlib import import_module, invalidate_caches